@jupyterlite/ai 0.15.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.
- package/lib/agent.d.ts +5 -1
- package/lib/agent.js +53 -7
- package/lib/chat-model.js +8 -2
- package/lib/components/index.d.ts +1 -1
- package/lib/components/index.js +1 -1
- package/lib/components/{token-usage-display.d.ts → usage-display.d.ts} +11 -11
- package/lib/components/usage-display.js +109 -0
- package/lib/index.js +5 -5
- package/lib/models/settings-model.js +1 -0
- package/lib/providers/built-in-providers.js +5 -0
- package/lib/providers/generated-context-windows.d.ts +8 -0
- package/lib/providers/generated-context-windows.js +96 -0
- package/lib/providers/model-info.d.ts +3 -0
- package/lib/providers/model-info.js +58 -0
- package/lib/tokens.d.ts +21 -0
- package/lib/tokens.js +7 -7
- package/lib/widgets/ai-settings.js +9 -0
- package/lib/widgets/main-area-chat.js +3 -3
- package/lib/widgets/provider-config-dialog.js +18 -5
- package/package.json +3 -2
- package/schema/settings-model.json +11 -0
- package/src/agent.ts +79 -7
- package/src/chat-model.ts +7 -4
- package/src/components/index.ts +1 -1
- package/src/components/usage-display.tsx +208 -0
- package/src/index.ts +5 -9
- package/src/models/settings-model.ts +1 -0
- package/src/providers/built-in-providers.ts +5 -0
- package/src/providers/generated-context-windows.ts +102 -0
- package/src/providers/model-info.ts +88 -0
- package/src/tokens.ts +33 -7
- package/src/widgets/ai-settings.tsx +42 -0
- package/src/widgets/main-area-chat.ts +3 -3
- package/src/widgets/provider-config-dialog.tsx +45 -5
- package/lib/components/token-usage-display.js +0 -72
- package/src/components/token-usage-display.tsx +0 -137
package/lib/agent.d.ts
CHANGED
|
@@ -160,9 +160,13 @@ export declare class AgentManager implements IAgentManager {
|
|
|
160
160
|
*/
|
|
161
161
|
generateResponse(message: string): Promise<void>;
|
|
162
162
|
/**
|
|
163
|
-
* Updates token usage statistics.
|
|
163
|
+
* Updates cumulative token usage statistics from a completed model step.
|
|
164
164
|
*/
|
|
165
165
|
private _updateTokenUsage;
|
|
166
|
+
/**
|
|
167
|
+
* Gets the configured context window for the active provider.
|
|
168
|
+
*/
|
|
169
|
+
private _getActiveContextWindow;
|
|
166
170
|
/**
|
|
167
171
|
* Initializes the AI agent with current settings and tools.
|
|
168
172
|
* Sets up the agent with model configuration, tools, and MCP tools.
|
package/lib/agent.js
CHANGED
|
@@ -3,6 +3,7 @@ import { PromiseDelegate } from '@lumino/coreutils';
|
|
|
3
3
|
import { Signal } from '@lumino/signaling';
|
|
4
4
|
import { ToolLoopAgent, stepCountIs } from 'ai';
|
|
5
5
|
import { createModel } from './providers/models';
|
|
6
|
+
import { getEffectiveContextWindow } from './providers/model-info';
|
|
6
7
|
import { createProviderTools } from './providers/provider-tools';
|
|
7
8
|
import { SECRETS_NAMESPACE } from './tokens';
|
|
8
9
|
/**
|
|
@@ -256,7 +257,14 @@ export class AgentManager {
|
|
|
256
257
|
return this._activeProvider;
|
|
257
258
|
}
|
|
258
259
|
set activeProvider(value) {
|
|
260
|
+
const previousProvider = this._activeProvider;
|
|
259
261
|
this._activeProvider = value;
|
|
262
|
+
// Reset request-level context estimate only when switching between providers.
|
|
263
|
+
if (previousProvider && previousProvider !== value) {
|
|
264
|
+
this._tokenUsage.lastRequestInputTokens = undefined;
|
|
265
|
+
}
|
|
266
|
+
this._tokenUsage.contextWindow = this._getActiveContextWindow();
|
|
267
|
+
this._tokenUsageChanged.emit(this._tokenUsage);
|
|
260
268
|
this.initializeAgent();
|
|
261
269
|
this._activeProviderChanged.emit(this._activeProvider);
|
|
262
270
|
}
|
|
@@ -315,7 +323,11 @@ export class AgentManager {
|
|
|
315
323
|
await this._streaming.promise;
|
|
316
324
|
// Clear history and token usage
|
|
317
325
|
this._history = [];
|
|
318
|
-
this._tokenUsage = {
|
|
326
|
+
this._tokenUsage = {
|
|
327
|
+
inputTokens: 0,
|
|
328
|
+
outputTokens: 0,
|
|
329
|
+
contextWindow: this._getActiveContextWindow()
|
|
330
|
+
};
|
|
319
331
|
this._tokenUsageChanged.emit(this._tokenUsage);
|
|
320
332
|
}
|
|
321
333
|
/**
|
|
@@ -420,9 +432,20 @@ export class AgentManager {
|
|
|
420
432
|
abortSignal: this._controller.signal
|
|
421
433
|
});
|
|
422
434
|
const streamResult = await this._processStreamResult(result);
|
|
423
|
-
|
|
435
|
+
if (streamResult.aborted) {
|
|
436
|
+
try {
|
|
437
|
+
const responseMessages = await result.response;
|
|
438
|
+
if (responseMessages.messages?.length) {
|
|
439
|
+
this._history.push(...Private.sanitizeModelMessages(responseMessages.messages));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
catch {
|
|
443
|
+
// Aborting before a step finishes leaves no completed response to persist.
|
|
444
|
+
}
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
447
|
+
// Get response messages for completed steps.
|
|
424
448
|
const responseMessages = await result.response;
|
|
425
|
-
this._updateTokenUsage(await result.usage);
|
|
426
449
|
// Add response messages to history
|
|
427
450
|
if (responseMessages.messages?.length) {
|
|
428
451
|
responseHistory.push(...responseMessages.messages);
|
|
@@ -462,14 +485,25 @@ export class AgentManager {
|
|
|
462
485
|
}
|
|
463
486
|
}
|
|
464
487
|
/**
|
|
465
|
-
* Updates token usage statistics.
|
|
488
|
+
* Updates cumulative token usage statistics from a completed model step.
|
|
466
489
|
*/
|
|
467
|
-
_updateTokenUsage(usage) {
|
|
490
|
+
_updateTokenUsage(usage, lastRequestInputTokens) {
|
|
491
|
+
const contextWindow = this._getActiveContextWindow();
|
|
492
|
+
const estimatedRequestInputTokens = lastRequestInputTokens ?? usage?.inputTokens;
|
|
468
493
|
if (usage) {
|
|
469
494
|
this._tokenUsage.inputTokens += usage.inputTokens ?? 0;
|
|
470
495
|
this._tokenUsage.outputTokens += usage.outputTokens ?? 0;
|
|
471
|
-
this._tokenUsageChanged.emit(this._tokenUsage);
|
|
472
496
|
}
|
|
497
|
+
this._tokenUsage.lastRequestInputTokens = estimatedRequestInputTokens;
|
|
498
|
+
this._tokenUsage.contextWindow = contextWindow;
|
|
499
|
+
this._tokenUsageChanged.emit(this._tokenUsage);
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Gets the configured context window for the active provider.
|
|
503
|
+
*/
|
|
504
|
+
_getActiveContextWindow() {
|
|
505
|
+
const activeProviderConfig = this._settingsModel.getProvider(this._activeProvider);
|
|
506
|
+
return getEffectiveContextWindow(activeProviderConfig, this._providerRegistry);
|
|
473
507
|
}
|
|
474
508
|
/**
|
|
475
509
|
* Initializes the AI agent with current settings and tools.
|
|
@@ -521,6 +555,9 @@ export class AgentManager {
|
|
|
521
555
|
const activeProviderInfo = activeProviderConfig && this._providerRegistry
|
|
522
556
|
? this._providerRegistry.getProviderInfo(activeProviderConfig.provider)
|
|
523
557
|
: null;
|
|
558
|
+
const contextWindow = getEffectiveContextWindow(activeProviderConfig, this._providerRegistry);
|
|
559
|
+
this._tokenUsage.contextWindow = contextWindow;
|
|
560
|
+
this._tokenUsageChanged.emit(this._tokenUsage);
|
|
524
561
|
const temperature = activeProviderConfig?.parameters?.temperature ?? DEFAULT_TEMPERATURE;
|
|
525
562
|
const maxTokens = activeProviderConfig?.parameters?.maxOutputTokens;
|
|
526
563
|
const maxTurns = activeProviderConfig?.parameters?.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
@@ -599,7 +636,10 @@ ${richOutputWorkflowInstruction}`;
|
|
|
599
636
|
async _processStreamResult(result) {
|
|
600
637
|
let fullResponse = '';
|
|
601
638
|
let currentMessageId = null;
|
|
602
|
-
const processResult = {
|
|
639
|
+
const processResult = {
|
|
640
|
+
approvalProcessed: false,
|
|
641
|
+
aborted: false
|
|
642
|
+
};
|
|
603
643
|
for await (const part of result.fullStream) {
|
|
604
644
|
switch (part.type) {
|
|
605
645
|
case 'text-delta':
|
|
@@ -654,6 +694,12 @@ ${richOutputWorkflowInstruction}`;
|
|
|
654
694
|
}
|
|
655
695
|
await this._handleApprovalRequest(part, processResult);
|
|
656
696
|
break;
|
|
697
|
+
case 'finish-step':
|
|
698
|
+
this._updateTokenUsage(part.usage, part.usage.inputTokens);
|
|
699
|
+
break;
|
|
700
|
+
case 'abort':
|
|
701
|
+
processResult.aborted = true;
|
|
702
|
+
break;
|
|
657
703
|
// Ignore: text-start, text-end, finish, error, and others
|
|
658
704
|
default:
|
|
659
705
|
break;
|
package/lib/chat-model.js
CHANGED
|
@@ -258,7 +258,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
258
258
|
return false;
|
|
259
259
|
}
|
|
260
260
|
const contentModel = await this._contentsManager
|
|
261
|
-
.get(filepath, { content: true })
|
|
261
|
+
.get(filepath, { content: true, type: 'file', format: 'text' })
|
|
262
262
|
.catch(() => {
|
|
263
263
|
if (!silent) {
|
|
264
264
|
console.log(`There is no backup for chat '${this.name}'`);
|
|
@@ -268,7 +268,13 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
268
268
|
if (!contentModel) {
|
|
269
269
|
return false;
|
|
270
270
|
}
|
|
271
|
-
|
|
271
|
+
let content;
|
|
272
|
+
try {
|
|
273
|
+
content = JSON.parse(contentModel.content);
|
|
274
|
+
}
|
|
275
|
+
catch (e) {
|
|
276
|
+
throw `Error when parsing the chat ${filepath}\n${e}`;
|
|
277
|
+
}
|
|
272
278
|
if (content.metadata?.provider) {
|
|
273
279
|
if (this._settingsModel.getProvider(content.metadata.provider)) {
|
|
274
280
|
this._agentManager.activeProvider = content.metadata.provider;
|
package/lib/components/index.js
CHANGED
|
@@ -4,9 +4,9 @@ import React from 'react';
|
|
|
4
4
|
import { ISignal } from '@lumino/signaling';
|
|
5
5
|
import type { IAISettingsModel, ITokenUsage } from '../tokens';
|
|
6
6
|
/**
|
|
7
|
-
* Props for the
|
|
7
|
+
* Props for the UsageDisplay component.
|
|
8
8
|
*/
|
|
9
|
-
export interface
|
|
9
|
+
export interface IUsageDisplayProps {
|
|
10
10
|
/**
|
|
11
11
|
* The token usage changed signal
|
|
12
12
|
*/
|
|
@@ -25,24 +25,24 @@ export interface ITokenUsageDisplayProps {
|
|
|
25
25
|
translator: TranslationBundle;
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* React component that displays
|
|
29
|
-
* Shows input/output token counts
|
|
30
|
-
* Only renders when token usage display is enabled in settings.
|
|
28
|
+
* React component that displays usage information.
|
|
29
|
+
* Shows input/output token counts and optional estimated context usage.
|
|
30
|
+
* Only renders when token or context usage display is enabled in settings.
|
|
31
31
|
*/
|
|
32
|
-
export declare const
|
|
32
|
+
export declare const UsageDisplay: React.FC<IUsageDisplayProps>;
|
|
33
33
|
/**
|
|
34
|
-
* JupyterLab widget wrapper for the
|
|
34
|
+
* JupyterLab widget wrapper for the UsageDisplay component.
|
|
35
35
|
* Extends ReactWidget to integrate with the JupyterLab widget system.
|
|
36
36
|
*/
|
|
37
|
-
export declare class
|
|
37
|
+
export declare class UsageWidget extends ReactWidget {
|
|
38
38
|
/**
|
|
39
|
-
* Creates a new
|
|
39
|
+
* Creates a new UsageWidget instance.
|
|
40
40
|
* @param options - Configuration options containing required models
|
|
41
41
|
*/
|
|
42
|
-
constructor(options:
|
|
42
|
+
constructor(options: IUsageDisplayProps);
|
|
43
43
|
/**
|
|
44
44
|
* Renders the React component within the widget.
|
|
45
|
-
* @returns The
|
|
45
|
+
* @returns The UsageDisplay React element
|
|
46
46
|
*/
|
|
47
47
|
protected render(): React.ReactElement;
|
|
48
48
|
private _options;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { ReactWidget, UseSignal } from '@jupyterlab/ui-components';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* React component that displays usage information.
|
|
5
|
+
* Shows input/output token counts and optional estimated context usage.
|
|
6
|
+
* Only renders when token or context usage display is enabled in settings.
|
|
7
|
+
*/
|
|
8
|
+
export const UsageDisplay = ({ tokenUsageChanged, settingsModel, initialTokenUsage, translator: trans }) => {
|
|
9
|
+
const formatContextPercent = (value) => {
|
|
10
|
+
return Math.round(value).toLocaleString();
|
|
11
|
+
};
|
|
12
|
+
const badgeStyle = {
|
|
13
|
+
display: 'flex',
|
|
14
|
+
alignItems: 'center',
|
|
15
|
+
gap: '6px',
|
|
16
|
+
fontSize: '12px',
|
|
17
|
+
color: 'var(--jp-ui-font-color2)',
|
|
18
|
+
padding: '4px 8px',
|
|
19
|
+
backgroundColor: 'var(--jp-layout-color1)',
|
|
20
|
+
border: '1px solid var(--jp-border-color1)',
|
|
21
|
+
borderRadius: '4px',
|
|
22
|
+
whiteSpace: 'nowrap'
|
|
23
|
+
};
|
|
24
|
+
return (React.createElement(UseSignal, { signal: settingsModel.stateChanged, initialArgs: undefined }, () => {
|
|
25
|
+
const config = settingsModel.config;
|
|
26
|
+
const showTokenUsage = config.showTokenUsage;
|
|
27
|
+
const showContextUsage = config.showContextUsage;
|
|
28
|
+
if (!showTokenUsage && !showContextUsage) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return (React.createElement(UseSignal, { signal: tokenUsageChanged, initialArgs: initialTokenUsage }, (_, tokenUsage) => {
|
|
32
|
+
if (!tokenUsage) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const total = tokenUsage.inputTokens + tokenUsage.outputTokens;
|
|
36
|
+
const hasKnownContextWindow = showContextUsage && tokenUsage.contextWindow !== undefined;
|
|
37
|
+
const contextUsagePercent = tokenUsage.lastRequestInputTokens !== undefined &&
|
|
38
|
+
tokenUsage.contextWindow !== undefined &&
|
|
39
|
+
tokenUsage.contextWindow > 0
|
|
40
|
+
? Math.max(0, Math.min(100, (tokenUsage.lastRequestInputTokens /
|
|
41
|
+
tokenUsage.contextWindow) *
|
|
42
|
+
100))
|
|
43
|
+
: undefined;
|
|
44
|
+
const hasContextEstimate = hasKnownContextWindow &&
|
|
45
|
+
contextUsagePercent !== undefined &&
|
|
46
|
+
tokenUsage.lastRequestInputTokens !== undefined;
|
|
47
|
+
const contextLabel = hasContextEstimate
|
|
48
|
+
? `${formatContextPercent(contextUsagePercent)}%`
|
|
49
|
+
: hasKnownContextWindow
|
|
50
|
+
? '0%'
|
|
51
|
+
: '?';
|
|
52
|
+
const contextTitle = hasContextEstimate
|
|
53
|
+
? trans.__('Context Usage (estimated): %1% (%2 / %3 tokens)', formatContextPercent(contextUsagePercent), tokenUsage.lastRequestInputTokens.toLocaleString(), tokenUsage.contextWindow.toLocaleString())
|
|
54
|
+
: hasKnownContextWindow
|
|
55
|
+
? trans.__('Context usage estimate will appear after the next request. Showing 0% until then. Context window: %1 tokens', tokenUsage.contextWindow.toLocaleString())
|
|
56
|
+
: trans.__('Context Usage unavailable. Configure a context window for the active provider/model to enable estimation.');
|
|
57
|
+
return (React.createElement("div", { style: {
|
|
58
|
+
display: 'flex',
|
|
59
|
+
alignItems: 'center',
|
|
60
|
+
gap: '6px'
|
|
61
|
+
} },
|
|
62
|
+
showTokenUsage && (React.createElement("span", { style: badgeStyle, title: trans.__('Token Usage - Sent: %1, Received: %2, Total: %3', tokenUsage.inputTokens.toLocaleString(), tokenUsage.outputTokens.toLocaleString(), total.toLocaleString()) },
|
|
63
|
+
React.createElement("span", { style: {
|
|
64
|
+
display: 'flex',
|
|
65
|
+
alignItems: 'center',
|
|
66
|
+
gap: '2px'
|
|
67
|
+
} },
|
|
68
|
+
React.createElement("span", null, "\u2191"),
|
|
69
|
+
React.createElement("span", null, tokenUsage.inputTokens.toLocaleString())),
|
|
70
|
+
React.createElement("span", { style: {
|
|
71
|
+
display: 'flex',
|
|
72
|
+
alignItems: 'center',
|
|
73
|
+
gap: '2px'
|
|
74
|
+
} },
|
|
75
|
+
React.createElement("span", null, "\u2193"),
|
|
76
|
+
React.createElement("span", null, tokenUsage.outputTokens.toLocaleString())))),
|
|
77
|
+
showContextUsage && (React.createElement("span", { style: badgeStyle, title: contextTitle },
|
|
78
|
+
React.createElement("span", { style: {
|
|
79
|
+
display: 'flex',
|
|
80
|
+
alignItems: 'center',
|
|
81
|
+
gap: '2px'
|
|
82
|
+
} },
|
|
83
|
+
React.createElement("span", null, "ctx"),
|
|
84
|
+
React.createElement("span", null, contextLabel))))));
|
|
85
|
+
}));
|
|
86
|
+
}));
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* JupyterLab widget wrapper for the UsageDisplay component.
|
|
90
|
+
* Extends ReactWidget to integrate with the JupyterLab widget system.
|
|
91
|
+
*/
|
|
92
|
+
export class UsageWidget extends ReactWidget {
|
|
93
|
+
/**
|
|
94
|
+
* Creates a new UsageWidget instance.
|
|
95
|
+
* @param options - Configuration options containing required models
|
|
96
|
+
*/
|
|
97
|
+
constructor(options) {
|
|
98
|
+
super();
|
|
99
|
+
this._options = options;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Renders the React component within the widget.
|
|
103
|
+
* @returns The UsageDisplay React element
|
|
104
|
+
*/
|
|
105
|
+
render() {
|
|
106
|
+
return React.createElement(UsageDisplay, { ...this._options });
|
|
107
|
+
}
|
|
108
|
+
_options;
|
|
109
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -25,7 +25,7 @@ import { ChatModelHandler } from './chat-model-handler';
|
|
|
25
25
|
import { CommandIds, IAgentManagerFactory, IAISettingsModel, IChatModelHandler, IDiffManager, IProviderRegistry, IToolRegistry, ISkillRegistry, SECRETS_NAMESPACE } from './tokens';
|
|
26
26
|
import { anthropicProvider, googleProvider, mistralProvider, openaiProvider, genericProvider } from './providers/built-in-providers';
|
|
27
27
|
import { AICompletionProvider } from './completion';
|
|
28
|
-
import { clearItem, createModelSelectItem, createToolSelectItem, stopItem, CompletionStatusWidget,
|
|
28
|
+
import { clearItem, createModelSelectItem, createToolSelectItem, stopItem, CompletionStatusWidget, UsageWidget } from './components';
|
|
29
29
|
import { AISettingsModel } from './models/settings-model';
|
|
30
30
|
import { loadSkillsFromPaths, SkillRegistry } from './skills';
|
|
31
31
|
import { DiffManager } from './diff-manager';
|
|
@@ -338,7 +338,7 @@ const plugin = {
|
|
|
338
338
|
app.commands.commandChanged.connect(onCommandChanged);
|
|
339
339
|
chatPanel.disposed.connect(disconnectSettingsButtonListener);
|
|
340
340
|
}
|
|
341
|
-
let
|
|
341
|
+
let usageWidget = null;
|
|
342
342
|
chatPanel.chatOpened.connect((_, widget) => {
|
|
343
343
|
const model = widget.model;
|
|
344
344
|
// Add the widget to the tracker.
|
|
@@ -351,14 +351,14 @@ const plugin = {
|
|
|
351
351
|
// Update the tracker if the active provider changed.
|
|
352
352
|
model.agentManager.activeProviderChanged.connect(saveTracker);
|
|
353
353
|
// Update the token usage widget.
|
|
354
|
-
|
|
355
|
-
|
|
354
|
+
usageWidget?.dispose();
|
|
355
|
+
usageWidget = new UsageWidget({
|
|
356
356
|
tokenUsageChanged: model.tokenUsageChanged,
|
|
357
357
|
settingsModel,
|
|
358
358
|
initialTokenUsage: model.agentManager.tokenUsage,
|
|
359
359
|
translator: trans
|
|
360
360
|
});
|
|
361
|
-
chatPanel.current?.toolbar.insertBefore('markRead', '
|
|
361
|
+
chatPanel.current?.toolbar.insertBefore('markRead', 'usage', usageWidget);
|
|
362
362
|
if (model.saveAvailable) {
|
|
363
363
|
const saveChatButton = new SaveComponentWidget({
|
|
364
364
|
model,
|
|
@@ -3,6 +3,7 @@ import { createGoogleGenerativeAI } from '@ai-sdk/google';
|
|
|
3
3
|
import { createMistral } from '@ai-sdk/mistral';
|
|
4
4
|
import { createOpenAI } from '@ai-sdk/openai';
|
|
5
5
|
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
|
6
|
+
import { BUILT_IN_PROVIDER_MODEL_INFO } from './generated-context-windows';
|
|
6
7
|
/**
|
|
7
8
|
* Anthropic provider
|
|
8
9
|
*/
|
|
@@ -26,6 +27,7 @@ export const anthropicProvider = {
|
|
|
26
27
|
'claude-sonnet-4-0',
|
|
27
28
|
'claude-sonnet-4-20250514'
|
|
28
29
|
],
|
|
30
|
+
modelInfo: BUILT_IN_PROVIDER_MODEL_INFO.anthropic,
|
|
29
31
|
supportsBaseURL: true,
|
|
30
32
|
supportsHeaders: true,
|
|
31
33
|
providerToolCapabilities: {
|
|
@@ -72,6 +74,7 @@ export const googleProvider = {
|
|
|
72
74
|
'gemini-flash-latest',
|
|
73
75
|
'gemini-flash-lite-latest'
|
|
74
76
|
],
|
|
77
|
+
modelInfo: BUILT_IN_PROVIDER_MODEL_INFO.google,
|
|
75
78
|
supportsBaseURL: true,
|
|
76
79
|
factory: (options) => {
|
|
77
80
|
if (!options.apiKey) {
|
|
@@ -107,6 +110,7 @@ export const mistralProvider = {
|
|
|
107
110
|
'codestral-latest',
|
|
108
111
|
'devstral-latest'
|
|
109
112
|
],
|
|
113
|
+
modelInfo: BUILT_IN_PROVIDER_MODEL_INFO.mistral,
|
|
110
114
|
supportsBaseURL: true,
|
|
111
115
|
factory: (options) => {
|
|
112
116
|
if (!options.apiKey) {
|
|
@@ -175,6 +179,7 @@ export const openaiProvider = {
|
|
|
175
179
|
'gpt-3.5-turbo',
|
|
176
180
|
'gpt-3.5-turbo-0125'
|
|
177
181
|
],
|
|
182
|
+
modelInfo: BUILT_IN_PROVIDER_MODEL_INFO.openai,
|
|
178
183
|
supportsBaseURL: true,
|
|
179
184
|
supportsHeaders: true,
|
|
180
185
|
providerToolCapabilities: {
|
|
@@ -0,0 +1,8 @@
|
|
|
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
|
+
import type { IProviderModelInfo } from '../tokens';
|
|
8
|
+
export declare const BUILT_IN_PROVIDER_MODEL_INFO: Record<string, Record<string, IProviderModelInfo>>;
|
|
@@ -0,0 +1,96 @@
|
|
|
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
|
+
export const BUILT_IN_PROVIDER_MODEL_INFO = {
|
|
8
|
+
anthropic: {
|
|
9
|
+
'claude-opus-4-6': { contextWindow: 1000000 },
|
|
10
|
+
'claude-sonnet-4-6': { contextWindow: 1000000 },
|
|
11
|
+
'claude-opus-4-5': { contextWindow: 200000 },
|
|
12
|
+
'claude-opus-4-5-20251101': { contextWindow: 200000 },
|
|
13
|
+
'claude-sonnet-4-5': { contextWindow: 200000 },
|
|
14
|
+
'claude-sonnet-4-5-20250929': { contextWindow: 200000 },
|
|
15
|
+
'claude-haiku-4-5': { contextWindow: 200000 },
|
|
16
|
+
'claude-haiku-4-5-20251001': { contextWindow: 200000 },
|
|
17
|
+
'claude-opus-4-1': { contextWindow: 200000 },
|
|
18
|
+
'claude-opus-4-1-20250805': { contextWindow: 200000 },
|
|
19
|
+
'claude-opus-4-0': { contextWindow: 200000 },
|
|
20
|
+
'claude-opus-4-20250514': { contextWindow: 200000 },
|
|
21
|
+
'claude-sonnet-4-0': { contextWindow: 200000 },
|
|
22
|
+
'claude-sonnet-4-20250514': { contextWindow: 200000 }
|
|
23
|
+
},
|
|
24
|
+
google: {
|
|
25
|
+
'gemini-3.1-pro-preview': { contextWindow: 1048576 },
|
|
26
|
+
'gemini-3.1-pro-preview-customtools': { contextWindow: 1048576 },
|
|
27
|
+
'gemini-3.1-flash-image-preview': { contextWindow: 131072 },
|
|
28
|
+
'gemini-3.1-flash-lite-preview': { contextWindow: 1048576 },
|
|
29
|
+
'gemini-3-flash-preview': { contextWindow: 1048576 },
|
|
30
|
+
'gemini-2.5-pro': { contextWindow: 1048576 },
|
|
31
|
+
'gemini-2.5-flash': { contextWindow: 1048576 },
|
|
32
|
+
'gemini-2.5-flash-image': { contextWindow: 32768 },
|
|
33
|
+
'gemini-2.5-flash-lite': { contextWindow: 1048576 },
|
|
34
|
+
'gemini-flash-latest': { contextWindow: 1048576 },
|
|
35
|
+
'gemini-flash-lite-latest': { contextWindow: 1048576 }
|
|
36
|
+
},
|
|
37
|
+
mistral: {
|
|
38
|
+
'mistral-large-latest': { contextWindow: 262144 },
|
|
39
|
+
'mistral-medium-latest': { contextWindow: 128000 },
|
|
40
|
+
'mistral-medium-2508': { contextWindow: 262144 },
|
|
41
|
+
'mistral-small-latest': { contextWindow: 256000 },
|
|
42
|
+
'mistral-small-2506': { contextWindow: 128000 },
|
|
43
|
+
'ministral-3b-latest': { contextWindow: 128000 },
|
|
44
|
+
'ministral-8b-latest': { contextWindow: 128000 },
|
|
45
|
+
'magistral-small-latest': { contextWindow: 128000 },
|
|
46
|
+
'magistral-medium-latest': { contextWindow: 128000 },
|
|
47
|
+
'pixtral-large-latest': { contextWindow: 128000 },
|
|
48
|
+
'codestral-latest': { contextWindow: 256000 },
|
|
49
|
+
'devstral-latest': { contextWindow: 262144 },
|
|
50
|
+
'devstral-2512': { contextWindow: 262144 }
|
|
51
|
+
},
|
|
52
|
+
openai: {
|
|
53
|
+
'gpt-5.4': { contextWindow: 1050000 },
|
|
54
|
+
'gpt-5.4-mini': { contextWindow: 400000 },
|
|
55
|
+
'gpt-5.4-nano': { contextWindow: 400000 },
|
|
56
|
+
'gpt-5.2': { contextWindow: 400000 },
|
|
57
|
+
'gpt-5.2-2025-12-11': { contextWindow: 400000 },
|
|
58
|
+
'gpt-5.2-chat-latest': { contextWindow: 128000 },
|
|
59
|
+
'gpt-5.2-pro': { contextWindow: 400000 },
|
|
60
|
+
'gpt-5.2-pro-2025-12-11': { contextWindow: 400000 },
|
|
61
|
+
'gpt-5.2-codex': { contextWindow: 400000 },
|
|
62
|
+
'gpt-5.1': { contextWindow: 400000 },
|
|
63
|
+
'gpt-5.1-2025-11-13': { contextWindow: 400000 },
|
|
64
|
+
'gpt-5.1-chat-latest': { contextWindow: 128000 },
|
|
65
|
+
'gpt-5': { contextWindow: 400000 },
|
|
66
|
+
'gpt-5-2025-08-07': { contextWindow: 400000 },
|
|
67
|
+
'gpt-5-chat-latest': { contextWindow: 400000 },
|
|
68
|
+
'gpt-5-mini': { contextWindow: 400000 },
|
|
69
|
+
'gpt-5-mini-2025-08-07': { contextWindow: 400000 },
|
|
70
|
+
'gpt-5-nano': { contextWindow: 400000 },
|
|
71
|
+
'gpt-5-nano-2025-08-07': { contextWindow: 400000 },
|
|
72
|
+
'o4-mini': { contextWindow: 200000 },
|
|
73
|
+
'o4-mini-2025-04-16': { contextWindow: 200000 },
|
|
74
|
+
'o3-pro': { contextWindow: 200000 },
|
|
75
|
+
o3: { contextWindow: 200000 },
|
|
76
|
+
'o3-2025-04-16': { contextWindow: 200000 },
|
|
77
|
+
'o3-mini': { contextWindow: 200000 },
|
|
78
|
+
'o3-mini-2025-01-31': { contextWindow: 200000 },
|
|
79
|
+
o1: { contextWindow: 200000 },
|
|
80
|
+
'o1-2024-12-17': { contextWindow: 200000 },
|
|
81
|
+
'gpt-4.1': { contextWindow: 1047576 },
|
|
82
|
+
'gpt-4.1-2025-04-14': { contextWindow: 1047576 },
|
|
83
|
+
'gpt-4.1-mini': { contextWindow: 1047576 },
|
|
84
|
+
'gpt-4.1-mini-2025-04-14': { contextWindow: 1047576 },
|
|
85
|
+
'gpt-4.1-nano': { contextWindow: 1047576 },
|
|
86
|
+
'gpt-4.1-nano-2025-04-14': { contextWindow: 1047576 },
|
|
87
|
+
'gpt-4o': { contextWindow: 128000 },
|
|
88
|
+
'gpt-4o-2024-05-13': { contextWindow: 128000 },
|
|
89
|
+
'gpt-4o-2024-08-06': { contextWindow: 128000 },
|
|
90
|
+
'gpt-4o-2024-11-20': { contextWindow: 128000 },
|
|
91
|
+
'gpt-4o-mini': { contextWindow: 128000 },
|
|
92
|
+
'gpt-4o-mini-2024-07-18': { contextWindow: 128000 },
|
|
93
|
+
'gpt-3.5-turbo': { contextWindow: 16385 },
|
|
94
|
+
'gpt-3.5-turbo-0125': { contextWindow: 16385 }
|
|
95
|
+
}
|
|
96
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { IProviderConfig, IProviderInfo, IProviderModelInfo, IProviderRegistry } from '../tokens';
|
|
2
|
+
export declare function getProviderModelInfo(providerInfo: IProviderInfo | null | undefined, model: string | undefined): IProviderModelInfo | undefined;
|
|
3
|
+
export declare function getEffectiveContextWindow(providerConfig: IProviderConfig | undefined, providerRegistry?: IProviderRegistry): number | undefined;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const DATE_SUFFIX = /^(.*)-\d{4}-\d{2}-\d{2}$/;
|
|
2
|
+
const SHORT_VERSION_SUFFIX = /^(.*)-\d{4}$/;
|
|
3
|
+
// Treat rolling aliases and dated releases as the same model family so they
|
|
4
|
+
// can share provider metadata such as context windows.
|
|
5
|
+
function normalizeModelId(modelId) {
|
|
6
|
+
if (modelId.endsWith('-latest')) {
|
|
7
|
+
return modelId.slice(0, -7);
|
|
8
|
+
}
|
|
9
|
+
const dateSuffixMatch = modelId.match(DATE_SUFFIX);
|
|
10
|
+
if (dateSuffixMatch) {
|
|
11
|
+
return dateSuffixMatch[1];
|
|
12
|
+
}
|
|
13
|
+
const shortVersionSuffixMatch = modelId.match(SHORT_VERSION_SUFFIX);
|
|
14
|
+
if (shortVersionSuffixMatch) {
|
|
15
|
+
return shortVersionSuffixMatch[1];
|
|
16
|
+
}
|
|
17
|
+
return modelId;
|
|
18
|
+
}
|
|
19
|
+
function getCandidateModelIds(modelId) {
|
|
20
|
+
const candidates = [modelId];
|
|
21
|
+
const normalizedModelId = normalizeModelId(modelId);
|
|
22
|
+
candidates.push(normalizedModelId);
|
|
23
|
+
if (normalizedModelId !== modelId) {
|
|
24
|
+
candidates.push(`${normalizedModelId}-latest`);
|
|
25
|
+
}
|
|
26
|
+
return [...new Set(candidates)];
|
|
27
|
+
}
|
|
28
|
+
export function getProviderModelInfo(providerInfo, model) {
|
|
29
|
+
if (!providerInfo || !model) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
const modelInfo = providerInfo.modelInfo;
|
|
33
|
+
if (!modelInfo) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
for (const candidateId of getCandidateModelIds(model)) {
|
|
37
|
+
if (modelInfo[candidateId]) {
|
|
38
|
+
return modelInfo[candidateId];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const normalizedModelId = normalizeModelId(model);
|
|
42
|
+
// As a last resort, match any known model entry that normalizes to the same
|
|
43
|
+
// base ID, even if the exact alias/version string differs.
|
|
44
|
+
return Object.entries(modelInfo).find(([candidateId]) => {
|
|
45
|
+
return normalizeModelId(candidateId) === normalizedModelId;
|
|
46
|
+
})?.[1];
|
|
47
|
+
}
|
|
48
|
+
export function getEffectiveContextWindow(providerConfig, providerRegistry) {
|
|
49
|
+
if (!providerConfig) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
if (providerConfig.parameters?.contextWindow !== undefined) {
|
|
53
|
+
return providerConfig.parameters.contextWindow;
|
|
54
|
+
}
|
|
55
|
+
const providerInfo = providerRegistry?.getProviderInfo(providerConfig.provider);
|
|
56
|
+
return getProviderModelInfo(providerInfo, providerConfig.model)
|
|
57
|
+
?.contextWindow;
|
|
58
|
+
}
|
package/lib/tokens.d.ts
CHANGED
|
@@ -153,6 +153,12 @@ export interface IProviderToolCapabilities {
|
|
|
153
153
|
/**
|
|
154
154
|
* Provider information
|
|
155
155
|
*/
|
|
156
|
+
export interface IProviderModelInfo {
|
|
157
|
+
/**
|
|
158
|
+
* Default context window for the model in tokens.
|
|
159
|
+
*/
|
|
160
|
+
contextWindow?: number;
|
|
161
|
+
}
|
|
156
162
|
export interface IProviderInfo {
|
|
157
163
|
/**
|
|
158
164
|
* Unique identifier for the provider
|
|
@@ -173,6 +179,10 @@ export interface IProviderInfo {
|
|
|
173
179
|
* Default model names for this provider
|
|
174
180
|
*/
|
|
175
181
|
defaultModels: string[];
|
|
182
|
+
/**
|
|
183
|
+
* Optional per-model metadata keyed by model ID.
|
|
184
|
+
*/
|
|
185
|
+
modelInfo?: Record<string, IProviderModelInfo>;
|
|
176
186
|
/**
|
|
177
187
|
* Whether this provider supports custom base URLs
|
|
178
188
|
*/
|
|
@@ -246,6 +256,7 @@ export interface IProviderParameters {
|
|
|
246
256
|
temperature?: number;
|
|
247
257
|
maxOutputTokens?: number;
|
|
248
258
|
maxTurns?: number;
|
|
259
|
+
contextWindow?: number;
|
|
249
260
|
supportsFillInMiddle?: boolean;
|
|
250
261
|
useFilterText?: boolean;
|
|
251
262
|
}
|
|
@@ -282,6 +293,7 @@ export interface IAIConfig {
|
|
|
282
293
|
toolsEnabled: boolean;
|
|
283
294
|
sendWithShiftEnter: boolean;
|
|
284
295
|
showTokenUsage: boolean;
|
|
296
|
+
showContextUsage: boolean;
|
|
285
297
|
commandsRequiringApproval: string[];
|
|
286
298
|
commandsAutoRenderMimeBundles: string[];
|
|
287
299
|
trustedMimeTypesForAutoRender: string[];
|
|
@@ -633,6 +645,15 @@ export interface ITokenUsage {
|
|
|
633
645
|
* Number of output tokens generated (completion tokens)
|
|
634
646
|
*/
|
|
635
647
|
outputTokens: number;
|
|
648
|
+
/**
|
|
649
|
+
* Estimated prompt tokens used by the most recent model request.
|
|
650
|
+
* This is based on the final step of the latest request.
|
|
651
|
+
*/
|
|
652
|
+
lastRequestInputTokens?: number;
|
|
653
|
+
/**
|
|
654
|
+
* Configured context window size for the active provider/model.
|
|
655
|
+
*/
|
|
656
|
+
contextWindow?: number;
|
|
636
657
|
}
|
|
637
658
|
/**
|
|
638
659
|
* The string that replaces a secret key in settings.
|