@jupyterlite/ai 0.14.0 → 0.16.0

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.
Files changed (64) hide show
  1. package/lib/agent.d.ts +33 -115
  2. package/lib/agent.js +192 -106
  3. package/lib/chat-model-handler.d.ts +9 -11
  4. package/lib/chat-model-handler.js +9 -4
  5. package/lib/chat-model.d.ts +84 -13
  6. package/lib/chat-model.js +214 -136
  7. package/lib/completion/completion-provider.d.ts +2 -3
  8. package/lib/components/completion-status.d.ts +2 -2
  9. package/lib/components/index.d.ts +1 -1
  10. package/lib/components/index.js +1 -1
  11. package/lib/components/model-select.d.ts +3 -3
  12. package/lib/components/save-button.d.ts +31 -0
  13. package/lib/components/save-button.js +41 -0
  14. package/lib/components/tool-select.d.ts +3 -4
  15. package/lib/components/{token-usage-display.d.ts → usage-display.d.ts} +13 -14
  16. package/lib/components/usage-display.js +109 -0
  17. package/lib/diff-manager.d.ts +2 -3
  18. package/lib/index.d.ts +2 -4
  19. package/lib/index.js +186 -28
  20. package/lib/models/settings-model.d.ts +11 -53
  21. package/lib/models/settings-model.js +38 -22
  22. package/lib/providers/built-in-providers.js +22 -36
  23. package/lib/providers/generated-context-windows.d.ts +8 -0
  24. package/lib/providers/generated-context-windows.js +96 -0
  25. package/lib/providers/model-info.d.ts +3 -0
  26. package/lib/providers/model-info.js +58 -0
  27. package/lib/tokens.d.ts +361 -36
  28. package/lib/tokens.js +18 -13
  29. package/lib/tools/commands.d.ts +2 -3
  30. package/lib/widgets/ai-settings.d.ts +3 -5
  31. package/lib/widgets/ai-settings.js +12 -0
  32. package/lib/widgets/main-area-chat.d.ts +2 -3
  33. package/lib/widgets/main-area-chat.js +12 -12
  34. package/lib/widgets/provider-config-dialog.d.ts +1 -2
  35. package/lib/widgets/provider-config-dialog.js +34 -34
  36. package/package.json +17 -10
  37. package/schema/settings-model.json +18 -1
  38. package/src/agent.ts +275 -248
  39. package/src/chat-model-handler.ts +25 -21
  40. package/src/chat-model.ts +307 -196
  41. package/src/completion/completion-provider.ts +7 -4
  42. package/src/components/completion-status.tsx +3 -3
  43. package/src/components/index.ts +1 -1
  44. package/src/components/model-select.tsx +4 -3
  45. package/src/components/save-button.tsx +84 -0
  46. package/src/components/tool-select.tsx +10 -4
  47. package/src/components/usage-display.tsx +208 -0
  48. package/src/diff-manager.ts +4 -4
  49. package/src/index.ts +250 -58
  50. package/src/models/settings-model.ts +46 -88
  51. package/src/providers/built-in-providers.ts +22 -36
  52. package/src/providers/generated-context-windows.ts +102 -0
  53. package/src/providers/model-info.ts +88 -0
  54. package/src/tokens.ts +438 -58
  55. package/src/tools/commands.ts +2 -3
  56. package/src/widgets/ai-settings.tsx +69 -15
  57. package/src/widgets/main-area-chat.ts +18 -15
  58. package/src/widgets/provider-config-dialog.tsx +96 -61
  59. package/style/base.css +17 -195
  60. package/lib/approval-buttons.d.ts +0 -49
  61. package/lib/approval-buttons.js +0 -79
  62. package/lib/components/token-usage-display.js +0 -72
  63. package/src/approval-buttons.ts +0 -115
  64. package/src/components/token-usage-display.tsx +0 -138
@@ -1,74 +1,16 @@
1
1
  import { VDomModel } from '@jupyterlab/ui-components';
2
2
  import { ISettingRegistry } from '@jupyterlab/settingregistry';
3
3
 
