@openchambery/web 1.18.2-beta.16 → 1.18.2-beta.18
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/{AssistantView-5b0FsCQa.js → AssistantView-BjECeCP-.js} +1 -1
- package/dist/assets/{ChatContainer-QtcnvWoA.js → ChatContainer-BYOJGW70.js} +4 -4
- package/dist/assets/{DiagramView-BoWXd8X4.js → DiagramView-y5N3UVYC.js} +1 -1
- package/dist/assets/{MarkdownRendererImpl-0Gq_Uqs_.js → MarkdownRendererImpl-C_42Xy0b.js} +1 -1
- package/dist/assets/{MobileDetailNavigation-CBWFnBIu.js → MobileDetailNavigation-CESuP4mM.js} +1 -1
- package/dist/assets/{MobileShareBridge-CTg5aHEn.js → MobileShareBridge-DteabHid.js} +1 -1
- package/dist/assets/{MobileSurface-Dw6Ina7A.js → MobileSurface-U-yc1m0-.js} +1 -1
- package/dist/assets/{MultiRunWindow-De0C4bCt.js → MultiRunWindow-CCF0PXlx.js} +1 -1
- package/dist/assets/{OnboardingScreen-7mFH7iBm.js → OnboardingScreen-CYeRZznl.js} +1 -1
- package/dist/assets/{OverlayScrollbar-DzX65Ew5.js → OverlayScrollbar-C5kI44g7.js} +1 -1
- package/dist/assets/{SettingsView-Bs2kVAA1.js → SettingsView-DZb1RZlV.js} +1 -1
- package/dist/assets/{SettingsWindow-axr7ewCq.js → SettingsWindow-DRQK5GqM.js} +1 -1
- package/dist/assets/{TerminalView-B_5ml6J4.js → TerminalView-Ynxbvy5-.js} +1 -1
- package/dist/assets/{ToolOutputDialog-CR5Ifk2Z.js → ToolOutputDialog-Cav2sifO.js} +1 -1
- package/dist/assets/{appThemeRegistry-ih8IT_2E.js → appThemeRegistry-C_UnqMeo.js} +2 -2
- package/dist/assets/{clipboard-Cm4VHQ3n.js → clipboard-BnElHSnO.js} +1 -1
- package/dist/assets/{context-menu-DyfEPvnE.js → context-menu-D00De8AC.js} +1 -1
- package/dist/assets/{dialog-ruyemRYh.js → dialog-COrLBm9O.js} +1 -1
- package/dist/assets/{es-W_z2kSYf.js → es-DIVYdoxi.js} +1 -1
- package/dist/assets/{fr-MYuf2QNQ.js → fr-CNTlVypq.js} +1 -1
- package/dist/assets/{gitApi-BrvHOAWU.js → gitApi-BSovla63.js} +1 -1
- package/dist/assets/{insertionBoundaries-BW7YHXi6.js → insertionBoundaries-BzxCFqY7.js} +1 -1
- package/dist/assets/{ja-DBkl4NXl.js → ja-C_eW5ZM8.js} +1 -1
- package/dist/assets/{ko-DwSsHsxa.js → ko-CE3cOv7v.js} +1 -1
- package/dist/assets/{main-BBKtt1Y9.js → main-BaqqdTzl.js} +4 -4
- package/dist/assets/{main-fdHtRqqY.js → main-yYt_TrZD.js} +2 -2
- package/dist/assets/{miniChat-AFFBcZuq.js → miniChat-DeS-YbIL.js} +2 -2
- package/dist/assets/{mobile-BjoYS6yy.js → mobile-Cg1Lw3I_.js} +2 -2
- package/dist/assets/{multirun-BUCHfy2k.js → multirun-CjmiAnFl.js} +1 -1
- package/dist/assets/{pl-BGLpnSSE.js → pl-BZKZOE99.js} +1 -1
- package/dist/assets/{projectMeta-GWaQA3Nh.js → projectMeta-BQJHLcsI.js} +1 -1
- package/dist/assets/{pt-BR-DuEh49lp.js → pt-BR-DHDgp8S3.js} +1 -1
- package/dist/assets/{radio-DLT8WkB1.js → radio-BpKBzfEa.js} +1 -1
- package/dist/assets/{renderElectronMiniChatApp-mk15OpZl.js → renderElectronMiniChatApp-DNRlOH_8.js} +2 -2
- package/dist/assets/renderMobileApp-zMbsHo-j.js +23 -0
- package/dist/assets/{runtimeEndpointReset-C-zHA9mH.js → runtimeEndpointReset-_cbs-WPN.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-B8ZHvoDm.js → sortable-tabs-strip-CQ6Jhk4W.js} +1 -1
- package/dist/assets/src-DcPZ_rWS.css +2 -0
- package/dist/assets/{store-Cb8Q6EtD.js → store-CUWqzRt7.js} +2 -2
- package/dist/assets/{uk-CUoSz-Q8.js → uk-CfQK6cz1.js} +1 -1
- package/dist/assets/{useAppFontEffects-BmoxCfbH.js → useAppFontEffects-CvGJXvOL.js} +2 -2
- package/dist/assets/{useDesktopWindowControlsLayout-BLOLzV-B.js → useDesktopWindowControlsLayout-BztJfpeS.js} +1 -1
- package/dist/assets/{useEffectiveDirectory-DYoU9MD8.js → useEffectiveDirectory-BNxfJJ4B.js} +1 -1
- package/dist/assets/{useMobileNavigationStore-DpFAK6sE.js → useMobileNavigationStore-te_AdqSf.js} +5 -5
- package/dist/assets/{useSessionAutoCleanup-B5wYiGWJ.js → useSessionAutoCleanup-DDcW7p4V.js} +1 -1
- package/dist/assets/{useWorkerHighlightedLines-DkDOqMcg.js → useWorkerHighlightedLines-HT_ad5F8.js} +1 -1
- package/dist/assets/{zh-CN-CJP5FWCK.js → zh-CN-DvY2fKWp.js} +1 -1
- package/dist/assets/{zh-TW-B_2h1yuo.js → zh-TW-eWe0lm3-.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/mini-chat.html +2 -2
- package/dist/mobile.html +2 -2
- package/package.json +1 -1
- package/server/index.js +7 -0
- 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/session-index/DOCUMENTATION.md +6 -5
- package/server/lib/session-index/service.js +1 -0
- package/server/lib/session-index/service.test.js +7 -2
- package/server/lib/session-title/DOCUMENTATION.md +3 -2
- package/server/lib/session-title/runtime.js +1 -0
- package/server/lib/session-title/runtime.test.js +6 -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/renderMobileApp-1_-KjQ6K.js +0 -23
- package/dist/assets/src-C8jn0x69.css +0 -2
|
@@ -1,19 +1,40 @@
|
|
|
1
|
-
import { describe, it, expect } from '
|
|
2
|
-
import {
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
resolveSmallModel,
|
|
4
|
+
parseModelRef,
|
|
5
|
+
isUsableAuthEntry,
|
|
6
|
+
rankSmallModelCandidates,
|
|
7
|
+
compareSmallModelCandidates,
|
|
8
|
+
} from './resolve.js';
|
|
3
9
|
|
|
4
10
|
const catalog = {
|
|
5
11
|
google: {
|
|
6
12
|
id: 'google',
|
|
7
13
|
models: {
|
|
8
|
-
'gemini-2.5-flash': {
|
|
9
|
-
|
|
14
|
+
'gemini-2.5-flash': {
|
|
15
|
+
id: 'gemini-2.5-flash',
|
|
16
|
+
family: 'gemini-flash',
|
|
17
|
+
release_date: '2025-06-01',
|
|
18
|
+
cost: { input: 0.15, output: 0.6 },
|
|
19
|
+
},
|
|
20
|
+
'gemini-2.0-flash': {
|
|
21
|
+
id: 'gemini-2.0-flash',
|
|
22
|
+
family: 'gemini-flash',
|
|
23
|
+
release_date: '2024-12-01',
|
|
24
|
+
cost: { input: 0.1, output: 0.4 },
|
|
25
|
+
},
|
|
10
26
|
'gemini-2.5-pro': { id: 'gemini-2.5-pro', family: 'gemini-pro', release_date: '2025-06-01' },
|
|
11
27
|
},
|
|
12
28
|
},
|
|
13
29
|
anthropic: {
|
|
14
30
|
id: 'anthropic',
|
|
15
31
|
models: {
|
|
16
|
-
'claude-haiku-4-5': {
|
|
32
|
+
'claude-haiku-4-5': {
|
|
33
|
+
id: 'claude-haiku-4-5',
|
|
34
|
+
family: 'claude-haiku',
|
|
35
|
+
release_date: '2025-10-01',
|
|
36
|
+
cost: { input: 1, output: 5 },
|
|
37
|
+
},
|
|
17
38
|
'claude-sonnet-4-5': { id: 'claude-sonnet-4-5', family: 'claude-sonnet', release_date: '2025-09-01' },
|
|
18
39
|
},
|
|
19
40
|
},
|
|
@@ -77,7 +98,7 @@ describe('resolveSmallModel', () => {
|
|
|
77
98
|
expect(result).toEqual({ providerID: 'openai', modelID: 'gpt-4o-mini', source: 'config' });
|
|
78
99
|
});
|
|
79
100
|
|
|
80
|
-
it('scans authenticated providers by family priority, newest
|
|
101
|
+
it('scans authenticated providers by family priority, cheapest then newest within family', () => {
|
|
81
102
|
const result = resolveSmallModel({
|
|
82
103
|
auth: {
|
|
83
104
|
google: { type: 'api', key: 'g-key' },
|
|
@@ -86,7 +107,8 @@ describe('resolveSmallModel', () => {
|
|
|
86
107
|
catalog,
|
|
87
108
|
configSmallModel: null,
|
|
88
109
|
});
|
|
89
|
-
|
|
110
|
+
// gemini-flash outranks other families; within family cheaper input wins.
|
|
111
|
+
expect(result).toEqual({ providerID: 'google', modelID: 'gemini-2.0-flash', source: 'family-scan' });
|
|
90
112
|
});
|
|
91
113
|
|
|
92
114
|
it('skips providers without a usable credential', () => {
|
|
@@ -135,7 +157,7 @@ describe('resolveSmallModel', () => {
|
|
|
135
157
|
configSmallModel: null,
|
|
136
158
|
preferredProviderID: 'anthropic',
|
|
137
159
|
});
|
|
138
|
-
expect(result).toEqual({ providerID: 'google', modelID: 'gemini-2.
|
|
160
|
+
expect(result).toEqual({ providerID: 'google', modelID: 'gemini-2.0-flash', source: 'family-scan' });
|
|
139
161
|
});
|
|
140
162
|
|
|
141
163
|
it('never uses a session provider without a login (opencode free models)', () => {
|
|
@@ -163,7 +185,7 @@ describe('resolveSmallModel', () => {
|
|
|
163
185
|
expect(result).toBeNull();
|
|
164
186
|
});
|
|
165
187
|
|
|
166
|
-
it('
|
|
188
|
+
it('stays on the preferred provider via keyword scan instead of switching providers', () => {
|
|
167
189
|
const result = resolveSmallModel({
|
|
168
190
|
auth: {
|
|
169
191
|
'opencode-go': { type: 'api', key: 'oc-key' },
|
|
@@ -181,7 +203,9 @@ describe('resolveSmallModel', () => {
|
|
|
181
203
|
preferredProviderID: 'opencode-go',
|
|
182
204
|
preferredModelID: 'deepseek-v4-flash',
|
|
183
205
|
});
|
|
184
|
-
|
|
206
|
+
// No gemini-flash/gpt-nano/claude-haiku family, but "flash" keyword matches
|
|
207
|
+
// within the preferred provider — never silently switch to openai oauth.
|
|
208
|
+
expect(result).toEqual({ providerID: 'opencode-go', modelID: 'deepseek-v4-flash', source: 'keyword-scan' });
|
|
185
209
|
});
|
|
186
210
|
|
|
187
211
|
it('falls back to the session model itself when nothing resolves', () => {
|
|
@@ -194,4 +218,46 @@ describe('resolveSmallModel', () => {
|
|
|
194
218
|
});
|
|
195
219
|
expect(result).toEqual({ providerID: 'mistral', modelID: 'mistral-large-latest', source: 'session-model' });
|
|
196
220
|
});
|
|
221
|
+
|
|
222
|
+
it('picks keyword+cheapest catalog models when no family match exists', () => {
|
|
223
|
+
const result = resolveSmallModel({
|
|
224
|
+
auth: {
|
|
225
|
+
codebuddy: { type: 'api', key: 'cb-key' },
|
|
226
|
+
deepseek: { type: 'api', key: 'ds-key' },
|
|
227
|
+
},
|
|
228
|
+
catalog: {
|
|
229
|
+
codebuddy: {
|
|
230
|
+
id: 'codebuddy',
|
|
231
|
+
models: {
|
|
232
|
+
'codebuddy-pro': { id: 'codebuddy-pro', release_date: '2026-01-01', cost: { input: 0.01 } },
|
|
233
|
+
'codebuddy-mini': { id: 'codebuddy-mini', release_date: '2025-06-01', cost: { input: 0.5 } },
|
|
234
|
+
'codebuddy-lite': { id: 'codebuddy-lite', release_date: '2025-06-01', cost: { input: 0.05 } },
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
deepseek: {
|
|
238
|
+
id: 'deepseek',
|
|
239
|
+
models: {
|
|
240
|
+
'deepseek-chat': { id: 'deepseek-chat', release_date: '2025-01-01', cost: { input: 0.14 } },
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
configSmallModel: null,
|
|
245
|
+
});
|
|
246
|
+
// mini outranks lite/chat; within mini cheaper input would win (only one mini).
|
|
247
|
+
expect(result).toEqual({ providerID: 'codebuddy', modelID: 'codebuddy-mini', source: 'keyword-scan' });
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('ranks missing cost after priced models and prefers newer release_date on ties', () => {
|
|
251
|
+
const ranked = rankSmallModelCandidates({
|
|
252
|
+
'a-mini': { id: 'a-mini', release_date: '2024-01-01', cost: { input: 1 } },
|
|
253
|
+
'b-mini': { id: 'b-mini', release_date: '2025-01-01', cost: { input: 1 } },
|
|
254
|
+
'c-mini': { id: 'c-mini', release_date: '2026-01-01' },
|
|
255
|
+
'd-flash': { id: 'd-flash', release_date: '2023-01-01', cost: { input: 2 } },
|
|
256
|
+
});
|
|
257
|
+
expect(ranked.map((m) => m.id)).toEqual(['d-flash', 'b-mini', 'a-mini', 'c-mini']);
|
|
258
|
+
expect(compareSmallModelCandidates(
|
|
259
|
+
{ id: 'x', cost: { input: 1 }, release_date: '2024-01-01' },
|
|
260
|
+
{ id: 'y', cost: { input: 2 }, release_date: '2026-01-01' },
|
|
261
|
+
)).toBeLessThan(0);
|
|
262
|
+
});
|
|
197
263
|
});
|
|
@@ -19,8 +19,14 @@ vi.mock('./call.js', () => ({
|
|
|
19
19
|
callSmallModel: vi.fn(async () => 'Generated summary'),
|
|
20
20
|
}));
|
|
21
21
|
|
|
22
|
+
vi.mock('./opencode-session.js', () => ({
|
|
23
|
+
generateViaOpenCodeSession: vi.fn(async () => 'Session path summary'),
|
|
24
|
+
stop: vi.fn(async () => {}),
|
|
25
|
+
}));
|
|
26
|
+
|
|
22
27
|
const { createSmallModelService } = await import('./index.js');
|
|
23
28
|
const { callSmallModel } = await import('./call.js');
|
|
29
|
+
const { generateViaOpenCodeSession } = await import('./opencode-session.js');
|
|
24
30
|
const { readAuthFile } = await import('../opencode/auth.js');
|
|
25
31
|
|
|
26
32
|
const createService = (catalog = {}) => createSmallModelService({
|
|
@@ -32,6 +38,7 @@ const createService = (catalog = {}) => createSmallModelService({
|
|
|
32
38
|
describe('summary AI settings', () => {
|
|
33
39
|
beforeEach(async () => {
|
|
34
40
|
vi.mocked(callSmallModel).mockClear();
|
|
41
|
+
vi.mocked(generateViaOpenCodeSession).mockClear();
|
|
35
42
|
vi.mocked(readAuthFile).mockReturnValue({});
|
|
36
43
|
await fsPromises.writeFile(path.join(tempRoot, 'settings.json'), JSON.stringify({
|
|
37
44
|
summaryModelMode: 'custom',
|
|
@@ -195,10 +202,11 @@ describe('summary AI settings', () => {
|
|
|
195
202
|
expect(await listCallableModels()).toEqual({ openai: ['gpt-5.4-mini'] });
|
|
196
203
|
});
|
|
197
204
|
|
|
198
|
-
it('
|
|
205
|
+
it('lists logged-in providers with catalog models even without api.url', async () => {
|
|
199
206
|
vi.mocked(readAuthFile).mockReturnValue({
|
|
200
207
|
mistral: { type: 'api', key: 'mistral-key' },
|
|
201
208
|
deepseek: { type: 'api', key: 'deepseek-key' },
|
|
209
|
+
emptyplug: { type: 'api', key: 'empty-key' },
|
|
202
210
|
});
|
|
203
211
|
|
|
204
212
|
const catalog = {
|
|
@@ -219,10 +227,59 @@ describe('summary AI settings', () => {
|
|
|
219
227
|
},
|
|
220
228
|
},
|
|
221
229
|
},
|
|
230
|
+
emptyplug: {
|
|
231
|
+
id: 'emptyplug',
|
|
232
|
+
name: 'Empty',
|
|
233
|
+
models: {},
|
|
234
|
+
},
|
|
222
235
|
};
|
|
223
236
|
|
|
224
237
|
const { listCallableProviders, listCallableModels } = createService(catalog);
|
|
225
|
-
expect(await listCallableProviders()).toEqual(['deepseek']);
|
|
226
|
-
expect(await listCallableModels()).toEqual({
|
|
238
|
+
expect((await listCallableProviders()).sort()).toEqual(['deepseek', 'mistral']);
|
|
239
|
+
expect(await listCallableModels()).toEqual({
|
|
240
|
+
mistral: ['mistral-small'],
|
|
241
|
+
deepseek: ['deepseek-chat'],
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('routes non-dedicated providers through the OpenCode session path', async () => {
|
|
246
|
+
vi.mocked(readAuthFile).mockReturnValue({
|
|
247
|
+
codebuddy: { type: 'api', key: 'codebuddy-key' },
|
|
248
|
+
});
|
|
249
|
+
await fsPromises.writeFile(path.join(tempRoot, 'settings.json'), JSON.stringify({
|
|
250
|
+
summaryModelMode: 'provider',
|
|
251
|
+
summaryProviderID: 'codebuddy',
|
|
252
|
+
summaryModelID: 'codebuddy-flash',
|
|
253
|
+
}), 'utf8');
|
|
254
|
+
|
|
255
|
+
const catalog = {
|
|
256
|
+
codebuddy: {
|
|
257
|
+
id: 'codebuddy',
|
|
258
|
+
name: 'CodeBuddy',
|
|
259
|
+
models: {
|
|
260
|
+
'codebuddy-flash': { id: 'codebuddy-flash' },
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const { generateSmallModelText } = createService(catalog);
|
|
266
|
+
const result = await generateSmallModelText({
|
|
267
|
+
purpose: 'commit',
|
|
268
|
+
prompt: 'Diff content',
|
|
269
|
+
system: 'Fallback system prompt',
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
expect(callSmallModel).not.toHaveBeenCalled();
|
|
273
|
+
expect(generateViaOpenCodeSession).toHaveBeenCalledWith(expect.objectContaining({
|
|
274
|
+
providerID: 'codebuddy',
|
|
275
|
+
modelID: 'codebuddy-flash',
|
|
276
|
+
purpose: 'commit',
|
|
277
|
+
}));
|
|
278
|
+
expect(result).toEqual({
|
|
279
|
+
text: 'Session path summary',
|
|
280
|
+
providerID: 'codebuddy',
|
|
281
|
+
modelID: 'codebuddy-flash',
|
|
282
|
+
source: 'summary-provider',
|
|
283
|
+
});
|
|
227
284
|
});
|
|
228
285
|
});
|