@openchambery/web 1.18.2-beta.2 → 1.18.2-beta.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/dist/assets/{AgentAvatar-B2kZjgaf.js → AgentAvatar-B4fj9PIe.js} +1 -1
  2. package/dist/assets/{AssistantView-DvoyOGkx.js → AssistantView-C00aiEHK.js} +1 -1
  3. package/dist/assets/{ChatContainer-D6yfuigV.js → ChatContainer-BDFeDKu-.js} +39 -39
  4. package/dist/assets/{DiagramView-MGGo9iME.js → DiagramView-CUfSFI3e.js} +1 -1
  5. package/dist/assets/{FadeInOnReveal-aeWlaqVB.js → FadeInOnReveal-CKKU6fKI.js} +1 -1
  6. package/dist/assets/{Icon-AXqa-fYB.js → Icon-DiIuWOuC.js} +1 -1
  7. package/dist/assets/{MarkdownRendererImpl-jVOkZkyX.js → MarkdownRendererImpl-Cen6E9Lj.js} +2 -2
  8. package/dist/assets/{MobileDetailNavigation-Q2IzHqGA.js → MobileDetailNavigation-DILi0tN7.js} +1 -1
  9. package/dist/assets/{MobileShareBridge-apXM34Lb.js → MobileShareBridge-PZMRiieI.js} +1 -1
  10. package/dist/assets/MobileSurface-BLYcR0tO.js +2 -0
  11. package/dist/assets/{MultiRunWindow-B-l_O-bd.js → MultiRunWindow-D_2mWZyv.js} +1 -1
  12. package/dist/assets/{OnboardingScreen-CUsgwt4y.js → OnboardingScreen-CYeRZznl.js} +1 -1
  13. package/dist/assets/{OverlayScrollbar-D6NySAxi.js → OverlayScrollbar-BgMLtMIq.js} +1 -1
  14. package/dist/assets/SettingsView-CbDBfQp1.js +25 -0
  15. package/dist/assets/{SettingsWindow-DfVgTQ4I.js → SettingsWindow-Br7XLcSd.js} +1 -1
  16. package/dist/assets/{TerminalView-DNyjfkvT.js → TerminalView-CM5jRKlj.js} +1 -1
  17. package/dist/assets/{ToolOutputDialog-DLG1iThm.js → ToolOutputDialog-D3U6aTZ4.js} +1 -1
  18. package/dist/assets/appThemeRegistry-CDxBJNNB.js +2 -0
  19. package/dist/assets/assistant-guide-hero-BeUHQcxE.jpg +0 -0
  20. package/dist/assets/{button-CDvMVaiO.js → button-DaAoVF4X.js} +1 -1
  21. package/dist/assets/{clipboard-BW7nk_Cx.js → clipboard-BnElHSnO.js} +1 -1
  22. package/dist/assets/{collapsible-C2aamDvD.js → collapsible-BmuQu0dq.js} +1 -1
  23. package/dist/assets/{context-menu-BaxmodD8.js → context-menu-DBOMervd.js} +1 -1
  24. package/dist/assets/{desktopHosts-CBoBpJ1Z.js → desktopHosts-ZK_vkdzL.js} +1 -1
  25. package/dist/assets/{desktopRelayRestore-l2R9muux.js → desktopRelayRestore-CewM6-g5.js} +1 -1
  26. package/dist/assets/{diagram-CEon0BQs.js → diagram-DAE4CZy2.js} +1 -1
  27. package/dist/assets/{dialog-DjYfj1Sn.js → dialog-COrLBm9O.js} +1 -1
  28. package/dist/assets/{es-5Lhudh0n.js → es-DIVYdoxi.js} +1 -1
  29. package/dist/assets/{fileQueries-CE_DaZWQ.js → fileQueries-CmWYnRVz.js} +1 -1
  30. package/dist/assets/{fr-Q_UhFmKT.js → fr-CNTlVypq.js} +2 -2
  31. package/dist/assets/{gitApi-BYxg_qGJ.js → gitApi-BQT3QXD2.js} +1 -1
  32. package/dist/assets/gitApiHttp-BxGAFLTf.js +2 -0
  33. package/dist/assets/{input-k9DGGyZ6.js → input-XiL2-2gG.js} +1 -1
  34. package/dist/assets/insertionBoundaries-9a0RuIDc.js +1 -0
  35. package/dist/assets/{ja-CzMPz7bT.js → ja-C_eW5ZM8.js} +1 -1
  36. package/dist/assets/{ko-B-EslUsR.js → ko-CE3cOv7v.js} +1 -1
  37. package/dist/assets/{main-LDphyc-v.js → main-BGfX81Y9.js} +20 -20
  38. package/dist/assets/{main-CO6z5yRB.js → main-CEqSDnE2.js} +2 -2
  39. package/dist/assets/{markdown-worker-DkeWiER6.js → markdown-worker-BDyY0vt6.js} +1 -1
  40. package/dist/assets/{markdownTheme-DNT93i5L.js → markdownTheme-D-aLQZaM.js} +1 -1
  41. package/dist/assets/{miniChat-BZQwTZpx.js → miniChat-CKucSAhg.js} +2 -2
  42. package/dist/assets/mobile-C4bTWKry.js +2 -0
  43. package/dist/assets/{multirun-ZlwlkIy5.js → multirun-DeNtSkgb.js} +1 -1
  44. package/dist/assets/{persistence-DPNMIbF5.js → persistence-D1lIuyqO.js} +1 -1
  45. package/dist/assets/{pl-Dnea6Ppc.js → pl-BZKZOE99.js} +1 -1
  46. package/dist/assets/projectMeta-BunN9pH5.js +3 -0
  47. package/dist/assets/{pt-BR-Ca6QTem7.js → pt-BR-DHDgp8S3.js} +1 -1
  48. package/dist/assets/{radio-CJuhbQ5a.js → radio-Bziafaje.js} +1 -1
  49. package/dist/assets/renderElectronMiniChatApp-D8-FDgWE.js +2 -0
  50. package/dist/assets/renderMobileApp-CdU0aJ-e.js +23 -0
  51. package/dist/assets/{runtimeConfig-BtWi74Pm.js → runtimeConfig-BXCt7ZsQ.js} +1 -1
  52. package/dist/assets/runtimeEndpointReset-CK1GhIsm.js +145 -0
  53. package/dist/assets/{sortable-tabs-strip-D8JHUI0z.js → sortable-tabs-strip-pWlBorwQ.js} +1 -1
  54. package/dist/assets/src-8kQFmtjw.css +2 -0
  55. package/dist/assets/{store-wa1ersRf.js → store-CUWqzRt7.js} +2 -2
  56. package/dist/assets/{terminalApi-tB3qLcfa.js → terminalApi-DpiAJlil.js} +1 -1
  57. package/dist/assets/{tooltip-DMNR5rN0.js → tooltip-BLugzWq-.js} +1 -1
  58. package/dist/assets/{uk-xGc9Pzge.js → uk-CfQK6cz1.js} +1 -1
  59. package/dist/assets/{url-7MqoLs9X.js → url-DMs_PgIC.js} +1 -1
  60. package/dist/assets/useAppFontEffects-IEUQDvE8.js +7 -0
  61. package/dist/assets/useDesktopWindowControlsLayout-MlqJX89k.js +1 -0
  62. package/dist/assets/{useEffectiveDirectory-aUC3zzl_.js → useEffectiveDirectory-GbhkdO6m.js} +1 -1
  63. package/dist/assets/{useFontPreferences-CIurCeNv.js → useFontPreferences-92Qg-98H.js} +1 -1
  64. package/dist/assets/useMobileNavigationStore-DQHF_ETu.js +476 -0
  65. package/dist/assets/useSessionAutoCleanup-BuU_S_Sh.js +1 -0
  66. package/dist/assets/{useWorkerHighlightedLines-CiYQjBe6.js → useWorkerHighlightedLines-Ca6OgNHF.js} +1 -1
  67. package/dist/assets/{utils-Co19EJEL.js → utils-BxJEIWv5.js} +1 -1
  68. package/dist/assets/{zh-CN-sL4ktGB3.js → zh-CN-DvY2fKWp.js} +1 -1
  69. package/dist/assets/{zh-TW-Cfxps4gq.js → zh-TW-eWe0lm3-.js} +1 -1
  70. package/dist/index.html +8 -8
  71. package/dist/mini-chat.html +8 -8
  72. package/dist/mobile.html +8 -8
  73. package/package.json +1 -1
  74. package/server/index.d.ts +6 -0
  75. package/server/index.js +13 -0
  76. package/server/lib/config-sync/DOCUMENTATION.md +70 -0
  77. package/server/lib/config-sync/config-sync.test.js +197 -0
  78. package/server/lib/config-sync/constants.js +39 -0
  79. package/server/lib/config-sync/contract.js +130 -0
  80. package/server/lib/config-sync/credential-auth.js +39 -0
  81. package/server/lib/config-sync/engine.js +108 -0
  82. package/server/lib/config-sync/index.js +67 -0
  83. package/server/lib/config-sync/local-backup.js +171 -0
  84. package/server/lib/config-sync/plan.js +318 -0
  85. package/server/lib/config-sync/receiver.js +330 -0
  86. package/server/lib/config-sync/routes.js +141 -0
  87. package/server/lib/config-sync/routes.test.js +154 -0
  88. package/server/lib/config-sync/scripts.js +264 -0
  89. package/server/lib/config-sync/selections.js +112 -0
  90. package/server/lib/config-sync/tar.js +35 -0
  91. package/server/lib/config-sync/target-id.js +24 -0
  92. package/server/lib/event-stream/DOCUMENTATION.md +2 -2
  93. package/server/lib/event-stream/diff-summary.js +39 -11
  94. package/server/lib/event-stream/diff-summary.test.js +73 -6
  95. package/server/lib/event-stream/protocol.test.js +70 -7
  96. package/server/lib/notifications/DOCUMENTATION.md +2 -2
  97. package/server/lib/notifications/runtime.js +68 -27
  98. package/server/lib/notifications/runtime.test.js +176 -0
  99. package/server/lib/opencode/DOCUMENTATION.md +4 -1
  100. package/server/lib/opencode/bootstrap-runtime.js +2 -0
  101. package/server/lib/opencode/core-routes.js +1 -0
  102. package/server/lib/opencode/feature-routes-runtime.test.js +10 -1
  103. package/server/lib/opencode/openchamber-routes.js +7 -0
  104. package/server/lib/opencode/settings-runtime.js +82 -51
  105. package/server/lib/opencode/settings-runtime.test.js +42 -0
  106. package/server/lib/relay/DOCUMENTATION.md +1 -1
  107. package/server/lib/session-index/DOCUMENTATION.md +12 -6
  108. package/server/lib/session-index/routes.js +34 -0
  109. package/server/lib/session-index/routes.test.js +76 -0
  110. package/server/lib/session-index/service.js +53 -4
  111. package/server/lib/session-index/service.test.js +72 -2
  112. package/server/lib/session-title/DOCUMENTATION.md +19 -14
  113. package/server/lib/session-title/runtime.js +8 -8
  114. package/server/lib/session-title/runtime.test.js +231 -0
  115. package/server/lib/session-turn-pages/DOCUMENTATION.md +26 -6
  116. package/server/lib/session-turn-pages/changes.service.js +156 -0
  117. package/server/lib/session-turn-pages/changes.service.test.js +188 -0
  118. package/server/lib/session-turn-pages/reconcile.routes.test.js +52 -0
  119. package/server/lib/session-turn-pages/reconcile.service.js +3 -1
  120. package/server/lib/session-turn-pages/reconcile.service.test.js +44 -0
  121. package/server/lib/session-turn-pages/routes.js +273 -12
  122. package/server/lib/session-turn-pages/routes.test.js +387 -0
  123. package/server/lib/session-turn-pages/service.js +31 -7
  124. package/server/lib/session-turn-pages/service.test.js +40 -0
  125. package/server/lib/small-model/DOCUMENTATION.md +93 -38
  126. package/server/lib/small-model/catalog.js +10 -2
  127. package/server/lib/small-model/catalog.test.js +3 -1
  128. package/server/lib/small-model/index.js +52 -28
  129. package/server/lib/small-model/opencode-session.js +339 -0
  130. package/server/lib/small-model/opencode-session.test.js +177 -0
  131. package/server/lib/small-model/resolve.js +116 -5
  132. package/server/lib/small-model/resolve.test.js +76 -10
  133. package/server/lib/small-model/summary-settings.test.js +60 -3
  134. package/dist/assets/MobileSurface-htGkf7Ua.js +0 -2
  135. package/dist/assets/SettingsView-DxMpVLlt.js +0 -25
  136. package/dist/assets/appThemeRegistry-CgfOR_0L.js +0 -2
  137. package/dist/assets/gitApiHttp-D8j1qRH4.js +0 -2
  138. package/dist/assets/insertionBoundaries-DzXMTleb.js +0 -1
  139. package/dist/assets/mobile-BktO3_4R.js +0 -2
  140. package/dist/assets/projectMeta-v-LP2mzz.js +0 -3
  141. package/dist/assets/renderElectronMiniChatApp-Db8uO-6q.js +0 -2
  142. package/dist/assets/renderMobileApp-BCV81Ryl.js +0 -23
  143. package/dist/assets/runtimeEndpointReset-Dg3DMy9v.js +0 -145
  144. package/dist/assets/src-oHrYIqHF.css +0 -2
  145. package/dist/assets/useAppFontEffects-CM0VKsdo.js +0 -7
  146. package/dist/assets/useDesktopWindowControlsLayout-Cq-r4Hcb.js +0 -1
  147. package/dist/assets/useMobileNavigationStore-DnzT4jO2.js +0 -475
  148. package/dist/assets/useSessionAutoCleanup-DxZeceKO.js +0 -1
