mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-11-02 13:13:20 +08:00
Merge pull request #26 from Kilo-Org/fix-env-injection
fix(sdk): preserve OPENCODE_CONFIG_CONTENT when spawning CLI
This commit is contained in:
@@ -1,6 +1,39 @@
|
||||
import { spawn } from "node:child_process"
|
||||
import { type Config } from "./gen/types.gen.js"
|
||||
|
||||
// kilocode_change start - Merge existing OPENCODE_CONFIG_CONTENT with new config
|
||||
// This preserves Kilocode-injected modes when spawning nested CLI instances
|
||||
function mergeConfig(existing: Config | undefined, incoming: Config | undefined): Config {
|
||||
const base = existing ?? {}
|
||||
const override = incoming ?? {}
|
||||
return {
|
||||
...base,
|
||||
...override,
|
||||
agent: { ...base.agent, ...override.agent },
|
||||
command: { ...base.command, ...override.command },
|
||||
mcp: { ...base.mcp, ...override.mcp },
|
||||
mode: { ...base.mode, ...override.mode },
|
||||
plugin: [...(base.plugin ?? []), ...(override.plugin ?? [])],
|
||||
instructions: [...(base.instructions ?? []), ...(override.instructions ?? [])],
|
||||
}
|
||||
}
|
||||
|
||||
function parseExistingConfig(): Config | undefined {
|
||||
const content = process.env.OPENCODE_CONFIG_CONTENT
|
||||
if (!content) return undefined
|
||||
try {
|
||||
return JSON.parse(content)
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export function buildConfigEnv(config?: Config): string {
|
||||
const merged = mergeConfig(parseExistingConfig(), config)
|
||||
return JSON.stringify(merged)
|
||||
}
|
||||
// kilocode_change end
|
||||
|
||||
export type ServerOptions = {
|
||||
hostname?: string
|
||||
port?: number
|
||||
@@ -35,7 +68,7 @@ export async function createOpencodeServer(options?: ServerOptions) {
|
||||
signal: options.signal,
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCODE_CONFIG_CONTENT: JSON.stringify(options.config ?? {}),
|
||||
OPENCODE_CONFIG_CONTENT: buildConfigEnv(options.config), // kilocode_change
|
||||
},
|
||||
})
|
||||
|
||||
@@ -111,7 +144,7 @@ export function createOpencodeTui(options?: TuiOptions) {
|
||||
stdio: "inherit",
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCODE_CONFIG_CONTENT: JSON.stringify(options?.config ?? {}),
|
||||
OPENCODE_CONFIG_CONTENT: buildConfigEnv(options?.config), // kilocode_change
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,39 @@
|
||||
import { spawn } from "node:child_process"
|
||||
import { type Config } from "./gen/types.gen.js"
|
||||
|
||||
// kilocode_change start - Merge existing OPENCODE_CONFIG_CONTENT with new config
|
||||
// This preserves Kilocode-injected modes when spawning nested CLI instances
|
||||
function mergeConfig(existing: Config | undefined, incoming: Config | undefined): Config {
|
||||
const base = existing ?? {}
|
||||
const override = incoming ?? {}
|
||||
return {
|
||||
...base,
|
||||
...override,
|
||||
agent: { ...base.agent, ...override.agent },
|
||||
command: { ...base.command, ...override.command },
|
||||
mcp: { ...base.mcp, ...override.mcp },
|
||||
mode: { ...base.mode, ...override.mode },
|
||||
plugin: [...(base.plugin ?? []), ...(override.plugin ?? [])],
|
||||
instructions: [...(base.instructions ?? []), ...(override.instructions ?? [])],
|
||||
}
|
||||
}
|
||||
|
||||
function parseExistingConfig(): Config | undefined {
|
||||
const content = process.env.OPENCODE_CONFIG_CONTENT
|
||||
if (!content) return undefined
|
||||
try {
|
||||
return JSON.parse(content)
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export function buildConfigEnv(config?: Config): string {
|
||||
const merged = mergeConfig(parseExistingConfig(), config)
|
||||
return JSON.stringify(merged)
|
||||
}
|
||||
// kilocode_change end
|
||||
|
||||
export type ServerOptions = {
|
||||
hostname?: string
|
||||
port?: number
|
||||
@@ -35,7 +68,7 @@ export async function createOpencodeServer(options?: ServerOptions) {
|
||||
signal: options.signal,
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCODE_CONFIG_CONTENT: JSON.stringify(options.config ?? {}),
|
||||
OPENCODE_CONFIG_CONTENT: buildConfigEnv(options.config), // kilocode_change
|
||||
},
|
||||
})
|
||||
|
||||
@@ -111,7 +144,7 @@ export function createOpencodeTui(options?: TuiOptions) {
|
||||
stdio: "inherit",
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCODE_CONFIG_CONTENT: JSON.stringify(options?.config ?? {}),
|
||||
OPENCODE_CONFIG_CONTENT: buildConfigEnv(options?.config), // kilocode_change
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
// kilocode_change start - Tests for OPENCODE_CONFIG_CONTENT merging
|
||||
import { describe, test, expect, beforeEach, afterEach } from "bun:test"
|
||||
import { buildConfigEnv } from "../src/server"
|
||||
|
||||
describe("buildConfigEnv", () => {
|
||||
const originalEnv = process.env.OPENCODE_CONFIG_CONTENT
|
||||
|
||||
beforeEach(() => {
|
||||
delete process.env.OPENCODE_CONFIG_CONTENT
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (originalEnv === undefined) {
|
||||
delete process.env.OPENCODE_CONFIG_CONTENT
|
||||
} else {
|
||||
process.env.OPENCODE_CONFIG_CONTENT = originalEnv
|
||||
}
|
||||
})
|
||||
|
||||
test("returns empty config when no existing env and no incoming config", () => {
|
||||
const result = buildConfigEnv()
|
||||
const parsed = JSON.parse(result)
|
||||
|
||||
expect(parsed).toEqual({
|
||||
agent: {},
|
||||
command: {},
|
||||
mcp: {},
|
||||
mode: {},
|
||||
plugin: [],
|
||||
instructions: [],
|
||||
})
|
||||
})
|
||||
|
||||
test("returns incoming config when no existing env", () => {
|
||||
const result = buildConfigEnv({
|
||||
agent: { custom: { mode: "primary", prompt: "test" } },
|
||||
})
|
||||
const parsed = JSON.parse(result)
|
||||
|
||||
expect(parsed.agent).toEqual({ custom: { mode: "primary", prompt: "test" } })
|
||||
})
|
||||
|
||||
test("preserves existing OPENCODE_CONFIG_CONTENT when spawning with new config", () => {
|
||||
// Simulate Kilocode having injected modes via OPENCODE_CONFIG_CONTENT
|
||||
process.env.OPENCODE_CONFIG_CONTENT = JSON.stringify({
|
||||
agent: {
|
||||
translate: { mode: "primary", prompt: "You are a translator" },
|
||||
},
|
||||
instructions: [".kilocode/rules/main.md"],
|
||||
})
|
||||
|
||||
// Now spawn with additional config
|
||||
const result = buildConfigEnv({
|
||||
agent: { review: { mode: "primary", prompt: "You are a reviewer" } },
|
||||
instructions: ["additional-rule.md"],
|
||||
})
|
||||
const parsed = JSON.parse(result)
|
||||
|
||||
// Both agents should be present
|
||||
expect(parsed.agent.translate).toEqual({ mode: "primary", prompt: "You are a translator" })
|
||||
expect(parsed.agent.review).toEqual({ mode: "primary", prompt: "You are a reviewer" })
|
||||
|
||||
// Both instructions should be present
|
||||
expect(parsed.instructions).toContain(".kilocode/rules/main.md")
|
||||
expect(parsed.instructions).toContain("additional-rule.md")
|
||||
})
|
||||
|
||||
test("incoming config overrides existing config for same keys", () => {
|
||||
process.env.OPENCODE_CONFIG_CONTENT = JSON.stringify({
|
||||
agent: { code: { mode: "primary", prompt: "Original prompt" } },
|
||||
model: "original-model",
|
||||
})
|
||||
|
||||
const result = buildConfigEnv({
|
||||
agent: { code: { mode: "primary", prompt: "New prompt" } },
|
||||
model: "new-model",
|
||||
})
|
||||
const parsed = JSON.parse(result)
|
||||
|
||||
// Agent should be overridden
|
||||
expect(parsed.agent.code.prompt).toBe("New prompt")
|
||||
// Top-level config should be overridden
|
||||
expect(parsed.model).toBe("new-model")
|
||||
})
|
||||
|
||||
test("handles invalid JSON in existing OPENCODE_CONFIG_CONTENT gracefully", () => {
|
||||
process.env.OPENCODE_CONFIG_CONTENT = "invalid json {"
|
||||
|
||||
const result = buildConfigEnv({
|
||||
agent: { test: { mode: "primary", prompt: "test" } },
|
||||
})
|
||||
const parsed = JSON.parse(result)
|
||||
|
||||
// Should still work with just the incoming config
|
||||
expect(parsed.agent.test).toEqual({ mode: "primary", prompt: "test" })
|
||||
})
|
||||
|
||||
test("merges plugins from both sources", () => {
|
||||
process.env.OPENCODE_CONFIG_CONTENT = JSON.stringify({
|
||||
plugin: ["plugin-a", "plugin-b"],
|
||||
})
|
||||
|
||||
const result = buildConfigEnv({
|
||||
plugin: ["plugin-c"],
|
||||
})
|
||||
const parsed = JSON.parse(result)
|
||||
|
||||
expect(parsed.plugin).toEqual(["plugin-a", "plugin-b", "plugin-c"])
|
||||
})
|
||||
})
|
||||
// kilocode_change end
|
||||
Reference in New Issue
Block a user