diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 47afdfd7d0f1..25f0a7b746d8 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -1347,10 +1347,39 @@ export namespace Config { } export async function update(config: Info) { - const filepath = path.join(Instance.directory, "config.json") - const existing = await loadFile(filepath) - await Filesystem.writeJson(filepath, mergeDeep(existing, config)) + const filepath = projectConfigFile() + const before = await Filesystem.readText(filepath).catch((err: any) => { + if (err.code === "ENOENT") return "{}" + throw new JsonError({ path: filepath }, { cause: err }) + }) + + const next = await (async () => { + if (!filepath.endsWith(".jsonc")) { + const existing = parseConfig(before, filepath) + const merged = mergeDeep(existing, config) + await Filesystem.writeJson(filepath, merged) + return merged + } + + const updated = patchJsonc(before, config) + const merged = parseConfig(updated, filepath) + await Filesystem.write(filepath, updated) + return merged + })() + await Instance.dispose() + return next + } + + function projectConfigFile() { + const candidates = ["opencode.jsonc", "opencode.json"].map((file) => + path.join(Instance.directory, file), + ) + for (const file of candidates) { + if (existsSync(file)) return file + } + // Default to opencode.json (standard project config file name) + return path.join(Instance.directory, "opencode.json") } function globalConfigFile() { diff --git a/packages/opencode/src/server/routes/config.ts b/packages/opencode/src/server/routes/config.ts index 85d28f6aa6b8..7737a70c9e3c 100644 --- a/packages/opencode/src/server/routes/config.ts +++ b/packages/opencode/src/server/routes/config.ts @@ -54,8 +54,8 @@ export const ConfigRoutes = lazy(() => validator("json", Config.Info), async (c) => { const config = c.req.valid("json") - await Config.update(config) - return c.json(config) + const next = await Config.update(config) + return c.json(next) }, ) .get( diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index baf209d86079..3b7c52c926a4 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -683,14 +683,51 @@ test("updates config and writes to file", async () => { directory: tmp.path, fn: async () => { const newConfig = { model: "updated/model" } - await Config.update(newConfig as any) + const result = await Config.update(newConfig as any) - const writtenConfig = await Filesystem.readJson(path.join(tmp.path, "config.json")) + // Should return merged config + expect(result.model).toBe("updated/model") + + // Should write to opencode.json by default (not config.json) + const writtenConfig = await Filesystem.readJson(path.join(tmp.path, "opencode.json")) expect(writtenConfig.model).toBe("updated/model") }, }) }) +test("updates existing opencode.json instead of creating config.json", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + // Create an existing opencode.json file + await writeConfig(dir, { + $schema: "https://opencode.ai/config.json", + model: "original/model", + username: "testuser", + }) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const newConfig = { model: "updated/model" } + const result = await Config.update(newConfig as any) + + // Should return merged config with both old and new fields + expect(result.model).toBe("updated/model") + expect(result.username).toBe("testuser") + + // Should update existing opencode.json (not create config.json) + const writtenConfig = await Filesystem.readJson(path.join(tmp.path, "opencode.json")) + expect(writtenConfig.model).toBe("updated/model") + expect(writtenConfig.username).toBe("testuser") // Should preserve existing fields + + // config.json should not be created + const configJsonExists = await Filesystem.exists(path.join(tmp.path, "config.json")) + expect(configJsonExists).toBe(false) + }, + }) +}) + test("gets config directories", async () => { await using tmp = await tmpdir() await Instance.provide({