@@ -1,23 +1,30 @@
1
1
  # Small Model
2
2
 
3
- Server-side direct LLM calls that reuse the user's existing OpenCode provider
4
- logins (`~/.local/share/opencode/auth.json`). OpenCode uses a "small model"
5
- internally (titles, summaries) but does not expose it through the SDK or
6
- plugins — this module replicates that mechanism as an OpenChamber runtime API.
3
+ Server-side LLM calls that reuse the user's existing OpenCode provider logins
4
+ (`~/.local/share/opencode/auth.json`). OpenCode uses a "small model" internally
5
+ (titles, summaries) but does not expose it through the SDK or plugins — this
6
+ module replicates that mechanism as an OpenChamber runtime API.
7
7
 
8
8
  ## Security boundary
9
9
 
10
10
  Credentials never leave the server process. The client sends only a prompt;
11
- auth resolution, OAuth refresh, and provider dispatch all happen server-side.
12
- Routes live under `/api/*` and are gated by the ui-auth middleware like every
13
- other runtime API.
11
+ auth resolution, OAuth refresh, and provider dispatch all happen server-side
12
+ (either in dedicated adapters or inside the OpenCode runtime for the session
13
+ path). Routes live under `/api/*` and are gated by the ui-auth middleware like
14
+ every other runtime API.
14
15
 
