diff --git a/CHANGELOG.md b/CHANGELOG.md index dcbc2100..d48a55c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ All notable changes to this project will be documented in this file. - Sidebar: Recent rows show a compact timestamp on web and desktop, and pending permission/question badges are no longer covered by the hover actions (thanks to @makeittech). - Mobile: Chats — sessions that belong to no project — now appear in the sessions sheet above the project list; opening an already-open agent switches to its editor instead of duplicating it (thanks to @bashrusakh); Android connections can trust user-installed certificate authorities, such as a local proxy's (thanks to @Silvenga). - Settings: the editor font size survives a restart (thanks to @pascalandr); a change made right before closing the window is saved (thanks to @makeittech); number fields and selects no longer clip at large font sizes (thanks to @makeittech); refreshing GitHub account state no longer interrupts the page (thanks to @floze-the-genius); the Cloudflare Tunnel download link is fixed (thanks to @AyoubAchour); Windows skill paths are classified correctly, so disabled and duplicate skills are hidden as intended (thanks to @Ttungx). -- Small model: the model chosen in Settings now reaches the managed OpenCode process, so session titles use it (thanks to @makeittech); requests send the provider's configured headers, such as an API-gateway subscription key (thanks to @dmitrii-galantsev); a configured Anthropic endpoint is used without a doubled `/v1`, and Google models without reasoning no longer receive a thinking option (thanks to @mpeter and @IngTian). +- Small model: requests send the provider's configured headers, such as an API-gateway subscription key (thanks to @dmitrii-galantsev); a configured Anthropic endpoint is used without a doubled `/v1`, and Google models without reasoning no longer receive a thinking option (thanks to @mpeter and @IngTian). - Projects: the folder picker can enter a directory that is already a project to browse from there (thanks to @weixiang1862), and sending, forking, and image attachments work in projects whose path has non-ASCII characters, such as `Masaüstü` (thanks to @fitzgpt). - Git: the status panel refreshes from real repository state after checkout, branch, stash, merge, rebase, or reset, and remote branches that were never fetched appear in branch lists (thanks to @makeittech); the Branch diff scope no longer compares against the wrong base for branches created from the current branch (thanks to @gaojunran); picking `origin/main` in the branch selector checks out the local branch instead of a detached `HEAD` (thanks to @yulia-ivashko); branch search hides non-matching branches (thanks to @bashrusakh). - Updates: "Update OpenCode" no longer fails with a bare "Bad Request" — OpenChamber names the release to install and shows OpenCode's reason when refused — and the desktop "Restart to Update" button shows why an install failed, including an unsigned local build, and stays available to retry (thanks to @mdatsev and @yulia-ivashko). diff --git a/packages/web/server/index.js b/packages/web/server/index.js index 7fffdc21..7c174659 100644 --- a/packages/web/server/index.js +++ b/packages/web/server/index.js @@ -76,7 +76,6 @@ import { configureOpenCodeRuntimeProviders, resetOpenCodeRuntimeProviders } from import { createOpenCodeWatcherRuntime } from './lib/opencode/watcher.js'; import { createSessionAssistRuntime } from './lib/session-assist/runtime.js'; import { createSessionGoalRuntime } from './lib/session-goal/runtime.js'; -import { applySmallModelOverrideToOpenCodeConfig } from './lib/small-model/config-injection.js'; import { createContextObligatoryRuntime } from './lib/context-obligatory/runtime.js'; import { createSessionKnowledgeRuntime } from './lib/session-knowledge/runtime.js'; import { createScheduledTasksRuntime } from './lib/scheduled-tasks/runtime.js'; @@ -1205,25 +1204,11 @@ const openCodeLifecycleRuntime = createOpenCodeLifecycleRuntime({ const managedEnv = includeControl || includeWeb || includeMemory ? await (agentToolRuntime?.prepareManagedOpenCodeEnv({ includeControl, includeWeb, includeMemory }) || {}) : {}; - const envWithSystemPrompt = settings?.optimizeSystemPrompt === true - ? { - ...managedEnv, - ...(await systemPromptRuntime.prepareManagedOpenCodeEnv( - managedEnv.OPENCODE_CONFIG_CONTENT ?? process.env.OPENCODE_CONFIG_CONTENT, - )), - } - : managedEnv; + if (settings?.optimizeSystemPrompt !== true) return managedEnv; - // Apply the explicit Small Model override to the managed OpenCode config - // so OpenCode's own title/summary generation uses the user's chosen model. - const configContent = envWithSystemPrompt.OPENCODE_CONFIG_CONTENT ?? process.env.OPENCODE_CONFIG_CONTENT; - const withSmallModel = applySmallModelOverrideToOpenCodeConfig({ - configContent, - smallModelUseDefault: settings?.smallModelUseDefault, - smallModelOverride: settings?.smallModelOverride, - }); - if (withSmallModel === configContent) return envWithSystemPrompt; - return { ...envWithSystemPrompt, OPENCODE_CONFIG_CONTENT: withSmallModel }; + const configContent = managedEnv.OPENCODE_CONFIG_CONTENT ?? process.env.OPENCODE_CONFIG_CONTENT; + const systemPromptEnv = await systemPromptRuntime.prepareManagedOpenCodeEnv(configContent); + return { ...managedEnv, ...systemPromptEnv }; }, }); diff --git a/packages/web/server/lib/small-model/DOCUMENTATION.md b/packages/web/server/lib/small-model/DOCUMENTATION.md index 43a4536c..baee6280 100644 --- a/packages/web/server/lib/small-model/DOCUMENTATION.md +++ b/packages/web/server/lib/small-model/DOCUMENTATION.md @@ -137,20 +137,6 @@ other runtime API. - `routes.js` — `GET /api/small-model` (resolution preview) and `POST /api/small-model/generate` (`{ prompt, system?, maxOutputTokens?, model?, directory? }` → `{ text, providerID, modelID, source }`). -- `config-injection.js` — applies the Settings → Chat → Small Model override - to the config injected into the **managed OpenCode process** - (`OPENCODE_CONFIG_CONTENT`), so OpenCode's own internal `small_model` - consumers — session title and summary generation — use the user's explicit - choice instead of OpenCode's fallback chain. Only an explicit override - (`smallModelUseDefault === false` with a non-empty `smallModelOverride`) is - injected; "use default" leaves the config untouched so OpenCode's own - resolution stays authoritative. Wired into `getManagedOpenCodeEnv` in - `server/index.js`; the pure helper is unit-tested in - `config-injection.test.js`. External OpenCode servers are unaffected (they - are not launched with this env). The injected `small_model` is baked into - `OPENCODE_CONFIG_CONTENT` when the managed process spawns, so changing the - override in Settings applies on the next managed OpenCode restart, not to - the process already running. ## Which providers the pickers may offer diff --git a/packages/web/server/lib/small-model/config-injection.js b/packages/web/server/lib/small-model/config-injection.js deleted file mode 100644 index 3738ec8e..00000000 --- a/packages/web/server/lib/small-model/config-injection.js +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Applies the user's explicit Small Model override (Settings → Chat → Small - * Model) to the configuration injected into the managed OpenCode process. - * - * OpenCode's own session-title and summary generation reads `small_model` - * from its config layers. Previously the OpenChamber settings override only - * fed OpenChamber's own `/api/small-model/generate` utility service, so a - * configured Small Model never reached OpenCode's title generation and - * sessions kept their fallback/untitled state. Injecting the override as - * `small_model` in the managed `OPENCODE_CONFIG_CONTENT` closes that gap for - * the managed server. - * - * Only an explicit override applies (`smallModelUseDefault === false` with a - * non-empty `smallModelOverride`). "Use default" leaves the config untouched, - * so OpenCode's own resolution chain (config `small_model`, then its family - * scan) stays authoritative — this mirrors the precedence documented in - * `packages/web/server/lib/small-model/DOCUMENTATION.md`. - * - * Malformed user config is left untouched rather than rewritten: OpenCode's - * own loader is the right place to surface it, and silently rewriting it - * would hide the error. - */ -export const applySmallModelOverrideToOpenCodeConfig = ({ - configContent, - smallModelUseDefault, - smallModelOverride, -}) => { - if (smallModelUseDefault !== false) { - return configContent; - } - const override = typeof smallModelOverride === 'string' ? smallModelOverride.trim() : ''; - if (!override) { - return configContent; - } - - const current = (() => { - if (typeof configContent !== 'string' || configContent.trim().length === 0) { - return {}; - } - try { - return JSON.parse(configContent); - } catch { - return null; - } - })(); - if (current === null || typeof current !== 'object' || Array.isArray(current)) { - return configContent; - } - - return JSON.stringify({ ...current, small_model: override }); -}; diff --git a/packages/web/server/lib/small-model/config-injection.test.js b/packages/web/server/lib/small-model/config-injection.test.js deleted file mode 100644 index 22342038..00000000 --- a/packages/web/server/lib/small-model/config-injection.test.js +++ /dev/null @@ -1,90 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { applySmallModelOverrideToOpenCodeConfig } from './config-injection.js'; - -describe('applySmallModelOverrideToOpenCodeConfig', () => { - it('leaves config unchanged when use-default is not explicitly disabled', () => { - const config = '{"model":"anthropic/claude-sonnet-4-5"}'; - expect( - applySmallModelOverrideToOpenCodeConfig({ - configContent: config, - smallModelUseDefault: true, - smallModelOverride: 'anthropic/claude-haiku-4-5', - }), - ).toBe(config); - expect( - applySmallModelOverrideToOpenCodeConfig({ - configContent: config, - smallModelUseDefault: undefined, - smallModelOverride: 'anthropic/claude-haiku-4-5', - }), - ).toBe(config); - }); - - it('leaves config unchanged when the override is empty or whitespace', () => { - const config = '{"model":"anthropic/claude-sonnet-4-5"}'; - expect( - applySmallModelOverrideToOpenCodeConfig({ - configContent: config, - smallModelUseDefault: false, - smallModelOverride: ' ', - }), - ).toBe(config); - expect( - applySmallModelOverrideToOpenCodeConfig({ - configContent: config, - smallModelUseDefault: false, - smallModelOverride: undefined, - }), - ).toBe(config); - }); - - it('injects small_model into an empty config', () => { - const result = applySmallModelOverrideToOpenCodeConfig({ - configContent: undefined, - smallModelUseDefault: false, - smallModelOverride: 'anthropic/claude-haiku-4-5', - }); - expect(JSON.parse(result)).toEqual({ small_model: 'anthropic/claude-haiku-4-5' }); - }); - - it('injects small_model while preserving existing config keys and plugins', () => { - const result = applySmallModelOverrideToOpenCodeConfig({ - configContent: '{"model":"anthropic/claude-sonnet-4-5","plugin":["file:///tool.js"]}', - smallModelUseDefault: false, - smallModelOverride: 'google/gemini-2.5-flash', - }); - expect(JSON.parse(result)).toEqual({ - model: 'anthropic/claude-sonnet-4-5', - plugin: ['file:///tool.js'], - small_model: 'google/gemini-2.5-flash', - }); - }); - - it('replaces an existing small_model with the override', () => { - const result = applySmallModelOverrideToOpenCodeConfig({ - configContent: '{"small_model":"anthropic/claude-haiku-4-5"}', - smallModelUseDefault: false, - smallModelOverride: 'google/gemini-2.5-flash', - }); - expect(JSON.parse(result)).toEqual({ small_model: 'google/gemini-2.5-flash' }); - }); - - it('leaves malformed config untouched instead of rewriting it', () => { - const config = '{not-valid-json'; - expect( - applySmallModelOverrideToOpenCodeConfig({ - configContent: config, - smallModelUseDefault: false, - smallModelOverride: 'anthropic/claude-haiku-4-5', - }), - ).toBe(config); - const arrayConfig = '["not","an","object"]'; - expect( - applySmallModelOverrideToOpenCodeConfig({ - configContent: arrayConfig, - smallModelUseDefault: false, - smallModelOverride: 'anthropic/claude-haiku-4-5', - }), - ).toBe(arrayConfig); - }); -});