4
- const PLUGIN_ID = '@jupyterlite/ai:settings-model';
5
-
6
- export interface IProviderParameters {
7
- temperature?: number;
8
- maxOutputTokens?: number;
9
- maxTurns?: number;
10
- supportsFillInMiddle?: boolean;
11
- useFilterText?: boolean;
12
- }
13
-
14
- export interface IProviderConfig {
15
- id: string;
16
- name: string;
17
- provider: string;
18
- model: string;
19
- apiKey?: string;
20
- baseURL?: string;
21
- headers?: Record<string, string>;
22
- parameters?: IProviderParameters;
23
- customSettings?: Record<string, any>;
24
- [key: string]: any; // Index signature for JupyterLab settings compatibility
25
- }
4
+ import {
5
+ IAIConfig,
6
+ IAISettingsModel,
7
+ IMCPServerConfig,
8
+ IProviderConfig
9
+ } from '../tokens';
26
10
 
27
- export interface IMCPServerConfig {
28
- id: string;
29
- name: string;
30
- url: string;
31
- enabled: boolean;
32
- [key: string]: any; // Index signature for JupyterLab settings compatibility
33
- }
34
-
35
- export interface IAIConfig {
36
- // Whether to use the secrets manager
37
- useSecretsManager: boolean;
38
- // List of configured providers
39
- providers: IProviderConfig[];
40
- // Active provider IDs for different use cases
41
- defaultProvider: string; // Default provider for chat
42
- activeCompleterProvider?: string; // Provider for completions (if different)
43
- // When true, use the same provider for chat and completions
44
- useSameProviderForChatAndCompleter: boolean;
45
- // MCP servers configuration
46
- mcpServers: IMCPServerConfig[];
47
- // Global settings
48
- contextAwareness: boolean;
49
- codeExecution: boolean;
50
- systemPrompt: string;
51
- completionSystemPrompt: string;
52
- toolsEnabled: boolean;
53
- // Chat behavior settings
54
- sendWithShiftEnter: boolean;
55
- // Token usage display setting
56
- showTokenUsage: boolean;
57
- // Commands that require approval before execution
58
- commandsRequiringApproval: string[];
59
- // Commands whose execute_command outputs may auto-render MIME bundles in chat
60
- commandsAutoRenderMimeBundles: string[];
61
- // MIME types that are trusted when auto-rendering execute_command outputs
62
- trustedMimeTypesForAutoRender: string[];
63
- // Diff display settings
64
- showCellDiff: boolean;
65
- showFileDiff: boolean;
66
- diffDisplayMode: 'split' | 'unified';
67
- // Paths to directories containing agent skills
68
- skillsPaths: string[];
69
- }
11
+ const PLUGIN_ID = '@jupyterlite/ai:settings-model';
70
12
 