15
16
  ## Files
16
17
 
17
18
  - `index.js` — `createSmallModelService(dependencies)` factory. Production
18
19
  obtains a single lazy instance from `server/index.js` (`getSmallModelService`)
19
20
  and injects it into feature routes, session assist/title/goal, and scheduled
20
- tasks. Tests may call the factory with a mock `getModelCatalog`.
21
+ tasks. Tests may call the factory with a mock `getModelCatalog`. Returns
22
+ `stop()` to remove the lazy temp directory used by the session path (wired
23
+ from `server/index.js` `stop()`).
24
+ - `opencode-session.js` — temporary OpenCode session path for non-dedicated
25
+ providers. Lazily creates `mkdtemp('openchamber-smallmodel-')` with a hidden
26
+ deny-all agent, then create → archive → `prompt_async` → poll idle → read
27
+ assistant text → best-effort `session.delete`.
21
28
  - `resolve.js` — model selection, mirroring OpenCode's `getSmallModel` chain:
22
29
  0. OpenChamber's own settings override (Settings → Sessions → Small Model):
23
30
  when `smallModelUseDefault` is `false`, `smallModelOverride`
@@ -27,23 +34,30 @@ other runtime API.
27
34
  1. `small_model` from the merged OpenCode config layers (`provider/model`).
