@openchambery/web 1.19.19 → 1.19.20
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-BRfqdWCk.js → AgentAvatar-NT957MuI.js} +1 -1
- package/dist/assets/{AssistantView-DaQygS2g.js → AssistantView-BjvfkSRS.js} +3 -3
- package/dist/assets/{DiagramView-DU13N_4L.js → DiagramView-C_JfbxNl.js} +1 -1
- package/dist/assets/{FadeInOnReveal-C6tR_fyF.js → FadeInOnReveal-CilRl4gc.js} +1 -1
- package/dist/assets/{Icon-B5uh1TBO.js → Icon-i5GiQRLq.js} +1 -1
- package/dist/assets/{MarkdownRenderer-CQHRhqB1.js → MarkdownRenderer-Cg82L3fN.js} +2 -2
- package/dist/assets/{MarkdownRendererImpl-CfPlhzKB.js → MarkdownRendererImpl-B1iUwzOz.js} +1 -1
- package/dist/assets/{MarkstreamRendererImpl-BZu0kqan.js → MarkstreamRendererImpl-BPtMj3-X.js} +1 -1
- package/dist/assets/{MobileDetailNavigation-DeLsBXQl.js → MobileDetailNavigation-s_vvUD_F.js} +1 -1
- package/dist/assets/{MobileShareBridge-6jsuuuL-.js → MobileShareBridge-CKClHsWN.js} +2 -2
- package/dist/assets/{MobileSurface-DCP4BC35.js → MobileSurface-DHpXHbJp.js} +1 -1
- package/dist/assets/{MultiRunWindow-EN15uCln.js → MultiRunWindow-DiA24bnN.js} +1 -1
- package/dist/assets/{OnboardingScreen-_lWjW-Z6.js → OnboardingScreen-BGRLk4Wi.js} +1 -1
- package/dist/assets/{OverlayScrollbar-DfKTImP7.js → OverlayScrollbar-NuQI58XD.js} +1 -1
- package/dist/assets/{SettingsView-Cm-fwFNM.js → SettingsView-l0J7pfYo.js} +2 -2
- package/dist/assets/{SettingsWindow-CSBXDRrg.js → SettingsWindow-XlzRSS7E.js} +1 -1
- package/dist/assets/{TerminalView-CWRwefBO.js → TerminalView-DdIaaLba.js} +1 -1
- package/dist/assets/{ToolOutputDialog-CJFSAiw6.js → ToolOutputDialog-BbwuLdXU.js} +1 -1
- package/dist/assets/{appThemeRegistry-BCBPxOn0.js → appThemeRegistry-ChzfD7mc.js} +1 -1
- package/dist/assets/{button-B1A-UMjO.js → button-DtzFDvij.js} +1 -1
- package/dist/assets/{collapsible-CMDtiFan.js → collapsible-D-nzhcyF.js} +1 -1
- package/dist/assets/{context-menu-DhqbwEHq.js → context-menu-BzVSzHLG.js} +1 -1
- package/dist/assets/{desktopHosts-B052a4UK.js → desktopHosts-B1ixEylr.js} +1 -1
- package/dist/assets/{desktopRelayRestore-OjKNXr10.js → desktopRelayRestore-4JmFUw8r.js} +1 -1
- package/dist/assets/{diagram-Cp2-31s3.js → diagram-2YWalSb5.js} +1 -1
- package/dist/assets/{dialog-Ck2GVXYI.js → dialog-DGV7DsZC.js} +1 -1
- package/dist/assets/{es-0Yrlq6Oy.js → es-rqMiRP7-.js} +1 -1
- package/dist/assets/{fileMentionTouchSelection-LxAU-jJG.js → fileMentionTouchSelection-DyjmxUEN.js} +1 -1
- package/dist/assets/{fileQueries-B_s_B-fH.js → fileQueries-DQc8EJUf.js} +1 -1
- package/dist/assets/{fr-AXS_d4Ac.js → fr-DsBZeN8Y.js} +1 -1
- package/dist/assets/{gitApi-BSN7ZfSp.js → gitApi-CHpibtno.js} +1 -1
- package/dist/assets/{input-DVaUSQqw.js → input-CbB2HdLg.js} +1 -1
- package/dist/assets/{insertionBoundaries-DiTLBzlO.js → insertionBoundaries-Yd1Lorjw.js} +1 -1
- package/dist/assets/{ja-iISfz4W8.js → ja-Bgr8-dOg.js} +1 -1
- package/dist/assets/{ko-DeN9jk1E.js → ko-B_Gxh-0l.js} +1 -1
- package/dist/assets/{main-BbJQ9Vtx.js → main-BoRXxmB3.js} +2 -2
- package/dist/assets/main-DCDcdV5o.js +169 -0
- package/dist/assets/{markdownHeightCache-BWYOoF-W.js → markdownHeightCache-C9ym9Tsb.js} +1 -1
- package/dist/assets/{miniChat-DaZ0MUes.js → miniChat-12fxk5KR.js} +2 -2
- package/dist/assets/{mobile-ctmwtKIg.js → mobile-CkZem9Ad.js} +2 -2
- package/dist/assets/{multirun-9Z45iiC7.js → multirun-8fkF0Ji_.js} +1 -1
- package/dist/assets/{persistence-DwRBC4zl.js → persistence-Ce3AnpEr.js} +2 -2
- package/dist/assets/{pl-C32gRuHl.js → pl-DW2XJGC4.js} +1 -1
- package/dist/assets/{platform-DQ5_LNKO.js → platform-0w_Q9_ji.js} +1 -1
- package/dist/assets/{projectMeta-CIGtyoau.js → projectMeta--y6tyjrv.js} +1 -1
- package/dist/assets/{pt-BR-CodLmzOe.js → pt-BR-CIsanf4T.js} +1 -1
- package/dist/assets/{renderElectronMiniChatApp-Bpmux7pj.js → renderElectronMiniChatApp-COFc0ZDJ.js} +2 -2
- package/dist/assets/{renderMobileApp-DYlwZgHu.js → renderMobileApp-BKzx7JL3.js} +4 -4
- package/dist/assets/{runtimeConfig-B0raYq9L.js → runtimeConfig-DYWtBrkW.js} +1 -1
- package/dist/assets/{runtimeEndpointReset-D09YtB1G.js → runtimeEndpointReset-BSWg99jc.js} +1 -1
- package/dist/assets/{sessionChangeSummary-B2KglTnI.js → sessionChangeSummary-BwupJF-Q.js} +1 -1
- package/dist/assets/{sessionLookup-CBlhAA0q.js → sessionLookup-DP2X1qRR.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-CSQUAwi0.js → sortable-tabs-strip-CfgEJfB0.js} +1 -1
- package/dist/assets/{store-BBmfPqaV.js → store-B6dImdlY.js} +2 -2
- package/dist/assets/{terminalApi-CfgOfnZk.js → terminalApi-xjqssKke.js} +1 -1
- package/dist/assets/{textarea-C0GHF5Of.js → textarea-BnD799LW.js} +1 -1
- package/dist/assets/{tooltip-C5FMIBsd.js → tooltip-Djn7MSOX.js} +1 -1
- package/dist/assets/{uk-L_pdbOtS.js → uk-BPmPEUYJ.js} +1 -1
- package/dist/assets/{url-CMQwHq4k.js → url-_z7evzdK.js} +1 -1
- package/dist/assets/{useAppFontEffects-CDbz3983.js → useAppFontEffects-DWLxpWlS.js} +5 -5
- package/dist/assets/{useAssistantUIStore-CLPCNoj_.js → useAssistantUIStore-CMutVh9B.js} +1 -1
- package/dist/assets/{useDesktopWindowControlsLayout-DNqYfyg5.js → useDesktopWindowControlsLayout-BGPR_rq6.js} +1 -1
- package/dist/assets/{useEffectiveDirectory-B_8RoAcb.js → useEffectiveDirectory-CbV9_r1s.js} +1 -1
- package/dist/assets/{useFontPreferences-DCaY8NbU.js → useFontPreferences-B1JYyzpK.js} +1 -1
- package/dist/assets/{useMobileNavigationStore-Btsf88wS.js → useMobileNavigationStore-DrB_awgk.js} +4 -4
- package/dist/assets/{useSessionAutoCleanup-B7uBJZxU.js → useSessionAutoCleanup-Dnors7hB.js} +1 -1
- package/dist/assets/useUIStore-Bxv3f23A.js +1 -0
- package/dist/assets/{useWorkerHighlightedLines-CPzF9843.js → useWorkerHighlightedLines-DQqqrOBU.js} +1 -1
- package/dist/assets/{zh-CN-CwyG5bxD.js → zh-CN-R8-mDcIp.js} +2 -2
- package/dist/assets/{zh-TW-Ba7atZ7i.js → zh-TW-BsOQbrwT.js} +2 -2
- 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/lib/opencode/settings-helpers.js +6 -0
- package/server/lib/opencode/settings-helpers.test.js +3 -0
- package/server/lib/small-model/DOCUMENTATION.md +33 -14
- package/server/lib/small-model/custom-api.js +141 -0
- package/server/lib/small-model/custom-api.test.js +101 -0
- package/server/lib/small-model/index.js +91 -27
- package/server/lib/small-model/routes.js +22 -0
- package/server/lib/small-model/summary-settings.test.js +85 -1
- package/dist/assets/main-DFRHWaDr.js +0 -169
- package/dist/assets/useUIStore-CxOoCv7B.js +0 -1
package/dist/index.html
CHANGED
|
@@ -552,27 +552,27 @@
|
|
|
552
552
|
opacity: 0.72;
|
|
553
553
|
}
|
|
554
554
|
</style>
|
|
555
|
-
<script type="module" crossorigin src="/assets/main-
|
|
555
|
+
<script type="module" crossorigin src="/assets/main-BoRXxmB3.js"></script>
|
|
556
556
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
557
557
|
<link rel="modulepreload" crossorigin href="/assets/runtime-auth-D66yq6ie.js">
|
|
558
558
|
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-LvWDU-SL.js">
|
|
559
559
|
<link rel="modulepreload" crossorigin href="/assets/runtime-fetch-zIalUnbb.js">
|
|
560
560
|
<link rel="modulepreload" crossorigin href="/assets/vendor-base-ui-CnHfUmDN.js">
|
|
561
561
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-B4dlGEM2.js">
|
|
562
|
-
<link rel="modulepreload" crossorigin href="/assets/useUIStore-
|
|
562
|
+
<link rel="modulepreload" crossorigin href="/assets/useUIStore-Bxv3f23A.js">
|
|
563
563
|
<link rel="modulepreload" crossorigin href="/assets/vendor-pathe-vBmvQE8f.js">
|
|
564
564
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-DLPElpoQ.js">
|
|
565
565
|
<link rel="modulepreload" crossorigin href="/assets/vendor-tanstack-query-core-BWz0ose6.js">
|
|
566
|
-
<link rel="modulepreload" crossorigin href="/assets/persistence-
|
|
567
|
-
<link rel="modulepreload" crossorigin href="/assets/desktopHosts-
|
|
568
|
-
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-
|
|
566
|
+
<link rel="modulepreload" crossorigin href="/assets/persistence-Ce3AnpEr.js">
|
|
567
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopHosts-B1ixEylr.js">
|
|
568
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-4JmFUw8r.js">
|
|
569
569
|
<link rel="modulepreload" crossorigin href="/assets/vendor-opencode-sdk-C07HQypa.js">
|
|
570
570
|
<link rel="modulepreload" crossorigin href="/assets/vendor-aparajita-capacitor-secure-storage-CjQdwfjS.js">
|
|
571
|
-
<link rel="modulepreload" crossorigin href="/assets/platform-
|
|
572
|
-
<link rel="modulepreload" crossorigin href="/assets/terminalApi-
|
|
571
|
+
<link rel="modulepreload" crossorigin href="/assets/platform-0w_Q9_ji.js">
|
|
572
|
+
<link rel="modulepreload" crossorigin href="/assets/terminalApi-xjqssKke.js">
|
|
573
573
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
574
574
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
|
|
575
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
575
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-DYWtBrkW.js">
|
|
576
576
|
<link rel="stylesheet" crossorigin href="/assets/src-DZ60kHBP.css">
|
|
577
577
|
</head>
|
|
578
578
|
<body class="h-full bg-background text-foreground">
|
package/dist/mini-chat.html
CHANGED
|
@@ -74,27 +74,27 @@
|
|
|
74
74
|
}
|
|
75
75
|
</style>
|
|
76
76
|
|
|
77
|
-
<script type="module" crossorigin src="/assets/miniChat-
|
|
77
|
+
<script type="module" crossorigin src="/assets/miniChat-12fxk5KR.js"></script>
|
|
78
78
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
79
79
|
<link rel="modulepreload" crossorigin href="/assets/runtime-auth-D66yq6ie.js">
|
|
80
80
|
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-LvWDU-SL.js">
|
|
81
81
|
<link rel="modulepreload" crossorigin href="/assets/runtime-fetch-zIalUnbb.js">
|
|
82
82
|
<link rel="modulepreload" crossorigin href="/assets/vendor-base-ui-CnHfUmDN.js">
|
|
83
83
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-B4dlGEM2.js">
|
|
84
|
-
<link rel="modulepreload" crossorigin href="/assets/useUIStore-
|
|
84
|
+
<link rel="modulepreload" crossorigin href="/assets/useUIStore-Bxv3f23A.js">
|
|
85
85
|
<link rel="modulepreload" crossorigin href="/assets/vendor-pathe-vBmvQE8f.js">
|
|
86
86
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-DLPElpoQ.js">
|
|
87
87
|
<link rel="modulepreload" crossorigin href="/assets/vendor-tanstack-query-core-BWz0ose6.js">
|
|
88
|
-
<link rel="modulepreload" crossorigin href="/assets/persistence-
|
|
89
|
-
<link rel="modulepreload" crossorigin href="/assets/desktopHosts-
|
|
90
|
-
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-
|
|
88
|
+
<link rel="modulepreload" crossorigin href="/assets/persistence-Ce3AnpEr.js">
|
|
89
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopHosts-B1ixEylr.js">
|
|
90
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-4JmFUw8r.js">
|
|
91
91
|
<link rel="modulepreload" crossorigin href="/assets/vendor-opencode-sdk-C07HQypa.js">
|
|
92
92
|
<link rel="modulepreload" crossorigin href="/assets/vendor-aparajita-capacitor-secure-storage-CjQdwfjS.js">
|
|
93
|
-
<link rel="modulepreload" crossorigin href="/assets/platform-
|
|
94
|
-
<link rel="modulepreload" crossorigin href="/assets/terminalApi-
|
|
93
|
+
<link rel="modulepreload" crossorigin href="/assets/platform-0w_Q9_ji.js">
|
|
94
|
+
<link rel="modulepreload" crossorigin href="/assets/terminalApi-xjqssKke.js">
|
|
95
95
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
96
96
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
|
|
97
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
97
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-DYWtBrkW.js">
|
|
98
98
|
<link rel="stylesheet" crossorigin href="/assets/src-DZ60kHBP.css">
|
|
99
99
|
</head>
|
|
100
100
|
<body class="h-full bg-background text-foreground">
|
package/dist/mobile.html
CHANGED
|
@@ -63,27 +63,27 @@
|
|
|
63
63
|
})();
|
|
64
64
|
</script>
|
|
65
65
|
|
|
66
|
-
<script type="module" crossorigin src="/assets/mobile-
|
|
66
|
+
<script type="module" crossorigin src="/assets/mobile-CkZem9Ad.js"></script>
|
|
67
67
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
68
68
|
<link rel="modulepreload" crossorigin href="/assets/runtime-auth-D66yq6ie.js">
|
|
69
69
|
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-LvWDU-SL.js">
|
|
70
70
|
<link rel="modulepreload" crossorigin href="/assets/runtime-fetch-zIalUnbb.js">
|
|
71
71
|
<link rel="modulepreload" crossorigin href="/assets/vendor-base-ui-CnHfUmDN.js">
|
|
72
72
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-B4dlGEM2.js">
|
|
73
|
-
<link rel="modulepreload" crossorigin href="/assets/useUIStore-
|
|
73
|
+
<link rel="modulepreload" crossorigin href="/assets/useUIStore-Bxv3f23A.js">
|
|
74
74
|
<link rel="modulepreload" crossorigin href="/assets/vendor-pathe-vBmvQE8f.js">
|
|
75
75
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-DLPElpoQ.js">
|
|
76
76
|
<link rel="modulepreload" crossorigin href="/assets/vendor-tanstack-query-core-BWz0ose6.js">
|
|
77
|
-
<link rel="modulepreload" crossorigin href="/assets/persistence-
|
|
78
|
-
<link rel="modulepreload" crossorigin href="/assets/desktopHosts-
|
|
79
|
-
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-
|
|
77
|
+
<link rel="modulepreload" crossorigin href="/assets/persistence-Ce3AnpEr.js">
|
|
78
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopHosts-B1ixEylr.js">
|
|
79
|
+
<link rel="modulepreload" crossorigin href="/assets/desktopRelayRestore-4JmFUw8r.js">
|
|
80
80
|
<link rel="modulepreload" crossorigin href="/assets/vendor-opencode-sdk-C07HQypa.js">
|
|
81
81
|
<link rel="modulepreload" crossorigin href="/assets/vendor-aparajita-capacitor-secure-storage-CjQdwfjS.js">
|
|
82
|
-
<link rel="modulepreload" crossorigin href="/assets/platform-
|
|
83
|
-
<link rel="modulepreload" crossorigin href="/assets/terminalApi-
|
|
82
|
+
<link rel="modulepreload" crossorigin href="/assets/platform-0w_Q9_ji.js">
|
|
83
|
+
<link rel="modulepreload" crossorigin href="/assets/terminalApi-xjqssKke.js">
|
|
84
84
|
<link rel="modulepreload" crossorigin href="/assets/types-BuiqMmh_.js">
|
|
85
85
|
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-DCowZ2ab.js">
|
|
86
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
86
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-DYWtBrkW.js">
|
|
87
87
|
<link rel="stylesheet" crossorigin href="/assets/src-DZ60kHBP.css">
|
|
88
88
|
</head>
|
|
89
89
|
<body class="h-full bg-background text-foreground">
|
package/package.json
CHANGED
|
@@ -452,6 +452,12 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
452
452
|
? trimmed.slice(0, SUMMARY_MODEL_ID_MAX_LENGTH)
|
|
453
453
|
: undefined;
|
|
454
454
|
}
|
|
455
|
+
if (typeof candidate.summaryCustomModelID === 'string') {
|
|
456
|
+
const trimmed = candidate.summaryCustomModelID.trim();
|
|
457
|
+
result.summaryCustomModelID = trimmed.length > 0
|
|
458
|
+
? trimmed.slice(0, SUMMARY_MODEL_ID_MAX_LENGTH)
|
|
459
|
+
: undefined;
|
|
460
|
+
}
|
|
455
461
|
if (typeof candidate.summaryCustomBaseURL === 'string') {
|
|
456
462
|
const trimmed = candidate.summaryCustomBaseURL.trim();
|
|
457
463
|
if (!trimmed) {
|
|
@@ -45,6 +45,7 @@ describe('settings helpers', () => {
|
|
|
45
45
|
summaryModelMode: 'custom',
|
|
46
46
|
summaryCustomBaseURL: 'https://summary.example.test/v1/',
|
|
47
47
|
summaryModelID: 'summary-model',
|
|
48
|
+
summaryCustomModelID: 'custom-model',
|
|
48
49
|
summaryCustomAPIToken: 'summary-token',
|
|
49
50
|
summaryCommitPrompt: 'Return commit JSON.',
|
|
50
51
|
summarySessionTitlePrompt: 'Return a short title.',
|
|
@@ -54,6 +55,7 @@ describe('settings helpers', () => {
|
|
|
54
55
|
summaryModelMode: 'custom',
|
|
55
56
|
summaryCustomBaseURL: 'https://summary.example.test/v1',
|
|
56
57
|
summaryModelID: 'summary-model',
|
|
58
|
+
summaryCustomModelID: 'custom-model',
|
|
57
59
|
summaryCustomAPIToken: 'summary-token',
|
|
58
60
|
summaryCommitPrompt: 'Return commit JSON.',
|
|
59
61
|
summarySessionTitlePrompt: 'Return a short title.',
|
|
@@ -63,6 +65,7 @@ describe('settings helpers', () => {
|
|
|
63
65
|
summaryModelMode: 'custom',
|
|
64
66
|
summaryCustomBaseURL: 'https://summary.example.test/v1',
|
|
65
67
|
summaryModelID: 'summary-model',
|
|
68
|
+
summaryCustomModelID: 'custom-model',
|
|
66
69
|
hasSummaryCustomAPIToken: true,
|
|
67
70
|
});
|
|
68
71
|
expect(response).not.toHaveProperty('summaryCustomAPIToken');
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# Small Model
|
|
2
2
|
|
|
3
|
-
Server-side LLM calls that reuse the user's existing OpenCode provider logins
|
|
4
|
-
|
|
3
|
+
Server-side LLM calls that reuse the user's existing OpenCode provider logins.
|
|
4
|
+
Dedicated adapters still read `~/.local/share/opencode/auth.json`. Plugin and
|
|
5
|
+
other non-dedicated providers are listed from OpenCode's connected catalog
|
|
6
|
+
(`provider.list()` ∩ `config.providers`, same as `loadConnectedCatalog`).
|
|
7
|
+
OpenCode uses a "small model" internally
|
|
5
8
|
(titles, summaries) but does not expose it through the SDK or plugins — this
|
|
6
9
|
module replicates that mechanism as an OpenChamber runtime API.
|
|
7
10
|
|
|
@@ -83,12 +86,18 @@ every other runtime API.
|
|
|
83
86
|
`cost.input` / `cost.output`, `model.api.url`, provider `name`. Client-safe
|
|
84
87
|
provider catalog projection remains in `opencode/provider-catalog.js` for
|
|
85
88
|
`/api/config/catalog/providers`.
|
|
86
|
-
- `routes.js` — `GET /api/small-model` (resolution preview)
|
|
89
|
+
- `routes.js` — `GET /api/small-model` (resolution preview),
|
|
87
90
|
`POST /api/small-model/generate` (`{ prompt, system?, maxOutputTokens?,
|
|
88
|
-
model?, directory? }` → `{ text, providerID, modelID, source }`)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
`
|
|
91
|
+
model?, directory? }` → `{ text, providerID, modelID, source }`),
|
|
92
|
+
`POST /api/small-model/test` (custom OpenAI-compatible probe → `{ ok, code? }`),
|
|
93
|
+
and `POST /api/small-model/custom-models` (best-effort `{baseURL}/models`
|
|
94
|
+
suggestions → `{ models }`). The preview response includes `callableModels`,
|
|
95
|
+
the Provider/model allowlist consumed by Settings → Summary AI. Callable
|
|
96
|
+
lists receive the same `directory` as describe/generate so they share the
|
|
97
|
+
directory catalog.
|
|
98
|
+
- `custom-api.js` — custom Summary AI probe and model listing. Failure codes
|
|
99
|
+
are `incomplete`, `token` (401/403), `model` (404 / `model_not_found`), or
|
|
100
|
+
`baseURL` (network / non-OpenAI-compatible). Tokens are never logged.
|
|
92
101
|
|
|
93
102
|
## Dispatch
|
|
94
103
|
|
|
@@ -98,10 +107,16 @@ every other runtime API.
|
|
|
98
107
|
| `openai` / `anthropic` / `google` / `github-copilot` with usable dedicated auth | Direct dedicated adapter (`call.js`) |
|
|
99
108
|
| Any other usable auth + catalog model (plugin providers, …) | Temporary OpenCode session (`opencode-session.js`) |
|
|
100
109
|
|
|
101
|
-
Callable Settings lists
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
110
|
+
Callable Settings lists:
|
|
111
|
+
- Dedicated adapters (`openai`, `anthropic`, `google`, `github-copilot`) stay
|
|
112
|
+
gated by usable `auth.json` entries and the existing small-model whitelist
|
|
113
|
+
(OpenAI OAuth → `gpt-5.4-mini`; Copilot → `gpt-5.4-nano`).
|
|
114
|
+
- Every other provider is taken from the connected catalog (plugin / env
|
|
115
|
+
credentials that OpenCode reports as connected), intersected with catalog
|
|
116
|
+
model ids. No `api.url` gate. If the connected catalog request fails, the
|
|
117
|
+
list falls back to usable `auth.json` plugin entries rather than pretending
|
|
118
|
+
none exist.
|
|
119
|
+
Copilot auth aliases merge to `github-copilot`.
|
|
105
120
|
|
|
106
121
|
## OpenCode session lifecycle
|
|
107
122
|
|
|
@@ -149,9 +164,13 @@ directly.
|
|
|
149
164
|
Commit-message generation and session-title refresh pass `purpose: 'commit'`
|
|
150
165
|
or `purpose: 'session-title'` to `generateSmallModelText`. Settings → Summary
|
|
151
166
|
AI can select an authenticated OpenCode provider/model or a custom
|
|
152
|
-
OpenAI-compatible `baseURL`, model ID, and API token.
|
|
153
|
-
|
|
154
|
-
|
|
167
|
+
OpenAI-compatible `baseURL`, model ID, and API token. Custom mode stores the
|
|
168
|
+
model id in `summaryCustomModelID` (legacy `summaryModelID` is still read when
|
|
169
|
+
that field is absent). Custom mode is enabled only when base URL, model ID,
|
|
170
|
+
and API token are all present. A custom token stays in the server settings
|
|
171
|
+
file; settings read responses expose only `hasSummaryCustomAPIToken`. Settings
|
|
172
|
+
can probe the custom API (`POST /api/small-model/test`) and load model
|
|
173
|
+
suggestions from `{baseURL}/models` without blocking free-form entry.
|
|
155
174
|
|
|
156
175
|
`summaryCommitPrompt` and `summarySessionTitlePrompt` replace the respective
|
|
157
176
|
call's system prompt when non-empty. With no persisted provider choice, summary
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
const TEST_TIMEOUT_MS = 15_000;
|
|
2
|
+
const MODELS_TIMEOUT_MS = 8_000;
|
|
3
|
+
|
|
4
|
+
const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
5
|
+
|
|
6
|
+
export function parseCustomApiBaseURL(value) {
|
|
7
|
+
if (typeof value !== 'string') return null;
|
|
8
|
+
const trimmed = value.trim();
|
|
9
|
+
if (!trimmed) return null;
|
|
10
|
+
try {
|
|
11
|
+
const url = new URL(trimmed);
|
|
12
|
+
if ((url.protocol !== 'https:' && url.protocol !== 'http:') || url.username || url.password) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
return url.toString().replace(/\/+$/, '');
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function classifyCustomSummaryApiFailure({ status, bodyText, cause } = {}) {
|
|
22
|
+
const text = typeof bodyText === 'string' ? bodyText : '';
|
|
23
|
+
if (status === 401 || status === 403) return 'token';
|
|
24
|
+
if (status === 404) return 'model';
|
|
25
|
+
if (/model_not_found|invalid_model|model[_ ]?not[_ ]found/i.test(text)) return 'model';
|
|
26
|
+
if (status === 400 && /model/i.test(text)) return 'model';
|
|
27
|
+
|
|
28
|
+
const code = typeof cause?.code === 'string' ? cause.code : '';
|
|
29
|
+
const name = typeof cause?.name === 'string' ? cause.name : '';
|
|
30
|
+
if (
|
|
31
|
+
name === 'AbortError'
|
|
32
|
+
|| name === 'TimeoutError'
|
|
33
|
+
|| code === 'ENOTFOUND'
|
|
34
|
+
|| code === 'ECONNREFUSED'
|
|
35
|
+
|| code === 'ECONNRESET'
|
|
36
|
+
|| code === 'ERR_INVALID_URL'
|
|
37
|
+
|| code === 'ENETUNREACH'
|
|
38
|
+
) {
|
|
39
|
+
return 'baseURL';
|
|
40
|
+
}
|
|
41
|
+
if (!status) return 'baseURL';
|
|
42
|
+
return 'baseURL';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const parseJson = (bodyText) => {
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(bodyText);
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export async function testCustomSummaryApi({
|
|
54
|
+
baseURL,
|
|
55
|
+
modelID,
|
|
56
|
+
apiToken,
|
|
57
|
+
fetchImpl = globalThis.fetch.bind(globalThis),
|
|
58
|
+
} = {}) {
|
|
59
|
+
const parsedBaseURL = parseCustomApiBaseURL(baseURL);
|
|
60
|
+
const trimmedModelID = typeof modelID === 'string' ? modelID.trim() : '';
|
|
61
|
+
const trimmedToken = typeof apiToken === 'string' ? apiToken.trim() : '';
|
|
62
|
+
if (!parsedBaseURL || !trimmedModelID || !trimmedToken) {
|
|
63
|
+
return { ok: false, code: 'incomplete' };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const response = await fetchImpl(`${parsedBaseURL}/chat/completions`, {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
headers: {
|
|
70
|
+
'Content-Type': 'application/json',
|
|
71
|
+
Accept: 'application/json',
|
|
72
|
+
Authorization: `Bearer ${trimmedToken}`,
|
|
73
|
+
},
|
|
74
|
+
body: JSON.stringify({
|
|
75
|
+
model: trimmedModelID,
|
|
76
|
+
messages: [{ role: 'user', content: 'ping' }],
|
|
77
|
+
max_tokens: 1,
|
|
78
|
+
stream: false,
|
|
79
|
+
}),
|
|
80
|
+
signal: AbortSignal.timeout(TEST_TIMEOUT_MS),
|
|
81
|
+
});
|
|
82
|
+
const bodyText = await response.text().catch(() => '');
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
return {
|
|
85
|
+
ok: false,
|
|
86
|
+
code: classifyCustomSummaryApiFailure({ status: response.status, bodyText }),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const payload = parseJson(bodyText);
|
|
90
|
+
if (!isRecord(payload) || !Array.isArray(payload.choices)) {
|
|
91
|
+
return { ok: false, code: 'baseURL' };
|
|
92
|
+
}
|
|
93
|
+
return { ok: true };
|
|
94
|
+
} catch (error) {
|
|
95
|
+
return { ok: false, code: classifyCustomSummaryApiFailure({ cause: error }) };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function listCustomSummaryModels({
|
|
100
|
+
baseURL,
|
|
101
|
+
apiToken,
|
|
102
|
+
fetchImpl = globalThis.fetch.bind(globalThis),
|
|
103
|
+
} = {}) {
|
|
104
|
+
const parsedBaseURL = parseCustomApiBaseURL(baseURL);
|
|
105
|
+
const trimmedToken = typeof apiToken === 'string' ? apiToken.trim() : '';
|
|
106
|
+
if (!parsedBaseURL || !trimmedToken) {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const response = await fetchImpl(`${parsedBaseURL}/models`, {
|
|
112
|
+
method: 'GET',
|
|
113
|
+
headers: {
|
|
114
|
+
Accept: 'application/json',
|
|
115
|
+
Authorization: `Bearer ${trimmedToken}`,
|
|
116
|
+
},
|
|
117
|
+
signal: AbortSignal.timeout(MODELS_TIMEOUT_MS),
|
|
118
|
+
});
|
|
119
|
+
if (!response.ok) return [];
|
|
120
|
+
const payload = parseJson(await response.text().catch(() => ''));
|
|
121
|
+
const rows = Array.isArray(payload?.data)
|
|
122
|
+
? payload.data
|
|
123
|
+
: Array.isArray(payload?.models)
|
|
124
|
+
? payload.models
|
|
125
|
+
: Array.isArray(payload)
|
|
126
|
+
? payload
|
|
127
|
+
: [];
|
|
128
|
+
const ids = [];
|
|
129
|
+
for (const row of rows) {
|
|
130
|
+
const id = typeof row === 'string'
|
|
131
|
+
? row.trim()
|
|
132
|
+
: typeof row?.id === 'string'
|
|
133
|
+
? row.id.trim()
|
|
134
|
+
: '';
|
|
135
|
+
if (id) ids.push(id);
|
|
136
|
+
}
|
|
137
|
+
return [...new Set(ids)];
|
|
138
|
+
} catch {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
classifyCustomSummaryApiFailure,
|
|
4
|
+
listCustomSummaryModels,
|
|
5
|
+
parseCustomApiBaseURL,
|
|
6
|
+
testCustomSummaryApi,
|
|
7
|
+
} from './custom-api.js';
|
|
8
|
+
|
|
9
|
+
describe('parseCustomApiBaseURL', () => {
|
|
10
|
+
it('accepts http(s) URLs and strips trailing slashes', () => {
|
|
11
|
+
expect(parseCustomApiBaseURL('https://api.example.test/v1/')).toBe('https://api.example.test/v1');
|
|
12
|
+
expect(parseCustomApiBaseURL('http://127.0.0.1:4000')).toBe('http://127.0.0.1:4000');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('rejects non-http URLs and embedded credentials', () => {
|
|
16
|
+
expect(parseCustomApiBaseURL('ftp://api.example.test')).toBeNull();
|
|
17
|
+
expect(parseCustomApiBaseURL('https://user:pass@api.example.test/v1')).toBeNull();
|
|
18
|
+
expect(parseCustomApiBaseURL('not a url')).toBeNull();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('classifyCustomSummaryApiFailure', () => {
|
|
23
|
+
it('maps 401/403 to token and 404/model_not_found to model', () => {
|
|
24
|
+
expect(classifyCustomSummaryApiFailure({ status: 401 })).toBe('token');
|
|
25
|
+
expect(classifyCustomSummaryApiFailure({ status: 403 })).toBe('token');
|
|
26
|
+
expect(classifyCustomSummaryApiFailure({ status: 404 })).toBe('model');
|
|
27
|
+
expect(classifyCustomSummaryApiFailure({
|
|
28
|
+
status: 400,
|
|
29
|
+
bodyText: '{"error":{"code":"model_not_found"}}',
|
|
30
|
+
})).toBe('model');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('maps network and non-OpenAI-compatible failures to baseURL', () => {
|
|
34
|
+
expect(classifyCustomSummaryApiFailure({ cause: { code: 'ENOTFOUND' } })).toBe('baseURL');
|
|
35
|
+
expect(classifyCustomSummaryApiFailure({ cause: { name: 'AbortError' } })).toBe('baseURL');
|
|
36
|
+
expect(classifyCustomSummaryApiFailure({ status: 502, bodyText: '<html>bad gateway</html>' })).toBe('baseURL');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('testCustomSummaryApi', () => {
|
|
41
|
+
it('returns incomplete when any of the custom triple is missing', async () => {
|
|
42
|
+
expect(await testCustomSummaryApi({
|
|
43
|
+
baseURL: 'https://api.example.test/v1',
|
|
44
|
+
modelID: 'gpt-4.1-mini',
|
|
45
|
+
})).toEqual({ ok: false, code: 'incomplete' });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('classifies HTTP failures from the probe request', async () => {
|
|
49
|
+
const fetchImpl = vi.fn(async () => new Response('unauthorized', { status: 401 }));
|
|
50
|
+
expect(await testCustomSummaryApi({
|
|
51
|
+
baseURL: 'https://api.example.test/v1',
|
|
52
|
+
modelID: 'gpt-4.1-mini',
|
|
53
|
+
apiToken: 'sk-test',
|
|
54
|
+
fetchImpl,
|
|
55
|
+
})).toEqual({ ok: false, code: 'token' });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('accepts an OpenAI-compatible chat completion payload', async () => {
|
|
59
|
+
const fetchImpl = vi.fn(async () => new Response(JSON.stringify({
|
|
60
|
+
choices: [{ message: { content: 'ok' } }],
|
|
61
|
+
}), { status: 200, headers: { 'Content-Type': 'application/json' } }));
|
|
62
|
+
expect(await testCustomSummaryApi({
|
|
63
|
+
baseURL: 'https://api.example.test/v1',
|
|
64
|
+
modelID: 'gpt-4.1-mini',
|
|
65
|
+
apiToken: 'sk-test',
|
|
66
|
+
fetchImpl,
|
|
67
|
+
})).toEqual({ ok: true });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('treats a 200 HTML body as a baseURL failure', async () => {
|
|
71
|
+
const fetchImpl = vi.fn(async () => new Response('<html>not openai</html>', { status: 200 }));
|
|
72
|
+
expect(await testCustomSummaryApi({
|
|
73
|
+
baseURL: 'https://api.example.test/v1',
|
|
74
|
+
modelID: 'gpt-4.1-mini',
|
|
75
|
+
apiToken: 'sk-test',
|
|
76
|
+
fetchImpl,
|
|
77
|
+
})).toEqual({ ok: false, code: 'baseURL' });
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('listCustomSummaryModels', () => {
|
|
82
|
+
it('returns unique model ids from an OpenAI-compatible /models payload', async () => {
|
|
83
|
+
const fetchImpl = vi.fn(async () => new Response(JSON.stringify({
|
|
84
|
+
data: [{ id: 'gpt-4.1-mini' }, { id: 'gpt-4.1-nano' }, { id: 'gpt-4.1-mini' }],
|
|
85
|
+
}), { status: 200 }));
|
|
86
|
+
expect(await listCustomSummaryModels({
|
|
87
|
+
baseURL: 'https://api.example.test/v1',
|
|
88
|
+
apiToken: 'sk-test',
|
|
89
|
+
fetchImpl,
|
|
90
|
+
})).toEqual(['gpt-4.1-mini', 'gpt-4.1-nano']);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('degrades to an empty list when /models is unavailable', async () => {
|
|
94
|
+
const fetchImpl = vi.fn(async () => { throw Object.assign(new Error('offline'), { code: 'ENOTFOUND' }); });
|
|
95
|
+
expect(await listCustomSummaryModels({
|
|
96
|
+
baseURL: 'https://api.example.test/v1',
|
|
97
|
+
apiToken: 'sk-test',
|
|
98
|
+
fetchImpl,
|
|
99
|
+
})).toEqual([]);
|
|
100
|
+
});
|
|
101
|
+
});
|