@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.
- package/dist/assets/{AgentAvatar-B2kZjgaf.js → AgentAvatar-B4fj9PIe.js} +1 -1
- package/dist/assets/{AssistantView-DvoyOGkx.js → AssistantView-C00aiEHK.js} +1 -1
- package/dist/assets/{ChatContainer-D6yfuigV.js → ChatContainer-BDFeDKu-.js} +39 -39
- package/dist/assets/{DiagramView-MGGo9iME.js → DiagramView-CUfSFI3e.js} +1 -1
- package/dist/assets/{FadeInOnReveal-aeWlaqVB.js → FadeInOnReveal-CKKU6fKI.js} +1 -1
- package/dist/assets/{Icon-AXqa-fYB.js → Icon-DiIuWOuC.js} +1 -1
- package/dist/assets/{MarkdownRendererImpl-jVOkZkyX.js → MarkdownRendererImpl-Cen6E9Lj.js} +2 -2
- package/dist/assets/{MobileDetailNavigation-Q2IzHqGA.js → MobileDetailNavigation-DILi0tN7.js} +1 -1
- package/dist/assets/{MobileShareBridge-apXM34Lb.js → MobileShareBridge-PZMRiieI.js} +1 -1
- package/dist/assets/MobileSurface-BLYcR0tO.js +2 -0
- package/dist/assets/{MultiRunWindow-B-l_O-bd.js → MultiRunWindow-D_2mWZyv.js} +1 -1
- package/dist/assets/{OnboardingScreen-CUsgwt4y.js → OnboardingScreen-CYeRZznl.js} +1 -1
- package/dist/assets/{OverlayScrollbar-D6NySAxi.js → OverlayScrollbar-BgMLtMIq.js} +1 -1
- package/dist/assets/SettingsView-CbDBfQp1.js +25 -0
- package/dist/assets/{SettingsWindow-DfVgTQ4I.js → SettingsWindow-Br7XLcSd.js} +1 -1
- package/dist/assets/{TerminalView-DNyjfkvT.js → TerminalView-CM5jRKlj.js} +1 -1
- package/dist/assets/{ToolOutputDialog-DLG1iThm.js → ToolOutputDialog-D3U6aTZ4.js} +1 -1
- package/dist/assets/appThemeRegistry-CDxBJNNB.js +2 -0
- package/dist/assets/assistant-guide-hero-BeUHQcxE.jpg +0 -0
- package/dist/assets/{button-CDvMVaiO.js → button-DaAoVF4X.js} +1 -1
- package/dist/assets/{clipboard-BW7nk_Cx.js → clipboard-BnElHSnO.js} +1 -1
- package/dist/assets/{collapsible-C2aamDvD.js → collapsible-BmuQu0dq.js} +1 -1
- package/dist/assets/{context-menu-BaxmodD8.js → context-menu-DBOMervd.js} +1 -1
- package/dist/assets/{desktopHosts-CBoBpJ1Z.js → desktopHosts-ZK_vkdzL.js} +1 -1
- package/dist/assets/{desktopRelayRestore-l2R9muux.js → desktopRelayRestore-CewM6-g5.js} +1 -1
- package/dist/assets/{diagram-CEon0BQs.js → diagram-DAE4CZy2.js} +1 -1
- package/dist/assets/{dialog-DjYfj1Sn.js → dialog-COrLBm9O.js} +1 -1
- package/dist/assets/{es-5Lhudh0n.js → es-DIVYdoxi.js} +1 -1
- package/dist/assets/{fileQueries-CE_DaZWQ.js → fileQueries-CmWYnRVz.js} +1 -1
- package/dist/assets/{fr-Q_UhFmKT.js → fr-CNTlVypq.js} +2 -2
- package/dist/assets/{gitApi-BYxg_qGJ.js → gitApi-BQT3QXD2.js} +1 -1
- package/dist/assets/gitApiHttp-BxGAFLTf.js +2 -0
- package/dist/assets/{input-k9DGGyZ6.js → input-XiL2-2gG.js} +1 -1
- package/dist/assets/insertionBoundaries-9a0RuIDc.js +1 -0
- package/dist/assets/{ja-CzMPz7bT.js → ja-C_eW5ZM8.js} +1 -1
- package/dist/assets/{ko-B-EslUsR.js → ko-CE3cOv7v.js} +1 -1
- package/dist/assets/{main-LDphyc-v.js → main-BGfX81Y9.js} +20 -20
- package/dist/assets/{main-CO6z5yRB.js → main-CEqSDnE2.js} +2 -2
- package/dist/assets/{markdown-worker-DkeWiER6.js → markdown-worker-BDyY0vt6.js} +1 -1
- package/dist/assets/{markdownTheme-DNT93i5L.js → markdownTheme-D-aLQZaM.js} +1 -1
- package/dist/assets/{miniChat-BZQwTZpx.js → miniChat-CKucSAhg.js} +2 -2
- package/dist/assets/mobile-C4bTWKry.js +2 -0
- package/dist/assets/{multirun-ZlwlkIy5.js → multirun-DeNtSkgb.js} +1 -1
- package/dist/assets/{persistence-DPNMIbF5.js → persistence-D1lIuyqO.js} +1 -1
- package/dist/assets/{pl-Dnea6Ppc.js → pl-BZKZOE99.js} +1 -1
- package/dist/assets/projectMeta-BunN9pH5.js +3 -0
- package/dist/assets/{pt-BR-Ca6QTem7.js → pt-BR-DHDgp8S3.js} +1 -1
- package/dist/assets/{radio-CJuhbQ5a.js → radio-Bziafaje.js} +1 -1
- package/dist/assets/renderElectronMiniChatApp-D8-FDgWE.js +2 -0
- package/dist/assets/renderMobileApp-CdU0aJ-e.js +23 -0
- package/dist/assets/{runtimeConfig-BtWi74Pm.js → runtimeConfig-BXCt7ZsQ.js} +1 -1
- package/dist/assets/runtimeEndpointReset-CK1GhIsm.js +145 -0
- package/dist/assets/{sortable-tabs-strip-D8JHUI0z.js → sortable-tabs-strip-pWlBorwQ.js} +1 -1
- package/dist/assets/src-8kQFmtjw.css +2 -0
- package/dist/assets/{store-wa1ersRf.js → store-CUWqzRt7.js} +2 -2
- package/dist/assets/{terminalApi-tB3qLcfa.js → terminalApi-DpiAJlil.js} +1 -1
- package/dist/assets/{tooltip-DMNR5rN0.js → tooltip-BLugzWq-.js} +1 -1
- package/dist/assets/{uk-xGc9Pzge.js → uk-CfQK6cz1.js} +1 -1
- package/dist/assets/{url-7MqoLs9X.js → url-DMs_PgIC.js} +1 -1
- package/dist/assets/useAppFontEffects-IEUQDvE8.js +7 -0
- package/dist/assets/useDesktopWindowControlsLayout-MlqJX89k.js +1 -0
- package/dist/assets/{useEffectiveDirectory-aUC3zzl_.js → useEffectiveDirectory-GbhkdO6m.js} +1 -1
- package/dist/assets/{useFontPreferences-CIurCeNv.js → useFontPreferences-92Qg-98H.js} +1 -1
- package/dist/assets/useMobileNavigationStore-DQHF_ETu.js +476 -0
- package/dist/assets/useSessionAutoCleanup-BuU_S_Sh.js +1 -0
- package/dist/assets/{useWorkerHighlightedLines-CiYQjBe6.js → useWorkerHighlightedLines-Ca6OgNHF.js} +1 -1
- package/dist/assets/{utils-Co19EJEL.js → utils-BxJEIWv5.js} +1 -1
- package/dist/assets/{zh-CN-sL4ktGB3.js → zh-CN-DvY2fKWp.js} +1 -1
- package/dist/assets/{zh-TW-Cfxps4gq.js → zh-TW-eWe0lm3-.js} +1 -1
- package/dist/index.html +8 -8
- package/dist/mini-chat.html +8 -8
- package/dist/mobile.html +8 -8
- package/package.json +1 -1
- package/server/index.d.ts +6 -0
- package/server/index.js +13 -0
- package/server/lib/config-sync/DOCUMENTATION.md +70 -0
- package/server/lib/config-sync/config-sync.test.js +197 -0
- package/server/lib/config-sync/constants.js +39 -0
- package/server/lib/config-sync/contract.js +130 -0
- package/server/lib/config-sync/credential-auth.js +39 -0
- package/server/lib/config-sync/engine.js +108 -0
- package/server/lib/config-sync/index.js +67 -0
- package/server/lib/config-sync/local-backup.js +171 -0
- package/server/lib/config-sync/plan.js +318 -0
- package/server/lib/config-sync/receiver.js +330 -0
- package/server/lib/config-sync/routes.js +141 -0
- package/server/lib/config-sync/routes.test.js +154 -0
- package/server/lib/config-sync/scripts.js +264 -0
- package/server/lib/config-sync/selections.js +112 -0
- package/server/lib/config-sync/tar.js +35 -0
- package/server/lib/config-sync/target-id.js +24 -0
- package/server/lib/event-stream/DOCUMENTATION.md +2 -2
- package/server/lib/event-stream/diff-summary.js +39 -11
- package/server/lib/event-stream/diff-summary.test.js +73 -6
- package/server/lib/event-stream/protocol.test.js +70 -7
- package/server/lib/notifications/DOCUMENTATION.md +2 -2
- package/server/lib/notifications/runtime.js +68 -27
- package/server/lib/notifications/runtime.test.js +176 -0
- package/server/lib/opencode/DOCUMENTATION.md +4 -1
- package/server/lib/opencode/bootstrap-runtime.js +2 -0
- package/server/lib/opencode/core-routes.js +1 -0
- package/server/lib/opencode/feature-routes-runtime.test.js +10 -1
- package/server/lib/opencode/openchamber-routes.js +7 -0
- package/server/lib/opencode/settings-runtime.js +82 -51
- package/server/lib/opencode/settings-runtime.test.js +42 -0
- package/server/lib/relay/DOCUMENTATION.md +1 -1
- package/server/lib/session-index/DOCUMENTATION.md +12 -6
- package/server/lib/session-index/routes.js +34 -0
- package/server/lib/session-index/routes.test.js +76 -0
- package/server/lib/session-index/service.js +53 -4
- package/server/lib/session-index/service.test.js +72 -2
- package/server/lib/session-title/DOCUMENTATION.md +19 -14
- package/server/lib/session-title/runtime.js +8 -8
- package/server/lib/session-title/runtime.test.js +231 -0
- package/server/lib/session-turn-pages/DOCUMENTATION.md +26 -6
- package/server/lib/session-turn-pages/changes.service.js +156 -0
- package/server/lib/session-turn-pages/changes.service.test.js +188 -0
- package/server/lib/session-turn-pages/reconcile.routes.test.js +52 -0
- package/server/lib/session-turn-pages/reconcile.service.js +3 -1
- package/server/lib/session-turn-pages/reconcile.service.test.js +44 -0
- package/server/lib/session-turn-pages/routes.js +273 -12
- package/server/lib/session-turn-pages/routes.test.js +387 -0
- package/server/lib/session-turn-pages/service.js +31 -7
- package/server/lib/session-turn-pages/service.test.js +40 -0
- package/server/lib/small-model/DOCUMENTATION.md +93 -38
- package/server/lib/small-model/catalog.js +10 -2
- package/server/lib/small-model/catalog.test.js +3 -1
- package/server/lib/small-model/index.js +52 -28
- package/server/lib/small-model/opencode-session.js +339 -0
- package/server/lib/small-model/opencode-session.test.js +177 -0
- package/server/lib/small-model/resolve.js +116 -5
- package/server/lib/small-model/resolve.test.js +76 -10
- package/server/lib/small-model/summary-settings.test.js +60 -3
- package/dist/assets/MobileSurface-htGkf7Ua.js +0 -2
- package/dist/assets/SettingsView-DxMpVLlt.js +0 -25
- package/dist/assets/appThemeRegistry-CgfOR_0L.js +0 -2
- package/dist/assets/gitApiHttp-D8j1qRH4.js +0 -2
- package/dist/assets/insertionBoundaries-DzXMTleb.js +0 -1
- package/dist/assets/mobile-BktO3_4R.js +0 -2
- package/dist/assets/projectMeta-v-LP2mzz.js +0 -3
- package/dist/assets/renderElectronMiniChatApp-Db8uO-6q.js +0 -2
- package/dist/assets/renderMobileApp-BCV81Ryl.js +0 -23
- package/dist/assets/runtimeEndpointReset-Dg3DMy9v.js +0 -145
- package/dist/assets/src-oHrYIqHF.css +0 -2
- package/dist/assets/useAppFontEffects-CM0VKsdo.js +0 -7
- package/dist/assets/useDesktopWindowControlsLayout-Cq-r4Hcb.js +0 -1
- package/dist/assets/useMobileNavigationStore-DnzT4jO2.js +0 -475
- package/dist/assets/useSessionAutoCleanup-DxZeceKO.js +0 -1
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
# Small Model
|
|
2
2
|
|
|
3
|
-
Server-side
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
13
|
-
|
|
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
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
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
|
|
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
|
-
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
69
|
-
remains in `opencode/provider-catalog.js` for
|
|
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
|
|
80
|
-
|
|
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
|
|
117
|
-
keeps those outside `auth.json` in this generation;
|
|
118
|
-
work for Anthropic.
|
|
119
|
-
|
|
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 /
|
|
52
|
-
* for resolve + call; does not project the
|
|
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
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
|
|
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 (
|
|
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
|
|
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
|
|
187
|
-
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
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
}
|