28
35
  2. Family-priority scan (`gemini-flash` → `gpt-nano` → `claude-haiku`)
29
36
  **within the session's provider first** (`preferredProviderID`, like
30
- OpenCode resolves within the current provider), then the session model,
31
- then other authenticated providers' catalog families, then the four
32
- minimal hardcoded candidates.
37
+ OpenCode resolves within the current provider), then keyword tiers on that
38
+ provider's catalog, then the session model, then other authenticated
39
+ providers' catalog families, then keyword+cost default across providers,
40
+ then the four minimal hardcoded candidates.
33
41
  3. GitHub Copilot hidden utility models (`gpt-*-nano/mini`) — these never
34
42
  appear in the catalog, so they participate as the `gpt-nano` family entry
35
43
  and as a final utility fallback.
36
44
  4. Session model (`preferredModelID`) when the session provider has auth but
37
- no small family match.
38
- Auth-type constraints for the four minimal providers: OpenAI OAuth →
45
+ no small family/keyword match.
46
+ Auth-type constraints for the four dedicated providers: OpenAI OAuth →
39
47
  hardcoded codex-small; OpenAI API key → catalog families; Anthropic/Google
40
48
  require API keys; Copilot supports auth aliases (`copilot` / `github-copilot`).
49
+
50
+ **Default ranking (no explicit config):** candidates are catalog models from
51
+ usable auth entries. Keyword tiers (tokenized id / known family):
52
+ `flash` → `nano` → `haiku` → `mini` → `lite` → `turbo` → `instant` →
53
+ `small` → `chat`. Within a tier: `cost.input` ascending (missing cost last),
54
+ then newer `release_date`.
41
55
  - Input clamp: the prompt is truncated to the resolved model's catalog
42
56
  `limit.context` (minus an output reserve, ~4 chars/token estimate;
43
57
  conservative default when the model is not in the catalog). Truncation is
44
58
  reported as `inputTruncated: true` in the response.
45
- - `call.js` — wire formats and per-provider auth, replicating OpenCode's
46
- plugin auth loaders:
59
+ - `call.js` — dedicated wire formats and per-provider auth for the four
60
+ adapters (replicating OpenCode plugin auth loaders):
47
61
  - **GitHub Copilot**: OpenAI-compatible `/chat/completions` on