71
- export class AISettingsModel extends VDomModel {
13
+ export class AISettingsModel extends VDomModel implements IAISettingsModel {
72
14
  private _config: IAIConfig = {
73
15
  useSecretsManager: true,
74
16
  providers: [],
@@ -81,10 +23,12 @@ export class AISettingsModel extends VDomModel {
81
23
  toolsEnabled: true,
82
24
  sendWithShiftEnter: false,
83
25
  showTokenUsage: false,
26
+ showContextUsage: false,
84
27
  showCellDiff: true,
85
28
  showFileDiff: true,
86
29
  diffDisplayMode: 'split',
87
30
  skillsPaths: ['.agents/skills', '_agents/skills'],
31
+ chatBackupDirectory: '',
88
32
  commandsRequiringApproval: [
89
33
  'notebook:restart-run-all',
90
34
  'notebook:run-cell',
@@ -166,6 +110,15 @@ When asked to run code or perform computations, choose the most appropriate appr
166
110
 
167
111
  This means if the user asks you to "calculate the factorial of 100" or "check what library version is installed", run that directly with the jupyterlab-ai-commands kernel execution command rather than creating a new notebook file.
168
112
 
113
+ ## Notebook State and Cell Identity
114
+ When working with an existing notebook, use the notebook's current structure and kernel state as the source of truth.
115
+ - Before changing notebook content or structure, inspect the notebook and any target cells with the relevant notebook commands you have discovered.
116
+ - If the user may have edited the notebook, or if a previous command could have changed it, refresh your view before continuing rather than relying on earlier results.
117
+ - Treat variables from previously executed cells as part of the active kernel state. When the user asks you to work with existing data or variables, use them by name instead of recreating them unless the user asks you to redefine them or the kernel state is unavailable.
118
+ - Be explicit about the kind of cell reference you are using. A visible execution count (for example In [6]), a notebook position, and an internal cell ID or UUID are different identifiers and may not match.
119
+ - When the user identifies a cell by execution count, relative position, or content, verify the target cell from the current notebook contents before editing it or inserting cells relative to it.
120
+ - For relative insertions, anchor the change to the confirmed target cell rather than to empty placeholder or trailing cells unless the user explicitly refers to those cells.
121
+
169
122
  ## Your Approach
170
123
  - **Context-aware**: You understand the user is working in a data science/research environment
171
124
  - **Practical**: You focus on actionable solutions that work in the user's current setup
@@ -209,7 +162,7 @@ Guidelines:
209
162
 
210
163
  ## Multi-Step Task Handling
211
164
  When users request complex tasks, you use the command system to accomplish them:
212
- - For file and notebook operations, use discover_commands with query 'jupyterlab-ai-commands' to find the curated set of AI commands (~17 commands)
165
+ - For file and notebook operations, use discover_commands with query 'jupyterlab-ai-commands' to find the curated set of AI commands (~22 commands)
213
166
  - For other JupyterLab operations (terminal, launcher, UI), use specific keywords like 'terminal', 'launcher', etc.
214
167
  - IMPORTANT: Always use 'jupyterlab-ai-commands' as the query for file/notebook tasks - this returns a focused set instead of 100+ generic commands
215
168
  - For example, to create a notebook with cells:
@@ -251,16 +204,16 @@ Rules:
251
204
  constructor(options: AISettingsModel.IOptions) {
252
205
  super();
253
206
  this._settingRegistry = options.settingRegistry;
254
- this.initializeSettings();
207
+ this._initializeSettings();
255
208
  }
256
209
 
257
- private async initializeSettings(): Promise<void> {
210
+ private async _initializeSettings(): Promise<void> {
258
211
  try {
259
212
  this._settings = await this._settingRegistry.load(PLUGIN_ID);
260
- this.loadFromSettings();
213
+ this._loadFromSettings();
261
214
 
262
215
  // Listen for settings changes
263
- this._settings.changed.connect(this.onSettingsChanged, this);
216
+ this._settings.changed.connect(this._onSettingsChanged, this);
264
217
 
265
218
  this.stateChanged.emit(void 0);
266
219
  } catch (error) {
@@ -269,12 +222,12 @@ Rules:
269
222
  }
270
223
  }
271
224
 
272
- private onSettingsChanged(): void {
273
- this.loadFromSettings();
225
+ private _onSettingsChanged(): void {
226
+ this._loadFromSettings();
274
227
  this.stateChanged.emit(void 0);
275
228
  }
276
229
 
277
- private loadFromSettings(): void {
230
+ private _loadFromSettings(): void {
278
231
  if (!this._settings) {
279
232
  return;
280
233
  }
@@ -334,12 +287,12 @@ Rules:
334
287
  // If this is the first provider, make it active
335
288
  if (this._config.providers.length === 1) {
336
289
  // Save both providers and defaultProvider
337
- await this.saveSetting('providers', this._config.providers);
290
+ await this._saveSetting('providers', this._config.providers);
338
291
  this._config.defaultProvider = id;
339
- await this.saveSetting('defaultProvider', this._config.defaultProvider);
292
+ await this._saveSetting('defaultProvider', this._config.defaultProvider);
340
293
  } else {
341
294
  // Only save providers
342
- await this.saveSetting('providers', this._config.providers);
295
+ await this._saveSetting('providers', this._config.providers);
343
296
  }
344
297
 
345
298
  return id;
@@ -352,18 +305,18 @@ Rules:
352
305
  }
353
306
 
354
307
  this._config.providers.splice(index, 1);
355
- await this.saveSetting('providers', this._config.providers);
308
+ await this._saveSetting('providers', this._config.providers);
356
309
 
357
310
  // If this was the active provider, select a new one
358
311
  if (this._config.defaultProvider === id) {
359
312
  this._config.defaultProvider =
360
313
  this._config.providers.length > 0 ? this._config.providers[0].id : '';
361
- await this.saveSetting('defaultProvider', this._config.defaultProvider);
314
+ await this._saveSetting('defaultProvider', this._config.defaultProvider);
362
315
  }
363
316
 
364
317
  if (this._config.activeCompleterProvider === id) {
365
318
  this._config.activeCompleterProvider = undefined;
366
- await this.saveSetting(
319
+ await this._saveSetting(
367
320
  'activeCompleterProvider',
368
321
  this._config.activeCompleterProvider
369
322
  );
@@ -385,19 +338,19 @@ Rules:
385
338
  delete provider[key];
386
339
  }
387
340
  });
388
- await this.saveSetting('providers', this._config.providers);
341
+ await this._saveSetting('providers', this._config.providers);
389
342
  }
390
343
 
391
344
  async setActiveProvider(id: string): Promise<void> {
392
345
  if (this.getProvider(id)) {
393
346
  this._config.defaultProvider = id;
394
- await this.saveSetting('defaultProvider', this._config.defaultProvider);
347
+ await this._saveSetting('defaultProvider', this._config.defaultProvider);
395
348
  }
396
349
  }
397
350
 
398
351
  async setActiveCompleterProvider(id: string | undefined): Promise<void> {
399
352
  this._config.activeCompleterProvider = id;
400
- await this.saveSetting(
353
+ await this._saveSetting(
401
354
  'activeCompleterProvider',
402
355
  this._config.activeCompleterProvider
403
356
  );
@@ -423,7 +376,7 @@ Rules:
423
376
  };
424
377
 
425
378
  this._config.mcpServers.push(newServer);
426
- await this.saveSetting('mcpServers', this._config.mcpServers);
379
+ await this._saveSetting('mcpServers', this._config.mcpServers);
427
380
  return id;
428
381
  }
429
382
 
@@ -434,7 +387,7 @@ Rules:
434
387
  }
435
388
 
436
389
  this._config.mcpServers.splice(index, 1);
437
- await this.saveSetting('mcpServers', this._config.mcpServers);
390
+ await this._saveSetting('mcpServers', this._config.mcpServers);
438
391
  }
439
392
 
440
393
  async updateMCPServer(
@@ -447,7 +400,7 @@ Rules:
447
400
  }
448
401
 
449
402
  Object.assign(server, updates);
450
- await this.saveSetting('mcpServers', this._config.mcpServers);
403
+ await this._saveSetting('mcpServers', this._config.mcpServers);
451
404
  }
452
405
 
453
406
  async updateConfig(updates: Partial<IAIConfig>): Promise<void> {
@@ -460,7 +413,7 @@ Rules:
460
413
  this._config[key as keyof IAIConfig] !== value
461
414
  ) {
462
415
  (this._config as any)[key] = value;
463
- promises.push(this.saveSetting(key as keyof IAIConfig, value));
416
+ promises.push(this._saveSetting(key as keyof IAIConfig, value));
464
417
  }
465
418
  }
466
419
 
@@ -468,6 +421,11 @@ Rules:
468
421
  await Promise.all(promises);
469
422
  }
470
423
 
424
+ /**
425
+ * Get the API key saved in the settings file for a given provider.
426
+ *
427
+ * @param id - the id of the provider.
428
+ */
471
429
  getApiKey(id: string): string {
472
430
  // First check the active completer provider
473
431
  const activeCompleterProvider = this.getCompleterProvider();
@@ -484,7 +442,7 @@ Rules:
484
442
  return '';
485
443
  }
486
444
 
487
- private async saveSetting(key: keyof IAIConfig, value: any): Promise<void> {
445
+ private async _saveSetting(key: keyof IAIConfig, value: any): Promise<void> {
488
446
  try {
489
447
  if (this._settings) {
490
448
  // Only save the specific setting that changed
@@ -4,6 +4,7 @@ import { createMistral } from '@ai-sdk/mistral';
4
4
  import { createOpenAI } from '@ai-sdk/openai';
5
5
  import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
6
6
 
7
+ import { BUILT_IN_PROVIDER_MODEL_INFO } from './generated-context-windows';
7
8
  import type { IProviderInfo } from '../tokens';
8
9
  import type { IModelOptions } from './models';
9
10
 
@@ -28,9 +29,9 @@ export const anthropicProvider: IProviderInfo = {
28
29
  'claude-opus-4-0',
29
30
  'claude-opus-4-20250514',
30
31
  'claude-sonnet-4-0',
31
- 'claude-sonnet-4-20250514',
32
- 'claude-3-haiku-20240307'
32
+ 'claude-sonnet-4-20250514'
33
33
  ],
34
+ modelInfo: BUILT_IN_PROVIDER_MODEL_INFO.anthropic,
34
35
  supportsBaseURL: true,
35
36
  supportsHeaders: true,
36
37
  providerToolCapabilities: {
@@ -65,30 +66,20 @@ export const googleProvider: IProviderInfo = {
65
66
  'gemini-3.1-pro-preview',
66
67
  'gemini-3.1-pro-preview-customtools',
67
68
  'gemini-3.1-flash-image-preview',
68
- 'gemini-3-pro-preview',
69
+ 'gemini-3.1-flash-lite-preview',
69
70
  'gemini-3-pro-image-preview',
70
71
  'gemini-3-flash-preview',
71
72
  'gemini-2.5-pro',
72
73
  'gemini-2.5-flash',
73
74
  'gemini-2.5-flash-image',
74
75
  'gemini-2.5-flash-lite',
75
- 'gemini-2.5-flash-lite-preview-09-2025',
76
76
  'gemini-2.5-computer-use-preview-10-2025',
77
- 'gemini-2.0-flash',
78
- 'gemini-2.0-flash-001',
79
- 'gemini-2.0-flash-lite',
80
- 'gemini-2.0-flash-lite-001',
77
+ 'deep-research-pro-preview-12-2025',
81
78
  'gemini-pro-latest',
82
79
  'gemini-flash-latest',
83
- 'gemini-flash-lite-latest',
84
- 'deep-research-pro-preview-12-2025',
85
- 'gemma-3-27b-it',
86
- 'gemma-3-12b-it',
87
- 'gemma-3-4b-it',
88
- 'gemma-3-1b-it',
89
- 'gemma-3n-e4b-it',
90
- 'gemma-3n-e2b-it'
80
+ 'gemini-flash-lite-latest'
91
81
  ],
82
+ modelInfo: BUILT_IN_PROVIDER_MODEL_INFO.google,
92
83
  supportsBaseURL: true,
93
84
  factory: (options: IModelOptions) => {
94
85
  if (!options.apiKey) {
@@ -111,24 +102,21 @@ export const mistralProvider: IProviderInfo = {
111
102
  name: 'Mistral AI',
112
103
  apiKeyRequirement: 'required',
113
104
  defaultModels: [
114
- 'ministral-3b-latest',
115
- 'ministral-8b-latest',
116
105
  'mistral-large-latest',
117
106
  'mistral-medium-latest',
118
107
  'mistral-medium-2508',
119
- 'mistral-medium-2505',
120
108
  'mistral-small-latest',
121
- 'codestral-latest',
109
+ 'mistral-small-2506',
110
+ 'ministral-3b-latest',
111
+ 'ministral-8b-latest',
112
+ 'ministral-14b-latest',
113
+ 'magistral-small-latest',
114
+ 'magistral-medium-latest',
122
115
  'pixtral-large-latest',
123
- 'magistral-small-2507',
124
- 'magistral-medium-2507',
125
- 'magistral-small-2506',
126
- 'magistral-medium-2506',
127
- 'pixtral-12b-2409',
128
- 'open-mistral-7b',
129
- 'open-mixtral-8x7b',
130
- 'open-mixtral-8x22b'
116
+ 'codestral-latest',
117
+ 'devstral-latest'
131
118
  ],
119
+ modelInfo: BUILT_IN_PROVIDER_MODEL_INFO.mistral,
132
120
  supportsBaseURL: true,
133
121
  factory: (options: IModelOptions) => {
134
122
  if (!options.apiKey) {
@@ -151,6 +139,9 @@ export const openaiProvider: IProviderInfo = {
151
139
  name: 'OpenAI',
152
140
  apiKeyRequirement: 'required',
153
141
  defaultModels: [
142
+ 'gpt-5.4',
143
+ 'gpt-5.4-mini',
144
+ 'gpt-5.4-nano',
154
145
  'gpt-5.2',
155
146
  'gpt-5.2-2025-12-11',
156
147
  'gpt-5.2-chat-latest',
@@ -169,6 +160,7 @@ export const openaiProvider: IProviderInfo = {
169
160
  'gpt-5-nano-2025-08-07',
170
161
  'o4-mini',
171
162
  'o4-mini-2025-04-16',
163
+ 'o3-pro',
172
164
  'o3',
173
165
  'o3-2025-04-16',
174
166
  'o3-mini',
@@ -185,22 +177,16 @@ export const openaiProvider: IProviderInfo = {
185
177
  'gpt-4o-2024-05-13',
186
178
  'gpt-4o-2024-08-06',
187
179
  'gpt-4o-2024-11-20',
188
- 'gpt-4o-audio-preview',
189
- 'gpt-4o-audio-preview-2024-12-17',
190
- 'gpt-4o-audio-preview-2025-06-03',
191
180
  'gpt-4o-mini',
192
181
  'gpt-4o-mini-2024-07-18',
193
- 'gpt-4o-mini-audio-preview',
194
- 'gpt-4o-mini-audio-preview-2024-12-17',
195
182
  'gpt-4o-search-preview',
196
183
  'gpt-4o-search-preview-2025-03-11',
197
184
  'gpt-4o-mini-search-preview',
198
185
  'gpt-4o-mini-search-preview-2025-03-11',
199
186
  'gpt-3.5-turbo',
200
- 'gpt-3.5-turbo-0125',
201
- 'gpt-3.5-turbo-1106',
202
- 'gpt-3.5-turbo-16k'
187
+ 'gpt-3.5-turbo-0125'
203
188
  ],
189
+ modelInfo: BUILT_IN_PROVIDER_MODEL_INFO.openai,
204
190
  supportsBaseURL: true,
205
191
  supportsHeaders: true,
206
192
  providerToolCapabilities: {
@@ -0,0 +1,102 @@
1
+ /**
2
+ * This file is generated by `jlpm sync:model-context-windows`.
3
+ * Source: https://models.dev/api.json
4
+ * Backed by: https://github.com/anomalyco/models.dev
5
+ * Generated: 2026-04-08T16:23:34.080Z
6
+ */
7
+
8
+ import type { IProviderModelInfo } from '../tokens';
9
+
10
+ export const BUILT_IN_PROVIDER_MODEL_INFO: Record<
11
+ string,
12
+ Record<string, IProviderModelInfo>
13
+ > = {
14
+ anthropic: {
15
+ 'claude-opus-4-6': { contextWindow: 1000000 },
16
+ 'claude-sonnet-4-6': { contextWindow: 1000000 },
17
+ 'claude-opus-4-5': { contextWindow: 200000 },
18
+ 'claude-opus-4-5-20251101': { contextWindow: 200000 },
19
+ 'claude-sonnet-4-5': { contextWindow: 200000 },
20
+ 'claude-sonnet-4-5-20250929': { contextWindow: 200000 },
21
+ 'claude-haiku-4-5': { contextWindow: 200000 },
22
+ 'claude-haiku-4-5-20251001': { contextWindow: 200000 },
23
+ 'claude-opus-4-1': { contextWindow: 200000 },
24
+ 'claude-opus-4-1-20250805': { contextWindow: 200000 },
25
+ 'claude-opus-4-0': { contextWindow: 200000 },
26
+ 'claude-opus-4-20250514': { contextWindow: 200000 },
27
+ 'claude-sonnet-4-0': { contextWindow: 200000 },
28
+ 'claude-sonnet-4-20250514': { contextWindow: 200000 }
29
+ },
30
+ google: {
31
+ 'gemini-3.1-pro-preview': { contextWindow: 1048576 },
32
+ 'gemini-3.1-pro-preview-customtools': { contextWindow: 1048576 },
33
+ 'gemini-3.1-flash-image-preview': { contextWindow: 131072 },
34
+ 'gemini-3.1-flash-lite-preview': { contextWindow: 1048576 },
35
+ 'gemini-3-flash-preview': { contextWindow: 1048576 },
36
+ 'gemini-2.5-pro': { contextWindow: 1048576 },
37
+ 'gemini-2.5-flash': { contextWindow: 1048576 },
38
+ 'gemini-2.5-flash-image': { contextWindow: 32768 },
39
+ 'gemini-2.5-flash-lite': { contextWindow: 1048576 },
40
+ 'gemini-flash-latest': { contextWindow: 1048576 },
41
+ 'gemini-flash-lite-latest': { contextWindow: 1048576 }
42
+ },
43
+ mistral: {
44
+ 'mistral-large-latest': { contextWindow: 262144 },
45
+ 'mistral-medium-latest': { contextWindow: 128000 },
46
+ 'mistral-medium-2508': { contextWindow: 262144 },
47
+ 'mistral-small-latest': { contextWindow: 256000 },
48
+ 'mistral-small-2506': { contextWindow: 128000 },
49
+ 'ministral-3b-latest': { contextWindow: 128000 },
50
+ 'ministral-8b-latest': { contextWindow: 128000 },
51
+ 'magistral-small-latest': { contextWindow: 128000 },
52
+ 'magistral-medium-latest': { contextWindow: 128000 },
53
+ 'pixtral-large-latest': { contextWindow: 128000 },
54
+ 'codestral-latest': { contextWindow: 256000 },
55
+ 'devstral-latest': { contextWindow: 262144 },
56
+ 'devstral-2512': { contextWindow: 262144 }
57
+ },
58
+ openai: {
59
+ 'gpt-5.4': { contextWindow: 1050000 },
60
+ 'gpt-5.4-mini': { contextWindow: 400000 },
61
+ 'gpt-5.4-nano': { contextWindow: 400000 },
62
+ 'gpt-5.2': { contextWindow: 400000 },
63
+ 'gpt-5.2-2025-12-11': { contextWindow: 400000 },
64
+ 'gpt-5.2-chat-latest': { contextWindow: 128000 },
65
+ 'gpt-5.2-pro': { contextWindow: 400000 },
66
+ 'gpt-5.2-pro-2025-12-11': { contextWindow: 400000 },
67
+ 'gpt-5.2-codex': { contextWindow: 400000 },
68
+ 'gpt-5.1': { contextWindow: 400000 },
69
+ 'gpt-5.1-2025-11-13': { contextWindow: 400000 },
70
+ 'gpt-5.1-chat-latest': { contextWindow: 128000 },
71
+ 'gpt-5': { contextWindow: 400000 },
72
+ 'gpt-5-2025-08-07': { contextWindow: 400000 },
73
+ 'gpt-5-chat-latest': { contextWindow: 400000 },
74
+ 'gpt-5-mini': { contextWindow: 400000 },
75
+ 'gpt-5-mini-2025-08-07': { contextWindow: 400000 },
76
+ 'gpt-5-nano': { contextWindow: 400000 },
77
+ 'gpt-5-nano-2025-08-07': { contextWindow: 400000 },
78
+ 'o4-mini': { contextWindow: 200000 },
79
+ 'o4-mini-2025-04-16': { contextWindow: 200000 },
80
+ 'o3-pro': { contextWindow: 200000 },
81
+ o3: { contextWindow: 200000 },
82
+ 'o3-2025-04-16': { contextWindow: 200000 },
83
+ 'o3-mini': { contextWindow: 200000 },
84
+ 'o3-mini-2025-01-31': { contextWindow: 200000 },
85
+ o1: { contextWindow: 200000 },
86
+ 'o1-2024-12-17': { contextWindow: 200000 },
87
+ 'gpt-4.1': { contextWindow: 1047576 },
88
+ 'gpt-4.1-2025-04-14': { contextWindow: 1047576 },
89
+ 'gpt-4.1-mini': { contextWindow: 1047576 },
90
+ 'gpt-4.1-mini-2025-04-14': { contextWindow: 1047576 },
91
+ 'gpt-4.1-nano': { contextWindow: 1047576 },
92
+ 'gpt-4.1-nano-2025-04-14': { contextWindow: 1047576 },
93
+ 'gpt-4o': { contextWindow: 128000 },
94
+ 'gpt-4o-2024-05-13': { contextWindow: 128000 },
95
+ 'gpt-4o-2024-08-06': { contextWindow: 128000 },
96
+ 'gpt-4o-2024-11-20': { contextWindow: 128000 },
97
+ 'gpt-4o-mini': { contextWindow: 128000 },
98
+ 'gpt-4o-mini-2024-07-18': { contextWindow: 128000 },
99
+ 'gpt-3.5-turbo': { contextWindow: 16385 },
100
+ 'gpt-3.5-turbo-0125': { contextWindow: 16385 }
101
+ }
102
+ };
@@ -0,0 +1,88 @@
1
+ import type {
2
+ IProviderConfig,
3
+ IProviderInfo,
4
+ IProviderModelInfo,
5
+ IProviderRegistry
6
+ } from '../tokens';
7
+
8
+ const DATE_SUFFIX = /^(.*)-\d{4}-\d{2}-\d{2}$/;
9
+ const SHORT_VERSION_SUFFIX = /^(.*)-\d{4}$/;
10
+
11
+ // Treat rolling aliases and dated releases as the same model family so they
12
+ // can share provider metadata such as context windows.
13
+ function normalizeModelId(modelId: string): string {
14
+ if (modelId.endsWith('-latest')) {
15
+ return modelId.slice(0, -7);
16
+ }
17
+
18
+ const dateSuffixMatch = modelId.match(DATE_SUFFIX);
19
+ if (dateSuffixMatch) {
20
+ return dateSuffixMatch[1];
21
+ }
22
+
23
+ const shortVersionSuffixMatch = modelId.match(SHORT_VERSION_SUFFIX);
24
+ if (shortVersionSuffixMatch) {
25
+ return shortVersionSuffixMatch[1];
26
+ }
27
+
28
+ return modelId;
29
+ }
30
+
31
+ function getCandidateModelIds(modelId: string): string[] {
32
+ const candidates = [modelId];
33
+ const normalizedModelId = normalizeModelId(modelId);
34
+
35
+ candidates.push(normalizedModelId);
36
+
37
+ if (normalizedModelId !== modelId) {
38
+ candidates.push(`${normalizedModelId}-latest`);
39
+ }
40
+
41
+ return [...new Set(candidates)];
42
+ }
43
+
44
+ export function getProviderModelInfo(
45
+ providerInfo: IProviderInfo | null | undefined,
46
+ model: string | undefined
47
+ ): IProviderModelInfo | undefined {
48
+ if (!providerInfo || !model) {
49
+ return undefined;
50
+ }
51
+
52
+ const modelInfo = providerInfo.modelInfo;
53
+ if (!modelInfo) {
54
+ return undefined;
55
+ }
56
+
57
+ for (const candidateId of getCandidateModelIds(model)) {
58
+ if (modelInfo[candidateId]) {
59
+ return modelInfo[candidateId];
60
+ }
61
+ }
62
+
63
+ const normalizedModelId = normalizeModelId(model);
64
+ // As a last resort, match any known model entry that normalizes to the same
65
+ // base ID, even if the exact alias/version string differs.
66
+ return Object.entries(modelInfo).find(([candidateId]) => {
67
+ return normalizeModelId(candidateId) === normalizedModelId;
68
+ })?.[1];
69
+ }
70
+
71
+ export function getEffectiveContextWindow(
72
+ providerConfig: IProviderConfig | undefined,
73
+ providerRegistry?: IProviderRegistry
74
+ ): number | undefined {
75
+ if (!providerConfig) {
76
+ return undefined;
77
+ }
78
+
79
+ if (providerConfig.parameters?.contextWindow !== undefined) {
80
+ return providerConfig.parameters.contextWindow;
81
+ }
82
+
83
+ const providerInfo = providerRegistry?.getProviderInfo(
84
+ providerConfig.provider
85
+ );
86
+ return getProviderModelInfo(providerInfo, providerConfig.model)
87
+ ?.contextWindow;
88
+ }