mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
feat(config): add escapeJson parameter to substitute for raw content handling
Add optional `escapeJson` parameter to `ConfigPaths.substitute` to control whether file content should be JSON-escaped. When `false`, multiline content and special characters are preserved as-is, which is required for agent markdown prompts that need raw file content rather than escaped strings. Update agent config processing to pass `escapeJson: false` when substituting file content into markdown prompts, ensuring proper handling of multiline files without unwanted escape sequences. Add comprehensive test coverage for the new parameter and existing substitution functionality.
This commit is contained in:
committed by
Catriel Müller
parent
80edeee58f
commit
3ca9941bbf
@@ -169,6 +169,7 @@ export async function load(dir: string, warnings?: Warning[]) {
|
||||
dir: path.dirname(item),
|
||||
source: item,
|
||||
missing: "empty",
|
||||
escapeJson: false,
|
||||
})
|
||||
|
||||
const config = {
|
||||
|
||||
@@ -19,6 +19,7 @@ type ParseSource =
|
||||
type SubstituteInput = ParseSource & {
|
||||
text: string
|
||||
missing?: "error" | "empty"
|
||||
escapeJson?: boolean
|
||||
}
|
||||
|
||||
function source(input: ParseSource) {
|
||||
@@ -32,6 +33,7 @@ function dir(input: ParseSource) {
|
||||
/** Apply {env:VAR} and {file:path} substitutions to config text. */
|
||||
export async function substitute(input: SubstituteInput) {
|
||||
const missing = input.missing ?? "error"
|
||||
const escape = input.escapeJson ?? true
|
||||
let text = input.text.replace(/\{env:([^}]+)\}/g, (_, varName) => {
|
||||
return process.env[varName] || ""
|
||||
})
|
||||
@@ -81,7 +83,7 @@ export async function substitute(input: SubstituteInput) {
|
||||
})
|
||||
).trim()
|
||||
|
||||
out += JSON.stringify(fileContent).slice(1, -1)
|
||||
out += escape ? JSON.stringify(fileContent).slice(1, -1) : fileContent
|
||||
cursor = index + token.length
|
||||
}
|
||||
|
||||
|
||||
@@ -85,4 +85,32 @@ describe("agent {file:...} syntax in markdown body", () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("preserves multiline content without escaping", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
git: true,
|
||||
init: async (dir) => {
|
||||
const multilineContent = ["Line 1", "Line 2", "Line 3"].join("\n")
|
||||
await Bun.write(path.join(dir, "multiline.md"), multilineContent)
|
||||
|
||||
const agentsDir = path.join(dir, ".kilo", "agents")
|
||||
await fs.mkdir(agentsDir, { recursive: true })
|
||||
await Bun.write(
|
||||
path.join(agentsDir, "test.md"),
|
||||
["---", "description: Test", "---", "Base", "", "{file:../../multiline.md}", ""].join("\n"),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const agent = await Agent.get("test")
|
||||
|
||||
expect(agent).toBeDefined()
|
||||
expect(agent?.prompt).toContain("Line 1\nLine 2\nLine 3")
|
||||
expect(agent?.prompt).not.toContain("Line 1\\nLine 2\\nLine 3")
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import path from "node:path"
|
||||
import fs from "node:fs/promises"
|
||||
import { tmpdir } from "../fixture/fixture"
|
||||
import { ConfigPaths } from "../../src/config/paths"
|
||||
|
||||
describe("ConfigPaths.substitute", () => {
|
||||
describe("escapeJson parameter", () => {
|
||||
test("escapes newlines when escapeJson is true (default)", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
git: true,
|
||||
init: async (dir) => {
|
||||
await Bun.write(path.join(dir, "test.txt"), "Line 1\nLine 2\nLine 3")
|
||||
},
|
||||
})
|
||||
|
||||
const result = await ConfigPaths.substitute(
|
||||
"{file:./test.txt}",
|
||||
{ dir: tmp.path, source: "config.json" },
|
||||
"error",
|
||||
true,
|
||||
)
|
||||
|
||||
expect(result).toBe("Line 1\\nLine 2\\nLine 3")
|
||||
})
|
||||
|
||||
test("preserves newlines when escapeJson is false", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
git: true,
|
||||
init: async (dir) => {
|
||||
await Bun.write(path.join(dir, "test.txt"), "Line 1\nLine 2\nLine 3")
|
||||
},
|
||||
})
|
||||
|
||||
const result = await ConfigPaths.substitute(
|
||||
"{file:./test.txt}",
|
||||
{ dir: tmp.path, source: "agent.md" },
|
||||
"empty",
|
||||
false,
|
||||
)
|
||||
|
||||
expect(result).toBe("Line 1\nLine 2\nLine 3")
|
||||
})
|
||||
|
||||
test("escapes quotes when escapeJson is true", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
git: true,
|
||||
init: async (dir) => {
|
||||
await Bun.write(path.join(dir, "test.txt"), 'Text with "quotes"')
|
||||
},
|
||||
})
|
||||
|
||||
const result = await ConfigPaths.substitute(
|
||||
"{file:./test.txt}",
|
||||
{ dir: tmp.path, source: "config.json" },
|
||||
"error",
|
||||
true,
|
||||
)
|
||||
|
||||
expect(result).toBe('Text with \\"quotes\\"')
|
||||
})
|
||||
|
||||
test("preserves quotes when escapeJson is false", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
git: true,
|
||||
init: async (dir) => {
|
||||
await Bun.write(path.join(dir, "test.txt"), 'Text with "quotes"')
|
||||
},
|
||||
})
|
||||
|
||||
const result = await ConfigPaths.substitute(
|
||||
"{file:./test.txt}",
|
||||
{ dir: tmp.path, source: "agent.md" },
|
||||
"empty",
|
||||
false,
|
||||
)
|
||||
|
||||
expect(result).toBe('Text with "quotes"')
|
||||
})
|
||||
})
|
||||
|
||||
describe("existing functionality", () => {
|
||||
test("substitutes {env:VAR} with environment variable", async () => {
|
||||
process.env.TEST_VAR = "test_value"
|
||||
|
||||
const result = await ConfigPaths.substitute("Value: {env:TEST_VAR}", { dir: "/tmp", source: "test.json" })
|
||||
|
||||
expect(result).toBe("Value: test_value")
|
||||
|
||||
delete process.env.TEST_VAR
|
||||
})
|
||||
|
||||
test("replaces missing {env:VAR} with empty string", async () => {
|
||||
const result = await ConfigPaths.substitute("Value: {env:NONEXISTENT_VAR}", { dir: "/tmp", source: "test.json" })
|
||||
|
||||
expect(result).toBe("Value: ")
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user