48
62
  `https://api.githubcopilot.com` (or `copilot-api.<enterprise>`) with the
49
63
  stored device-OAuth token as the bearer — no token exchange, no expiry.
@@ -53,11 +67,12 @@ other runtime API.
53
67
  `auth.openai.com` (single-flight) and written back to `auth.json`.
54
68
  - **Anthropic** (`type: api`): `/v1/messages` with `x-api-key`.
55
69
  - **Google** (`type: api`): `generateContent` with `x-goog-api-key`.
56
- - Everything else: OpenAI-compatible `/chat/completions`. Base URL order:
57
- (1) `provider.<id>.options.baseURL` in the OpenCode merged config,
58
- (2) the target model's `api.url` from the directory catalog (OpenCode SDK),
59
- (3) known provider constants (`https://api.openai.com/v1` for openai).
60
- Never uses provider-level models.dev `api` fields.
70
+ - Custom Summary AI mode: OpenAI-compatible `/chat/completions` against the
71
+ user-supplied base URL + token.
72
+ Generic `api.url` chat-completions for arbitrary providers is **not** used
73
+ for dispatch anymore (plugin providers need runtime rewrite); those providers
74
+ take the OpenCode session path instead. The helper remains in `call.js` for
75
+ the dedicated adapters and custom mode.
61
76
  - `catalog.js` — directory-scoped provider catalog via official
62
77
  `@opencode-ai/sdk/v2` `client.config.providers()`. Base URL/auth come from
63
78
  the composition root (`buildOpenCodeUrl` / `getOpenCodeAuthHeaders`).
@@ -65,8 +80,9 @@ other runtime API.
65
80
  an **explicit** minimal fallback catalog (OpenAI OAuth / GitHub Copilot /
66
81
  Google API / Anthropic API candidates only) — never an authoritative empty
67
82
  map. Raw small-model fields kept: `family`, `release_date`, `limit`,
68
- `model.api.url`, provider `name`. Client-safe provider catalog projection
69
- remains in `opencode/provider-catalog.js` for `/api/config/catalog/providers`.
83
+ `cost.input` / `cost.output`, `model.api.url`, provider `name`. Client-safe
84
+ provider catalog projection remains in `opencode/provider-catalog.js` for
85
+ `/api/config/catalog/providers`.
70
86
  - `routes.js` — `GET /api/small-model` (resolution preview) and
71
87
  `POST /api/small-model/generate` (`{ prompt, system?, maxOutputTokens?,
72
88
  model?, directory? }` → `{ text, providerID, modelID, source }`).
@@ -74,10 +90,57 @@ other runtime API.
74
90
  consumed by Settings → Summary AI. Callable lists receive the same
75
91
  `directory` as describe/generate so they share the directory catalog.
76
92
 
93
+ ## Dispatch
94
+
95
+ | Mode / provider | Path |
96
+ |---|---|
97
+ | Summary AI `custom` | Direct OpenAI-compatible call (`call.js`) |
98
+ | `openai` / `anthropic` / `google` / `github-copilot` with usable dedicated auth | Direct dedicated adapter (`call.js`) |
99
+ | Any other usable auth + catalog model (plugin providers, …) | Temporary OpenCode session (`opencode-session.js`) |
100
+
101
+ Callable Settings lists include every `isUsableAuthEntry` provider that has at
102
+ least one catalog model id (no `api.url` gate). OpenAI OAuth still surfaces
103
+ only `gpt-5.4-mini`; Copilot only `gpt-5.4-nano`; Copilot auth aliases merge
104
+ to `github-copilot`.
105
+
106
+ ## OpenCode session lifecycle
107
+
108
+ 1. Lazy singleton temp directory under `os.tmpdir()` /
109
+ `openchamber-smallmodel-*` with `.opencode/agent/openchamber-smallmodel.md`
110
+ (`hidden: true`, `permissions: [{ action: "*", resource: "*", effect: deny }]`).
111
+ 2. `session.create` with title `[small-model] <purpose>`, agent name, and
112
+ `metadata.openchamber.smallModel = { purpose }` (sidebar invisibility also
113
+ depends on a parallel filter lane for that marker).
114
+ 3. Immediate `session.update({ time: { archived } })` (same pattern as
115
+ assistants / scheduled tasks) so ordinary lists never flash the session.
116
+ 4. `session.promptAsync` with `{ model: { providerID, modelID }, agent, system?,
117
+ parts: [{ type: 'text', text, synthetic: false }] }`.
118
+ 5. Poll `session.status` + `session.messages` until idle assistant completed
119
+ (budget `SESSION_SETTLE_TIMEOUT_MS` = 60s). Network / timeout / empty text
120
+ all throw with context — never an empty success.
121
+ 6. `finally`: best-effort `session.delete`. Delete failure is logged and does
122
+ not erase a successful text result.
123
+ 7. Server `stop()` calls `smallModelServiceInstance.stop()` to `rm` the temp
124
+ directory.
125
+
126
+ ### Skills / clean context boundary
127
+
128
+ OpenCode always loads skill *guidance* via `SkillInstructions.load` into the
129
+ system prompt (`packages/core/src/session/context.ts`). Availability is filtered
130
+ by `Permission.evaluate("skill", skill.id, agent.permissions)` — a deny-all
131
+ `action: "*"` rule therefore excludes every skill from the available list, so
132
+ the guidance renders as "No skills are currently available." Skill **body**
133
+ content is only injected when the skill tool runs; deny-all also blocks that
134
+ tool. There is no separate agent/directory flag to disable skill guidance
135
+ beyond permissions. Global `~/.agents/skills` may still be *discovered* by the
136
+ OpenCode instance, but they are not listed or loadable under this agent.
137
+ Project-level skills are absent because the temp directory has none.
138
+
77
139
  ## Registration
