@robhowley/pi-openrouter 0.9.0 → 0.9.1
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/README.md +39 -4
- package/extensions/openrouter/__tests__/cache.test.ts +769 -0
- package/extensions/openrouter/__tests__/client.test.ts +333 -15
- package/extensions/openrouter/__tests__/commands.test.ts +816 -0
- package/extensions/openrouter/__tests__/fixtures.ts +140 -1
- package/extensions/openrouter/__tests__/format.test.ts +19 -0
- package/extensions/openrouter/__tests__/hooks.test.ts +276 -0
- package/extensions/openrouter/__tests__/index.test.ts +112 -363
- package/extensions/openrouter/__tests__/local-usage.test.ts +777 -0
- package/extensions/openrouter/__tests__/normalizers.test.ts +288 -0
- package/extensions/openrouter/__tests__/overlay.test.ts +225 -0
- package/extensions/openrouter/__tests__/session-state.test.ts +233 -0
- package/extensions/openrouter/__tests__/session.test.ts +44 -43
- package/extensions/openrouter/account-client.ts +11 -61
- package/extensions/openrouter/cache.ts +203 -91
- package/extensions/openrouter/client.ts +49 -3
- package/extensions/openrouter/commands.ts +555 -0
- package/extensions/openrouter/format.ts +7 -4
- package/extensions/openrouter/hooks.ts +229 -0
- package/extensions/openrouter/index.ts +13 -990
- package/extensions/openrouter/local-usage.ts +145 -22
- package/extensions/openrouter/models/__tests__/cache.test.ts +63 -2
- package/extensions/openrouter/models/__tests__/mapper.test.ts +29 -0
- package/extensions/openrouter/models/__tests__/override-commands.test.ts +668 -0
- package/extensions/openrouter/models/__tests__/sync.test.ts +156 -4
- package/extensions/openrouter/models/cache.ts +27 -2
- package/extensions/openrouter/models/mapper.ts +35 -69
- package/extensions/openrouter/models/override-commands.ts +434 -0
- package/extensions/openrouter/models/skip-hints.ts +19 -0
- package/extensions/openrouter/models/sync.ts +22 -10
- package/extensions/openrouter/models/types.ts +2 -1
- package/extensions/openrouter/normalizers.ts +128 -0
- package/extensions/openrouter/overlay.ts +19 -8
- package/extensions/openrouter/session-state.ts +110 -0
- package/extensions/openrouter/session.ts +16 -0
- package/extensions/openrouter/types.ts +28 -9
- package/package.json +1 -1
|
@@ -1,414 +1,163 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
const { loadModelOverrides, saveModelOverrides } = vi.hoisted(() => ({
|
|
5
|
-
loadModelOverrides: vi.fn<() => Promise<ModelOverridesFile>>(),
|
|
6
|
-
saveModelOverrides: vi.fn(),
|
|
7
|
-
}));
|
|
8
|
-
|
|
9
|
-
vi.mock('../models/overrides.js', async (importOriginal) => {
|
|
10
|
-
const actual = await importOriginal<typeof import('../models/overrides.js')>();
|
|
11
|
-
return {
|
|
12
|
-
...actual,
|
|
13
|
-
loadModelOverrides,
|
|
14
|
-
saveModelOverrides,
|
|
15
|
-
};
|
|
16
|
-
});
|
|
2
|
+
import { createSessionCtx, createOpenRouterRequest, THROW_SESSION_ID } from './fixtures.js';
|
|
17
3
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
handleModelOverrideList,
|
|
22
|
-
handleModelOverrideSet,
|
|
23
|
-
parseScopedAssignment,
|
|
24
|
-
} from '../index.js';
|
|
25
|
-
|
|
26
|
-
const emptyOverrides = (): ModelOverridesFile => ({ version: 1, overrides: {} });
|
|
27
|
-
|
|
28
|
-
describe('parseScopedAssignment', () => {
|
|
29
|
-
it('parses thinking shorthand aliases', () => {
|
|
30
|
-
expect(parseScopedAssignment('thinking.high=high')).toEqual({
|
|
31
|
-
fullPath: 'thinkingLevelMap.high',
|
|
32
|
-
value: 'high',
|
|
33
|
-
});
|
|
4
|
+
describe('addSessionIdToOpenRouterRequest', () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
vi.resetModules();
|
|
34
7
|
});
|
|
35
8
|
|
|
36
|
-
it('
|
|
37
|
-
|
|
38
|
-
fullPath: 'thinkingLevelMap.xhigh',
|
|
39
|
-
value: 'max',
|
|
40
|
-
});
|
|
41
|
-
});
|
|
9
|
+
it('should add session_id to OpenRouter requests', async () => {
|
|
10
|
+
const { addSessionIdToOpenRouterRequest } = await import('../index.js');
|
|
42
11
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
fullPath: 'thinkingLevelMap.off',
|
|
46
|
-
value: null,
|
|
47
|
-
});
|
|
48
|
-
});
|
|
12
|
+
const mockCtx = createSessionCtx('stable-session-123');
|
|
13
|
+
const mockEvent = createOpenRouterRequest();
|
|
49
14
|
|
|
50
|
-
|
|
51
|
-
expect(parseScopedAssignment('contextWindow=64000')).toEqual({
|
|
52
|
-
fullPath: 'contextWindow',
|
|
53
|
-
value: 64000,
|
|
54
|
-
});
|
|
55
|
-
expect(parseScopedAssignment('maxTokens=8192')).toEqual({
|
|
56
|
-
fullPath: 'maxTokens',
|
|
57
|
-
value: 8192,
|
|
58
|
-
});
|
|
59
|
-
expect(parseScopedAssignment('reasoning=false')).toEqual({
|
|
60
|
-
fullPath: 'reasoning',
|
|
61
|
-
value: false,
|
|
62
|
-
});
|
|
63
|
-
});
|
|
15
|
+
const result = addSessionIdToOpenRouterRequest(mockEvent, mockCtx);
|
|
64
16
|
|
|
65
|
-
|
|
66
|
-
expect(
|
|
67
|
-
expect(
|
|
68
|
-
expect(parseScopedAssignment('contextWindow=large')).toBeNull();
|
|
69
|
-
expect(parseScopedAssignment('reasoning=yes')).toBeNull();
|
|
17
|
+
expect(result).toBeDefined();
|
|
18
|
+
expect(result?.['session_id']).toBe('pi:stable-session-123');
|
|
19
|
+
expect(result?.['model']).toBe('openrouter/anthropic/claude-sonnet-4');
|
|
70
20
|
});
|
|
71
|
-
});
|
|
72
21
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const target: Record<string, unknown> = {};
|
|
22
|
+
it('should return same session_id for multiple OpenRouter requests in same session', async () => {
|
|
23
|
+
const { addSessionIdToOpenRouterRequest } = await import('../index.js');
|
|
76
24
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
expect(target).toEqual({
|
|
81
|
-
thinkingLevelMap: { high: 'high' },
|
|
82
|
-
contextWindow: 64000,
|
|
25
|
+
const mockCtx = createSessionCtx('stable-session-123');
|
|
26
|
+
const mockEvent1 = createOpenRouterRequest({
|
|
27
|
+
payload: { model: 'openrouter/model-1', messages: [] },
|
|
83
28
|
});
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
it('replaces non-object intermediate values with objects', () => {
|
|
87
|
-
const target: Record<string, unknown> = { thinkingLevelMap: 'bad' };
|
|
88
|
-
|
|
89
|
-
applyNestedValue(target, 'thinkingLevelMap.high', 'high');
|
|
90
|
-
|
|
91
|
-
expect(target).toEqual({ thinkingLevelMap: { high: 'high' } });
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe('handleModelOverrideSet', () => {
|
|
96
|
-
beforeEach(() => {
|
|
97
|
-
loadModelOverrides.mockReset();
|
|
98
|
-
saveModelOverrides.mockReset();
|
|
99
|
-
loadModelOverrides.mockResolvedValue(emptyOverrides());
|
|
100
|
-
saveModelOverrides.mockResolvedValue(undefined);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('returns usage when no model id is provided', async () => {
|
|
104
|
-
const result = await handleModelOverrideSet('', emptyOverrides());
|
|
105
|
-
|
|
106
|
-
expect(result.success).toBe(false);
|
|
107
|
-
expect(result.message).toContain('Usage: /openrouter model-override-set');
|
|
108
|
-
expect(saveModelOverrides).not.toHaveBeenCalled();
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('rejects model IDs without provider/model format', async () => {
|
|
112
|
-
const result = await handleModelOverrideSet(
|
|
113
|
-
'not-a-model contextWindow=64000',
|
|
114
|
-
emptyOverrides(),
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
expect(result).toEqual({
|
|
118
|
-
success: false,
|
|
119
|
-
message:
|
|
120
|
-
'Invalid model ID format: "not-a-model"\nExpected format: provider/model (e.g., "deepseek/deepseek-v4-pro")',
|
|
29
|
+
const mockEvent2 = createOpenRouterRequest({
|
|
30
|
+
payload: { model: 'openrouter/model-2', messages: [] },
|
|
121
31
|
});
|
|
122
|
-
expect(saveModelOverrides).not.toHaveBeenCalled();
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('rejects missing field assignments', async () => {
|
|
126
|
-
const result = await handleModelOverrideSet('test/model --dry-run', emptyOverrides());
|
|
127
32
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
expect(saveModelOverrides).not.toHaveBeenCalled();
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('rejects invalid assignments', async () => {
|
|
134
|
-
const result = await handleModelOverrideSet('test/model unknown=value', emptyOverrides());
|
|
33
|
+
const result1 = addSessionIdToOpenRouterRequest(mockEvent1, mockCtx);
|
|
34
|
+
const result2 = addSessionIdToOpenRouterRequest(mockEvent2, mockCtx);
|
|
135
35
|
|
|
136
|
-
expect(
|
|
137
|
-
expect(
|
|
138
|
-
expect(
|
|
36
|
+
expect(result1?.['session_id']).toBe('pi:stable-session-123');
|
|
37
|
+
expect(result2?.['session_id']).toBe('pi:stable-session-123');
|
|
38
|
+
expect(result1?.['session_id']).toBe(result2?.['session_id']);
|
|
139
39
|
});
|
|
140
40
|
|
|
141
|
-
it('
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
const result = await handleModelOverrideSet('test/model contextWindow=64000', userOverrides);
|
|
41
|
+
it('should not overwrite existing session_id in payload', async () => {
|
|
42
|
+
const { addSessionIdToOpenRouterRequest } = await import('../index.js');
|
|
145
43
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
},
|
|
44
|
+
const mockCtx = createSessionCtx('new-session');
|
|
45
|
+
const mockEvent = createOpenRouterRequest({
|
|
46
|
+
payload: {
|
|
47
|
+
model: 'openrouter/anthropic/claude-sonnet-4',
|
|
48
|
+
messages: [],
|
|
49
|
+
session_id: 'existing-session-id',
|
|
153
50
|
},
|
|
154
51
|
});
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it('stores sparse thinking overrides without nulling unrelated levels', async () => {
|
|
158
|
-
const userOverrides = emptyOverrides();
|
|
159
52
|
|
|
160
|
-
const result =
|
|
53
|
+
const result = addSessionIdToOpenRouterRequest(mockEvent, mockCtx);
|
|
161
54
|
|
|
162
|
-
expect(result
|
|
163
|
-
expect(
|
|
164
|
-
version: 1,
|
|
165
|
-
overrides: {
|
|
166
|
-
'test/model': {
|
|
167
|
-
thinkingLevelMap: {
|
|
168
|
-
high: 'high',
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
});
|
|
55
|
+
expect(result).toBeUndefined();
|
|
56
|
+
expect(mockEvent['payload']?.['session_id']).toBe('existing-session-id');
|
|
173
57
|
});
|
|
174
58
|
|
|
175
|
-
it('
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
const result = await handleModelOverrideSet(
|
|
179
|
-
'test/model thinking.high=high thinking.xhigh=max thinking.off=null contextWindow=64000 maxTokens=8192 reasoning=true',
|
|
180
|
-
userOverrides,
|
|
181
|
-
);
|
|
59
|
+
it('should not tag non-OpenRouter requests', async () => {
|
|
60
|
+
const { addSessionIdToOpenRouterRequest } = await import('../index.js');
|
|
182
61
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
thinkingLevelMap: {
|
|
190
|
-
high: 'high',
|
|
191
|
-
xhigh: 'max',
|
|
192
|
-
off: null,
|
|
193
|
-
},
|
|
194
|
-
contextWindow: 64000,
|
|
195
|
-
maxTokens: 8192,
|
|
196
|
-
reasoning: true,
|
|
197
|
-
},
|
|
62
|
+
const mockCtx = createSessionCtx('my-session');
|
|
63
|
+
const mockEvent = createOpenRouterRequest({
|
|
64
|
+
provider: 'anthropic',
|
|
65
|
+
payload: {
|
|
66
|
+
model: 'claude-sonnet-4',
|
|
67
|
+
messages: [],
|
|
198
68
|
},
|
|
199
69
|
});
|
|
200
|
-
});
|
|
201
70
|
|
|
202
|
-
|
|
203
|
-
const userOverrides: ModelOverridesFile = {
|
|
204
|
-
version: 1,
|
|
205
|
-
overrides: {
|
|
206
|
-
'test/model': {
|
|
207
|
-
thinkingLevelMap: { minimal: null, high: 'old-high' },
|
|
208
|
-
contextWindow: 128000,
|
|
209
|
-
},
|
|
210
|
-
},
|
|
211
|
-
};
|
|
71
|
+
const result = addSessionIdToOpenRouterRequest(mockEvent, mockCtx);
|
|
212
72
|
|
|
213
|
-
|
|
214
|
-
'test/model thinking.high=high maxTokens=8192',
|
|
215
|
-
userOverrides,
|
|
216
|
-
);
|
|
217
|
-
|
|
218
|
-
expect(result.success).toBe(true);
|
|
219
|
-
expect(saveModelOverrides).toHaveBeenCalledWith({
|
|
220
|
-
version: 1,
|
|
221
|
-
overrides: {
|
|
222
|
-
'test/model': {
|
|
223
|
-
thinkingLevelMap: { minimal: null, high: 'high' },
|
|
224
|
-
contextWindow: 128000,
|
|
225
|
-
maxTokens: 8192,
|
|
226
|
-
},
|
|
227
|
-
},
|
|
228
|
-
});
|
|
73
|
+
expect(result).toBeUndefined();
|
|
229
74
|
});
|
|
230
75
|
|
|
231
|
-
it('
|
|
232
|
-
const
|
|
233
|
-
saveModelOverrides.mockRejectedValue(new Error('disk full'));
|
|
234
|
-
|
|
235
|
-
const result = await handleModelOverrideSet('test/model contextWindow=64000', userOverrides);
|
|
76
|
+
it('should fail open when payload is missing', async () => {
|
|
77
|
+
const { addSessionIdToOpenRouterRequest } = await import('../index.js');
|
|
236
78
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
describe('handleModelOverrideClear', () => {
|
|
245
|
-
beforeEach(() => {
|
|
246
|
-
saveModelOverrides.mockReset();
|
|
247
|
-
saveModelOverrides.mockResolvedValue(undefined);
|
|
248
|
-
});
|
|
79
|
+
const mockCtx = createSessionCtx('my-session');
|
|
80
|
+
const mockEvent = createOpenRouterRequest();
|
|
81
|
+
delete mockEvent['payload'];
|
|
249
82
|
|
|
250
|
-
|
|
251
|
-
const result = await handleModelOverrideClear('', emptyOverrides());
|
|
83
|
+
const result = addSessionIdToOpenRouterRequest(mockEvent, mockCtx);
|
|
252
84
|
|
|
253
|
-
expect(result).
|
|
254
|
-
success: false,
|
|
255
|
-
message: 'Usage: /openrouter model-override-clear <model-id>',
|
|
256
|
-
});
|
|
257
|
-
expect(saveModelOverrides).not.toHaveBeenCalled();
|
|
85
|
+
expect(result).toBeUndefined();
|
|
258
86
|
});
|
|
259
87
|
|
|
260
|
-
it('
|
|
261
|
-
const
|
|
88
|
+
it('should generate fallback UUID when session manager throws', async () => {
|
|
89
|
+
const { addSessionIdToOpenRouterRequest } = await import('../index.js');
|
|
262
90
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
91
|
+
const mockCtx = createSessionCtx(THROW_SESSION_ID);
|
|
92
|
+
const mockEvent = createOpenRouterRequest({
|
|
93
|
+
payload: {
|
|
94
|
+
model: 'openrouter/model',
|
|
95
|
+
messages: [],
|
|
96
|
+
},
|
|
266
97
|
});
|
|
267
|
-
expect(saveModelOverrides).not.toHaveBeenCalled();
|
|
268
|
-
});
|
|
269
98
|
|
|
270
|
-
|
|
271
|
-
const result = await handleModelOverrideClear('test/model', emptyOverrides());
|
|
99
|
+
const result = addSessionIdToOpenRouterRequest(mockEvent, mockCtx);
|
|
272
100
|
|
|
273
|
-
expect(result).
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
expect(saveModelOverrides).not.toHaveBeenCalled();
|
|
101
|
+
expect(result).toBeDefined();
|
|
102
|
+
expect(result?.['session_id']).toMatch(
|
|
103
|
+
/^pi:[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
|
|
104
|
+
);
|
|
278
105
|
});
|
|
279
106
|
|
|
280
|
-
it('
|
|
281
|
-
const
|
|
282
|
-
version: 1,
|
|
283
|
-
overrides: {
|
|
284
|
-
'test/model': { contextWindow: 64000 },
|
|
285
|
-
'other/model': { maxTokens: 8192 },
|
|
286
|
-
},
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
const result = await handleModelOverrideClear('test/model', userOverrides);
|
|
290
|
-
|
|
291
|
-
expect(result).toEqual({
|
|
292
|
-
success: true,
|
|
293
|
-
message: 'Cleared all overrides for test/model',
|
|
294
|
-
modelId: 'test/model',
|
|
295
|
-
});
|
|
296
|
-
expect(saveModelOverrides).toHaveBeenCalledWith({
|
|
297
|
-
version: 1,
|
|
298
|
-
overrides: {
|
|
299
|
-
'other/model': { maxTokens: 8192 },
|
|
300
|
-
},
|
|
301
|
-
});
|
|
302
|
-
});
|
|
107
|
+
it('should fail open when payload getter throws', async () => {
|
|
108
|
+
const { addSessionIdToOpenRouterRequest } = await import('../index.js');
|
|
303
109
|
|
|
304
|
-
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
'
|
|
309
|
-
contextWindow: 64000,
|
|
310
|
-
},
|
|
110
|
+
const mockCtx = createSessionCtx('my-session');
|
|
111
|
+
const mockEvent = {
|
|
112
|
+
provider: 'openrouter',
|
|
113
|
+
get payload() {
|
|
114
|
+
throw new Error('Payload getter error');
|
|
311
115
|
},
|
|
312
116
|
};
|
|
313
|
-
saveModelOverrides.mockRejectedValue(new Error('permission denied'));
|
|
314
|
-
|
|
315
|
-
const result = await handleModelOverrideClear('test/model', userOverrides);
|
|
316
117
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
message: 'Failed to clear overrides for test/model: permission denied',
|
|
320
|
-
});
|
|
118
|
+
const result = addSessionIdToOpenRouterRequest(mockEvent, mockCtx);
|
|
119
|
+
expect(result).toBeUndefined();
|
|
321
120
|
});
|
|
322
121
|
});
|
|
323
122
|
|
|
324
|
-
describe('
|
|
123
|
+
describe('default extension export', () => {
|
|
325
124
|
beforeEach(() => {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
)
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
expect(
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
overrides: {
|
|
364
|
-
'test/model': {
|
|
365
|
-
thinkingLevelMap: { off: null, high: 'high', xhigh: 'max' },
|
|
366
|
-
contextWindow: 64000,
|
|
367
|
-
maxTokens: 8192,
|
|
368
|
-
reasoning: true,
|
|
369
|
-
},
|
|
370
|
-
},
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
const result = await handleModelOverrideList('test/model');
|
|
374
|
-
|
|
375
|
-
expect(result).toBe(
|
|
376
|
-
[
|
|
377
|
-
'Overrides for test/model:',
|
|
378
|
-
' thinkingLevelMap:',
|
|
379
|
-
' off: null',
|
|
380
|
-
' high: high',
|
|
381
|
-
' xhigh: max',
|
|
382
|
-
' contextWindow: 64000',
|
|
383
|
-
' maxTokens: 8192',
|
|
384
|
-
' reasoning: true',
|
|
385
|
-
].join('\n'),
|
|
386
|
-
);
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
it('lists all override model IDs and summarizes non-null thinking values', async () => {
|
|
390
|
-
loadModelOverrides.mockResolvedValue({
|
|
391
|
-
version: 1,
|
|
392
|
-
overrides: {
|
|
393
|
-
'thinking/model': {
|
|
394
|
-
thinkingLevelMap: { off: null, high: 'high', xhigh: 'max' },
|
|
395
|
-
},
|
|
396
|
-
'top-level/model': {
|
|
397
|
-
contextWindow: 64000,
|
|
398
|
-
},
|
|
399
|
-
},
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
const result = await handleModelOverrideList('');
|
|
403
|
-
|
|
404
|
-
expect(result).toBe(
|
|
405
|
-
[
|
|
406
|
-
'2 model(s) with overrides:',
|
|
407
|
-
' thinking/model [high=high,xhigh=max]',
|
|
408
|
-
' top-level/model',
|
|
409
|
-
'',
|
|
410
|
-
'Use /openrouter model-override-list <model-id> for details',
|
|
411
|
-
].join('\n'),
|
|
412
|
-
);
|
|
125
|
+
vi.resetModules();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('composes startup cache loading, hook installation, and command registration', async () => {
|
|
129
|
+
const initializeSessionState = vi.fn();
|
|
130
|
+
const loadStartupCacheState = vi
|
|
131
|
+
.fn()
|
|
132
|
+
.mockResolvedValue({ info: { count: 3, age: '1 minute' } });
|
|
133
|
+
const installOpenRouterHooks = vi.fn();
|
|
134
|
+
const registerOpenRouterCommands = vi.fn();
|
|
135
|
+
|
|
136
|
+
vi.doMock('../hooks.js', () => ({
|
|
137
|
+
addSessionIdToOpenRouterRequest: vi.fn(),
|
|
138
|
+
getCurrentSessionId: vi.fn(),
|
|
139
|
+
initializeSessionState,
|
|
140
|
+
loadStartupCacheState,
|
|
141
|
+
installOpenRouterHooks,
|
|
142
|
+
}));
|
|
143
|
+
vi.doMock('../commands.js', () => ({
|
|
144
|
+
registerOpenRouterCommands,
|
|
145
|
+
}));
|
|
146
|
+
|
|
147
|
+
const { default: openRouterExtension } = await import('../index.js');
|
|
148
|
+
const pi = {
|
|
149
|
+
on: vi.fn(),
|
|
150
|
+
registerCommand: vi.fn(),
|
|
151
|
+
registerProvider: vi.fn(),
|
|
152
|
+
} as any;
|
|
153
|
+
|
|
154
|
+
await openRouterExtension(pi);
|
|
155
|
+
|
|
156
|
+
expect(initializeSessionState).toHaveBeenCalledTimes(1);
|
|
157
|
+
expect(loadStartupCacheState).toHaveBeenCalledWith(pi);
|
|
158
|
+
expect(installOpenRouterHooks).toHaveBeenCalledWith(pi, {
|
|
159
|
+
info: { count: 3, age: '1 minute' },
|
|
160
|
+
});
|
|
161
|
+
expect(registerOpenRouterCommands).toHaveBeenCalledWith(pi);
|
|
413
162
|
});
|
|
414
163
|
});
|