@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
|
@@ -3,10 +3,13 @@ import os from 'os';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { readAuthFile } from '../opencode/auth.js';
|
|
5
5
|
import { readConfigLayers } from '../opencode/shared.js';
|
|
6
|
+
import { createOpencodeClient } from '@opencode-ai/sdk/v2';
|
|
7
|
+
import { loadConnectedCatalog } from '../llm/catalog.js';
|
|
6
8
|
import { createModelCatalogLoader, getCatalogProvider } from './catalog.js';
|
|
7
9
|
import { resolveSmallModel, parseModelRef, isUsableAuthEntry, getAuthEntryForProvider } from './resolve.js';
|
|
8
10
|
import { callSmallModel } from './call.js';
|
|
9
11
|
import { generateViaOpenCodeSession, stop as stopOpenCodeSessionTemp } from './opencode-session.js';
|
|
12
|
+
import { listCustomSummaryModels, testCustomSummaryApi } from './custom-api.js';
|
|
10
13
|
|
|
11
14
|
const OPENCHAMBER_SETTINGS_FILE = path.join(
|
|
12
15
|
process.env.OPENCHAMBER_DATA_DIR
|
|
@@ -25,13 +28,19 @@ const readSummarySettings = () => {
|
|
|
25
28
|
const mode = settings.summaryModelMode === 'custom' ? 'custom' : 'provider';
|
|
26
29
|
const providerID = typeof settings.summaryProviderID === 'string' ? settings.summaryProviderID.trim() : '';
|
|
27
30
|
const modelID = typeof settings.summaryModelID === 'string' ? settings.summaryModelID.trim() : '';
|
|
31
|
+
const customModelID = typeof settings.summaryCustomModelID === 'string'
|
|
32
|
+
? settings.summaryCustomModelID.trim()
|
|
33
|
+
: '';
|
|
34
|
+
const effectiveCustomModelID = customModelID || modelID;
|
|
28
35
|
const baseURL = typeof settings.summaryCustomBaseURL === 'string' ? settings.summaryCustomBaseURL.trim() : '';
|
|
29
36
|
const apiToken = typeof settings.summaryCustomAPIToken === 'string' ? settings.summaryCustomAPIToken.trim() : '';
|
|
30
37
|
return {
|
|
31
38
|
mode,
|
|
32
39
|
providerID,
|
|
33
40
|
modelID,
|
|
34
|
-
custom: baseURL && apiToken &&
|
|
41
|
+
custom: baseURL && apiToken && effectiveCustomModelID
|
|
42
|
+
? { baseURL, apiToken, modelID: effectiveCustomModelID }
|
|
43
|
+
: null,
|
|
35
44
|
prompts: {
|
|
36
45
|
commit: typeof settings.summaryCommitPrompt === 'string' ? settings.summaryCommitPrompt.trim() : '',
|
|
37
46
|
'session-title': typeof settings.summarySessionTitlePrompt === 'string' ? settings.summarySessionTitlePrompt.trim() : '',
|
|
@@ -113,28 +122,39 @@ const shouldUseDirectAdapter = (providerID, auth) => {
|
|
|
113
122
|
return isCallableDedicatedAuth(auth, providerID);
|
|
114
123
|
};
|
|
115
124
|
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}),
|
|
128
|
-
);
|
|
125
|
+
const addNonDedicatedCallableProvider = (ids, catalog, providerID) => {
|
|
126
|
+
if (!providerID || providerID === 'copilot' || DEDICATED_DIRECT_PROVIDERS.has(providerID)) return;
|
|
127
|
+
const provider = getCatalogProvider(catalog, providerID);
|
|
128
|
+
if (providerHasCatalogModels(provider)) ids.add(providerID);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const listCallableProviderIDsForState = (auth, catalog, connectedIDs) => {
|
|
132
|
+
const ids = new Set();
|
|
133
|
+
for (const providerID of DEDICATED_DIRECT_PROVIDERS) {
|
|
134
|
+
if (isCallableDedicatedAuth(auth, providerID)) ids.add(providerID);
|
|
135
|
+
}
|
|
129
136
|
if (isUsableAuthEntry(getAuthEntryForProvider(auth, 'github-copilot'))) {
|
|
130
137
|
ids.add('github-copilot');
|
|
131
138
|
}
|
|
139
|
+
|
|
140
|
+
if (Array.isArray(connectedIDs)) {
|
|
141
|
+
for (const providerID of connectedIDs) {
|
|
142
|
+
addNonDedicatedCallableProvider(ids, catalog, providerID);
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
// Connected catalog failed: do not pretend plugin providers are absent.
|
|
146
|
+
for (const providerID of Object.keys(auth || {})) {
|
|
147
|
+
if (providerID === 'copilot') continue;
|
|
148
|
+
if (!isUsableAuthEntry(auth[providerID])) continue;
|
|
149
|
+
addNonDedicatedCallableProvider(ids, catalog, providerID);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
132
152
|
return Array.from(ids);
|
|
133
153
|
};
|
|
134
154
|
|
|
135
|
-
const listCallableModelsForState = (auth, catalog) => {
|
|
155
|
+
const listCallableModelsForState = (auth, catalog, connectedIDs) => {
|
|
136
156
|
const result = {};
|
|
137
|
-
for (const providerID of listCallableProviderIDsForState(auth, catalog)) {
|
|
157
|
+
for (const providerID of listCallableProviderIDsForState(auth, catalog, connectedIDs)) {
|
|
138
158
|
if (providerID === 'openai' && auth.openai?.type === 'oauth') {
|
|
139
159
|
result[providerID] = ['gpt-5.4-mini'];
|
|
140
160
|
continue;
|
|
@@ -154,8 +174,8 @@ const listCallableModelsForState = (auth, catalog) => {
|
|
|
154
174
|
return result;
|
|
155
175
|
};
|
|
156
176
|
|
|
157
|
-
const resolveDefaultSummaryModel = (auth, catalog) => {
|
|
158
|
-
const callableModels = listCallableModelsForState(auth, catalog);
|
|
177
|
+
const resolveDefaultSummaryModel = (auth, catalog, connectedIDs) => {
|
|
178
|
+
const callableModels = listCallableModelsForState(auth, catalog, connectedIDs);
|
|
159
179
|
const providerIDs = Object.keys(callableModels);
|
|
160
180
|
const providerID = callableModels.openai ? 'openai' : providerIDs[0];
|
|
161
181
|
const modelID = providerID ? callableModels[providerID]?.[0] : undefined;
|
|
@@ -183,6 +203,7 @@ const readConfiguredSmallModel = (workingDirectory) => {
|
|
|
183
203
|
* buildOpenCodeUrl: (pathname: string, search?: string) => string,
|
|
184
204
|
* getOpenCodeAuthHeaders: () => Record<string, string>,
|
|
185
205
|
* getModelCatalog?: (directory?: string) => Promise<object>,
|
|
206
|
+
* getConnectedCatalog?: (directory?: string) => Promise<{ connected?: string[] }>,
|
|
186
207
|
* }} dependencies
|
|
187
208
|
*/
|
|
188
209
|
export function createSmallModelService(dependencies) {
|
|
@@ -193,6 +214,41 @@ export function createSmallModelService(dependencies) {
|
|
|
193
214
|
buildOpenCodeUrl,
|
|
194
215
|
getOpenCodeAuthHeaders,
|
|
195
216
|
}).getModelCatalog;
|
|
217
|
+
const getConnectedCatalog = dependencies.getConnectedCatalog || (async (directory) => {
|
|
218
|
+
const client = createOpencodeClient({
|
|
219
|
+
baseUrl: buildOpenCodeUrl('/', '').replace(/\/$/, ''),
|
|
220
|
+
...(directory ? { directory } : {}),
|
|
221
|
+
headers: getOpenCodeAuthHeaders(),
|
|
222
|
+
});
|
|
223
|
+
return loadConnectedCatalog(client);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const loadCallableContext = async (directory) => {
|
|
227
|
+
const auth = readAuthFile();
|
|
228
|
+
const catalog = await getModelCatalog(directory);
|
|
229
|
+
let connectedIDs = null;
|
|
230
|
+
try {
|
|
231
|
+
const connectedCatalog = await getConnectedCatalog(directory);
|
|
232
|
+
connectedIDs = Array.isArray(connectedCatalog?.connected) ? connectedCatalog.connected : [];
|
|
233
|
+
} catch {
|
|
234
|
+
connectedIDs = null;
|
|
235
|
+
}
|
|
236
|
+
return { auth, catalog, connectedIDs };
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const resolveCustomApiInput = (body = {}) => {
|
|
240
|
+
const summarySettings = readSummarySettings();
|
|
241
|
+
const baseURL = typeof body.baseURL === 'string' && body.baseURL.trim()
|
|
242
|
+
? body.baseURL.trim()
|
|
243
|
+
: (summarySettings?.custom?.baseURL || '');
|
|
244
|
+
const modelID = typeof body.modelID === 'string' && body.modelID.trim()
|
|
245
|
+
? body.modelID.trim()
|
|
246
|
+
: (summarySettings?.custom?.modelID || '');
|
|
247
|
+
const apiToken = typeof body.apiToken === 'string' && body.apiToken.trim()
|
|
248
|
+
? body.apiToken.trim()
|
|
249
|
+
: (summarySettings?.custom?.apiToken || '');
|
|
250
|
+
return { baseURL, modelID, apiToken };
|
|
251
|
+
};
|
|
196
252
|
|
|
197
253
|
/**
|
|
198
254
|
* Generates text with the user's small model, resolved and authenticated
|
|
@@ -213,12 +269,11 @@ export function createSmallModelService(dependencies) {
|
|
|
213
269
|
throw Object.assign(new Error('prompt is required'), { statusCode: 400 });
|
|
214
270
|
}
|
|
215
271
|
|
|
216
|
-
const auth =
|
|
217
|
-
const catalog = await getModelCatalog(directory);
|
|
272
|
+
const { auth, catalog, connectedIDs } = await loadCallableContext(directory);
|
|
218
273
|
const summarySettings = SUMMARY_PURPOSES.has(purpose) ? readSummarySettings() : null;
|
|
219
274
|
const summaryPrompt = summarySettings?.prompts?.[purpose];
|
|
220
275
|
const defaultSummaryModel = SUMMARY_PURPOSES.has(purpose)
|
|
221
|
-
? resolveDefaultSummaryModel(auth, catalog)
|
|
276
|
+
? resolveDefaultSummaryModel(auth, catalog, connectedIDs)
|
|
222
277
|
: null;
|
|
223
278
|
|
|
224
279
|
const explicit = parseModelRef(model);
|
|
@@ -324,9 +379,8 @@ export function createSmallModelService(dependencies) {
|
|
|
324
379
|
*/
|
|
325
380
|
async function listCallableProviders({ directory } = {}) {
|
|
326
381
|
try {
|
|
327
|
-
const auth =
|
|
328
|
-
|
|
329
|
-
return listCallableProviderIDsForState(auth, catalog);
|
|
382
|
+
const { auth, catalog, connectedIDs } = await loadCallableContext(directory);
|
|
383
|
+
return listCallableProviderIDsForState(auth, catalog, connectedIDs);
|
|
330
384
|
} catch {
|
|
331
385
|
return [];
|
|
332
386
|
}
|
|
@@ -334,14 +388,22 @@ export function createSmallModelService(dependencies) {
|
|
|
334
388
|
|
|
335
389
|
async function listCallableModels({ directory } = {}) {
|
|
336
390
|
try {
|
|
337
|
-
const auth =
|
|
338
|
-
|
|
339
|
-
return listCallableModelsForState(auth, catalog);
|
|
391
|
+
const { auth, catalog, connectedIDs } = await loadCallableContext(directory);
|
|
392
|
+
return listCallableModelsForState(auth, catalog, connectedIDs);
|
|
340
393
|
} catch {
|
|
341
394
|
return {};
|
|
342
395
|
}
|
|
343
396
|
}
|
|
344
397
|
|
|
398
|
+
async function testCustomApi(body) {
|
|
399
|
+
return testCustomSummaryApi(resolveCustomApiInput(body));
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
async function listCustomModels(body) {
|
|
403
|
+
const { baseURL, apiToken } = resolveCustomApiInput(body);
|
|
404
|
+
return listCustomSummaryModels({ baseURL, apiToken });
|
|
405
|
+
}
|
|
406
|
+
|
|
345
407
|
/**
|
|
346
408
|
* Reports which model would be used, without calling it.
|
|
347
409
|
*/
|
|
@@ -363,6 +425,8 @@ export function createSmallModelService(dependencies) {
|
|
|
363
425
|
generateSmallModelText,
|
|
364
426
|
listCallableProviders,
|
|
365
427
|
listCallableModels,
|
|
428
|
+
testCustomApi,
|
|
429
|
+
listCustomModels,
|
|
366
430
|
describeSmallModel,
|
|
367
431
|
stop: stopOpenCodeSessionTemp,
|
|
368
432
|
};
|
|
@@ -44,4 +44,26 @@ export function registerSmallModelRoutes(app, { getSmallModelService }) {
|
|
|
44
44
|
res.status(statusCode).json({ error: error.message || 'Small model generation failed' });
|
|
45
45
|
}
|
|
46
46
|
});
|
|
47
|
+
|
|
48
|
+
app.post('/api/small-model/test', async (req, res) => {
|
|
49
|
+
try {
|
|
50
|
+
const { testCustomApi } = await getSmallModelService();
|
|
51
|
+
const result = await testCustomApi(req.body || {});
|
|
52
|
+
res.status(result.ok ? 200 : 400).json(result);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error('Small model custom API test failed:', error);
|
|
55
|
+
res.status(500).json({ ok: false, code: 'baseURL' });
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
app.post('/api/small-model/custom-models', async (req, res) => {
|
|
60
|
+
try {
|
|
61
|
+
const { listCustomModels } = await getSmallModelService();
|
|
62
|
+
const models = await listCustomModels(req.body || {});
|
|
63
|
+
res.json({ models });
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error('Small model custom model list failed:', error);
|
|
66
|
+
res.json({ models: [] });
|
|
67
|
+
}
|
|
68
|
+
});
|
|
47
69
|
}
|
|
@@ -29,10 +29,14 @@ const { callSmallModel } = await import('./call.js');
|
|
|
29
29
|
const { generateViaOpenCodeSession } = await import('./opencode-session.js');
|
|
30
30
|
const { readAuthFile } = await import('../opencode/auth.js');
|
|
31
31
|
|
|
32
|
-
const createService = (catalog = {}) => createSmallModelService({
|
|
32
|
+
const createService = (catalog = {}, connected = null) => createSmallModelService({
|
|
33
33
|
buildOpenCodeUrl: () => 'http://127.0.0.1:4096/',
|
|
34
34
|
getOpenCodeAuthHeaders: () => ({}),
|
|
35
35
|
getModelCatalog: async () => catalog,
|
|
36
|
+
getConnectedCatalog: async () => {
|
|
37
|
+
if (connected === null) throw new Error('connected catalog unavailable');
|
|
38
|
+
return { connected, providers: [], models: [] };
|
|
39
|
+
},
|
|
36
40
|
});
|
|
37
41
|
|
|
38
42
|
describe('summary AI settings', () => {
|
|
@@ -282,4 +286,84 @@ describe('summary AI settings', () => {
|
|
|
282
286
|
source: 'summary-provider',
|
|
283
287
|
});
|
|
284
288
|
});
|
|
289
|
+
|
|
290
|
+
it('lists connected plugin providers that have no auth.json entry', async () => {
|
|
291
|
+
vi.mocked(readAuthFile).mockReturnValue({});
|
|
292
|
+
const catalog = {
|
|
293
|
+
codebuddy: {
|
|
294
|
+
id: 'codebuddy',
|
|
295
|
+
name: 'CodeBuddy',
|
|
296
|
+
models: { 'codebuddy-flash': { id: 'codebuddy-flash' } },
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const { listCallableProviders, listCallableModels } = createService(catalog, ['codebuddy']);
|
|
301
|
+
expect(await listCallableProviders()).toEqual(['codebuddy']);
|
|
302
|
+
expect(await listCallableModels()).toEqual({ codebuddy: ['codebuddy-flash'] });
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('does not list auth.json plugin providers that are not connected', async () => {
|
|
306
|
+
vi.mocked(readAuthFile).mockReturnValue({
|
|
307
|
+
mistral: { type: 'api', key: 'mistral-key' },
|
|
308
|
+
});
|
|
309
|
+
const catalog = {
|
|
310
|
+
mistral: {
|
|
311
|
+
id: 'mistral',
|
|
312
|
+
name: 'Mistral',
|
|
313
|
+
models: { 'mistral-small': { id: 'mistral-small' } },
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const { listCallableProviders, listCallableModels } = createService(catalog, []);
|
|
318
|
+
expect(await listCallableProviders()).toEqual([]);
|
|
319
|
+
expect(await listCallableModels()).toEqual({});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('keeps dedicated providers callable from auth.json even when they are not connected', async () => {
|
|
323
|
+
vi.mocked(readAuthFile).mockReturnValue({
|
|
324
|
+
openai: { type: 'oauth', access: 'openai-access', refresh: 'openai-refresh', expires: 0 },
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const { listCallableProviders, listCallableModels } = createService({}, []);
|
|
328
|
+
expect(await listCallableProviders()).toEqual(['openai']);
|
|
329
|
+
expect(await listCallableModels()).toEqual({ openai: ['gpt-5.4-mini'] });
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('requires a custom base URL, model ID, and token before enabling custom mode', async () => {
|
|
333
|
+
await fsPromises.writeFile(path.join(tempRoot, 'settings.json'), JSON.stringify({
|
|
334
|
+
summaryModelMode: 'custom',
|
|
335
|
+
summaryCustomBaseURL: 'https://summary.example.test/v1',
|
|
336
|
+
summaryModelID: 'summary-model',
|
|
337
|
+
}), 'utf8');
|
|
338
|
+
|
|
339
|
+
const { generateSmallModelText } = createService();
|
|
340
|
+
await expect(generateSmallModelText({
|
|
341
|
+
purpose: 'commit',
|
|
342
|
+
prompt: 'Diff content',
|
|
343
|
+
})).rejects.toMatchObject({
|
|
344
|
+
message: 'Custom summary API requires a Base URL, model ID, and API token',
|
|
345
|
+
statusCode: 400,
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('prefers summaryCustomModelID over the shared summaryModelID for custom calls', async () => {
|
|
350
|
+
await fsPromises.writeFile(path.join(tempRoot, 'settings.json'), JSON.stringify({
|
|
351
|
+
summaryModelMode: 'custom',
|
|
352
|
+
summaryCustomBaseURL: 'https://summary.example.test/v1',
|
|
353
|
+
summaryModelID: 'provider-model',
|
|
354
|
+
summaryCustomModelID: 'custom-model',
|
|
355
|
+
summaryCustomAPIToken: 'summary-token',
|
|
356
|
+
}), 'utf8');
|
|
357
|
+
|
|
358
|
+
const { generateSmallModelText } = createService();
|
|
359
|
+
await generateSmallModelText({
|
|
360
|
+
purpose: 'commit',
|
|
361
|
+
prompt: 'Diff content',
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
expect(callSmallModel).toHaveBeenCalledWith(expect.objectContaining({
|
|
365
|
+
modelID: 'custom-model',
|
|
366
|
+
custom: expect.objectContaining({ modelID: 'custom-model' }),
|
|
367
|
+
}));
|
|
368
|
+
});
|
|
285
369
|
});
|