78
140
 
79
- `server/index.js` creates one lazy `getSmallModelService` and injects it into
80
- session assist/title/goal, scheduled tasks, and `createFeatureRoutesRuntime`.
141
+ `server/index.js` creates one lazy `getSmallModelService` and injects
142
+ `{ buildOpenCodeUrl, getOpenCodeAuthHeaders }` into the factory (and into
143
+ session assist/title/goal, scheduled tasks, and `createFeatureRoutesRuntime`).
81
144
  Feature routes no longer import `small-model/index.js` top-level exports
82
145
  directly.
83
146
 
@@ -96,14 +159,6 @@ calls use the same effective default shown by Settings: authenticated OpenAI
96
159
  when available, otherwise the first callable provider/model. This effective
97
160
  default remains authoritative when the active session uses another provider.
98
161
 
99
- Provider mode uses direct provider APIs from the server rather than sending a
100
- chat request through the active OpenCode session. The dispatcher has dedicated
101
- wire formats for GitHub Copilot, OpenAI OAuth, Anthropic API keys, and Google
102
- API keys. Catalog providers with an OpenAI-compatible chat-completions endpoint
103
- use the generic dispatcher. Provider plugins that require a region, resource
104
- name, browser session, or another provider-specific credential chain require a
105
- dedicated adapter. Custom mode is an explicit OpenAI-compatible API client.
106
-
107
162
  ## Known limitations
108
163
 
109
164
  - OpenCode's free models (`opencode/big-pickle`, `*-free`) work without a
@@ -113,12 +168,12 @@ dedicated adapter. Custom mode is an explicit OpenAI-compatible API client.
113
168
  a session on an unauthenticated `opencode` provider falls through to the
114
169
  global scan (or a clean 404 on a vanilla setup with no logins).
115
170
 
116
- - Anthropic OAuth (Claude Pro/Max) entries are not supported OpenCode itself
117
- keeps those outside `auth.json` in this generation; only `type: api` keys
118
- work for Anthropic.
119
- - Amazon Bedrock, GitLab, Azure and other credential-chain providers are out
120
- of scope; they need more than a key/token (regions, resource names).
171
+ - Anthropic OAuth (Claude Pro/Max) entries are not supported on the dedicated
172
+ adapter — OpenCode itself keeps those outside `auth.json` in this generation;
173
+ only `type: api` keys work for Anthropic direct calls. Plugin / other auth
174
+ shapes that OpenCode can already run may still succeed via the session path.
121
175
  - Responses from the codex backend are collected from the SSE stream; the
122
176
  endpoint itself is non-streaming by design (small utility calls).
123
177
  - The server never requests models.dev. When OpenCode is down, only the four
124
- minimal hardcoded candidates remain available.
178
+ minimal hardcoded candidates remain available for resolution; session-path
179
+ generation still requires a live OpenCode runtime.
@@ -48,8 +48,9 @@ const isRecord = (value) => value !== null && typeof value === 'object' && !Arra
48
48
 
49
49
  /**
50
50
  * Convert the official SDK provider list into the internal small-model catalog
51
- * shape. Keeps family / release_date / limit / model.api.url / provider name
52
- * for resolve + call; does not project the client-safe catalog allowlist.
51
+ * shape. Keeps family / release_date / limit / cost.input|output /
52
+ * model.api.url / provider name for resolve + call; does not project the
53
+ * client-safe catalog allowlist.
53
54
  */
54
55
  export function toSmallModelCatalog(source) {
55
56
  if (!isRecord(source) || !Array.isArray(source.providers)) {
@@ -77,6 +78,13 @@ export function toSmallModelCatalog(source) {
77
78
  if (Number.isFinite(model.limit.output)) limit.output = model.limit.output;
78
79
  if (Object.keys(limit).length > 0) entry.limit = limit;
79
80
  }
81
+ // cost.input/output feed default small-model ranking (cheapest first).
82
+ if (isRecord(model.cost)) {
83
+ const cost = Object.create(null);
84
+ if (Number.isFinite(model.cost.input)) cost.input = model.cost.input;
85
+ if (Number.isFinite(model.cost.output)) cost.output = model.cost.output;
86
+ if (Object.keys(cost).length > 0) entry.cost = cost;
87
+ }
80
88
  if (isRecord(model.api) && typeof model.api.url === 'string' && model.api.url.trim()) {
81
89
  entry.api = { url: model.api.url.trim() };
82
90
  }
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest';
2
2
  import { createModelCatalogLoader, toSmallModelCatalog, MINIMAL_FALLBACK_CATALOG } from './catalog.js';
3
3
 
4
4
  describe('toSmallModelCatalog', () => {
5
- it('keeps family, release_date, limit, model.api.url, and provider name', () => {
5
+ it('keeps family, release_date, limit, cost, model.api.url, and provider name', () => {
6
6
  const catalog = toSmallModelCatalog({
7
7
  providers: [{
8
8
  id: 'google',
@@ -14,6 +14,7 @@ describe('toSmallModelCatalog', () => {
14
14
  family: 'gemini-flash',
15
15
  release_date: '2025-06-01',
16
16
  limit: { context: 1_000_000, output: 8192 },
17
+ cost: { input: 0.15, output: 0.6, cache_read: 0.01 },
17
18
  api: { id: 'gemini-2.5-flash', url: 'https://generativelanguage.googleapis.com', npm: '@ai-sdk/google' },
18
19
  },
19
20
  },
@@ -30,6 +31,7 @@ describe('toSmallModelCatalog', () => {
30
31
  family: 'gemini-flash',
31
32
  release_date: '2025-06-01',
32
33
  limit: { context: 1_000_000, output: 8192 },
34
+ cost: { input: 0.15, output: 0.6 },
33
35
  api: { url: 'https://generativelanguage.googleapis.com' },
34
36
  },
35
37
  },
@@ -6,6 +6,7 @@ import { readConfigLayers } from '../opencode/shared.js';
6
6
  import { createModelCatalogLoader, getCatalogProvider } from './catalog.js';
7
7
  import { resolveSmallModel, parseModelRef, isUsableAuthEntry, getAuthEntryForProvider } from './resolve.js';
8
8
  import { callSmallModel } from './call.js';
9
+ import { generateViaOpenCodeSession, stop as stopOpenCodeSessionTemp } from './opencode-session.js';
9
10
 
10
11
  const OPENCHAMBER_SETTINGS_FILE = path.join(
11
12
  process.env.OPENCHAMBER_DATA_DIR
@@ -74,21 +75,13 @@ const clampPromptToModelLimit = ({ prompt, catalog, providerID, modelID }) => {
74
75
  return { prompt: `${prompt.slice(0, maxChars)}…`, truncated: true };
75
76
  };
76
77
 
77
- // Dedicated adapters (or OpenAI-compatible models that publish api.url) are
78
- // the only providers the small-model dispatcher can call without models.dev.
79
- // Auth-type constraints must match resolve/call: OpenAI api|oauth; Anthropic/
80
- // Google api only; Copilot via github-copilot/copilot alias token.
81
- const DEDICATED_CALLABLE_PROVIDERS = new Set(['openai', 'anthropic', 'google', 'github-copilot']);
82
-
83
- const providerHasCallableEndpoint = (provider) => {
84
- if (!provider || typeof provider !== 'object') return false;
85
- return Object.values(provider.models || {}).some(
86
- (model) => typeof model?.api?.url === 'string' && model.api.url.trim().length > 0,
87
- );
88
- };
78
+ // Dedicated adapters keep the direct wire-format path. Everything else
79
+ // (plugin providers, credential-chain, missing api.url, …) goes through a
80
+ // temporary OpenCode session so auth/endpoint rewrite stay in the runtime.
81
+ const DEDICATED_DIRECT_PROVIDERS = new Set(['openai', 'anthropic', 'google', 'github-copilot']);
89
82
 
90
83
  // Mirrors pickWithinProvider / callSmallModel auth gates so Settings pickers
91
- // never list providers that would only fail at dispatch time.
84
+ // never list dedicated providers that would only fail at dispatch time.
92
85
  const isCallableDedicatedAuth = (auth, providerID) => {
93
86
  if (providerID === 'openai') {
94
87
  const entry = auth?.openai;
@@ -108,17 +101,29 @@ const isCallableDedicatedAuth = (auth, providerID) => {
108
101
  return false;
109
102
  };
110
103
 
104
+ const providerHasCatalogModels = (provider) => {
105
+ if (!provider || typeof provider !== 'object') return false;
106
+ return Object.values(provider.models || {}).some(
107
+ (model) => typeof model?.id === 'string' && model.id.trim().length > 0,
108
+ );
109
+ };
110
+
111
+ const shouldUseDirectAdapter = (providerID, auth) => {
112
+ if (!DEDICATED_DIRECT_PROVIDERS.has(providerID)) return false;
113
+ return isCallableDedicatedAuth(auth, providerID);
114
+ };
115
+
111
116
  const listCallableProviderIDsForState = (auth, catalog) => {
112
117
  const ids = new Set(
113
118
  Object.keys(auth || {}).filter((providerID) => {
114
119
  // Surface Copilot only as github-copilot (alias handled below).
115
120
  if (providerID === 'copilot') return false;
116
- if (DEDICATED_CALLABLE_PROVIDERS.has(providerID)) {
121
+ if (DEDICATED_DIRECT_PROVIDERS.has(providerID)) {
117
122
  return isCallableDedicatedAuth(auth, providerID);
118
123
  }
119
124
  if (!isUsableAuthEntry(auth[providerID])) return false;
120
125
  const provider = getCatalogProvider(catalog, providerID);
121
- return providerHasCallableEndpoint(provider);
126
+ return providerHasCatalogModels(provider);
122
127
  }),
123
128
  );
124
129
  if (isUsableAuthEntry(getAuthEntryForProvider(auth, 'github-copilot'))) {
@@ -181,10 +186,12 @@ const readConfiguredSmallModel = (workingDirectory) => {
181
186
  * }} dependencies
182
187
  */
183
188
  export function createSmallModelService(dependencies) {
189
+ const buildOpenCodeUrl = dependencies.buildOpenCodeUrl;
190
+ const getOpenCodeAuthHeaders = dependencies.getOpenCodeAuthHeaders;
184
191
  const getModelCatalog = dependencies.getModelCatalog
185
192
  || createModelCatalogLoader({
186
- buildOpenCodeUrl: dependencies.buildOpenCodeUrl,
187
- getOpenCodeAuthHeaders: dependencies.getOpenCodeAuthHeaders,
193
+ buildOpenCodeUrl,
194
+ getOpenCodeAuthHeaders,
188
195
  }).getModelCatalog;
189
196
 
190
197
  /**
@@ -273,17 +280,33 @@ export function createSmallModelService(dependencies) {
273
280
  modelID: resolved.modelID,
274
281
  });
275
282
 
276
- const text = await callSmallModel({
277
- auth,
278
- catalog,
279
- workingDirectory: directory,
280
- providerID: resolved.providerID,
281
- modelID: resolved.modelID,
282
- prompt: clamped.prompt,
283
- system: summaryPrompt || (typeof system === 'string' && system.trim() ? system.trim() : undefined),
284
- maxOutputTokens,
285
- custom: summaryCustom,
286
- });
283
+ const effectiveSystem = summaryPrompt || (typeof system === 'string' && system.trim() ? system.trim() : undefined);
284
+
285
+ let text;
286
+ if (summaryCustom || shouldUseDirectAdapter(resolved.providerID, auth)) {
287
+ text = await callSmallModel({
288
+ auth,
289
+ catalog,
290
+ workingDirectory: directory,
291
+ providerID: resolved.providerID,
292
+ modelID: resolved.modelID,
293
+ prompt: clamped.prompt,
294
+ system: effectiveSystem,
295
+ maxOutputTokens,
296
+ custom: summaryCustom,
297
+ });
298
+ } else {
299
+ text = await generateViaOpenCodeSession({
300
+ buildOpenCodeUrl,
301
+ getOpenCodeAuthHeaders,
302
+ providerID: resolved.providerID,
303
+ modelID: resolved.modelID,
304
+ prompt: clamped.prompt,
305
+ system: effectiveSystem,
306
+ purpose: typeof purpose === 'string' && purpose.trim() ? purpose.trim() : 'generate',
307
+ directory,
308
+ });
309
+ }
287
310
 
288
311
  return {
289
312
  text: text.trim(),
@@ -341,5 +364,6 @@ export function createSmallModelService(dependencies) {
341
364
  listCallableProviders,
342
365
  listCallableModels,
343
366
  describeSmallModel,
367
+ stop: stopOpenCodeSessionTemp,
344
368
  };
345
369
  }