@jupyterlite/ai 0.15.0 → 0.17.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 (45) hide show
  1. package/lib/agent.d.ts +12 -2
  2. package/lib/agent.js +112 -17
  3. package/lib/chat-commands/clear.js +1 -1
  4. package/lib/chat-model-handler.js +4 -1
  5. package/lib/chat-model.d.ts +25 -24
  6. package/lib/chat-model.js +262 -132
  7. package/lib/components/clear-button.d.ts +1 -1
  8. package/lib/components/clear-button.js +1 -1
  9. package/lib/components/index.d.ts +1 -1
  10. package/lib/components/index.js +1 -1
  11. package/lib/components/{token-usage-display.d.ts → usage-display.d.ts} +11 -11
  12. package/lib/components/usage-display.js +109 -0
  13. package/lib/index.js +205 -20
  14. package/lib/models/settings-model.js +1 -0
  15. package/lib/providers/built-in-providers.js +5 -0
  16. package/lib/providers/generated-context-windows.d.ts +8 -0
  17. package/lib/providers/generated-context-windows.js +96 -0
  18. package/lib/providers/model-info.d.ts +3 -0
  19. package/lib/providers/model-info.js +58 -0
  20. package/lib/tokens.d.ts +34 -3
  21. package/lib/tokens.js +8 -7
  22. package/lib/widgets/ai-settings.js +9 -0
  23. package/lib/widgets/main-area-chat.d.ts +1 -0
  24. package/lib/widgets/main-area-chat.js +10 -4
  25. package/lib/widgets/provider-config-dialog.js +18 -5
  26. package/package.json +3 -2
  27. package/schema/settings-model.json +11 -0
  28. package/src/agent.ts +151 -21
  29. package/src/chat-commands/clear.ts +1 -1
  30. package/src/chat-model-handler.ts +6 -1
  31. package/src/chat-model.ts +350 -175
  32. package/src/components/clear-button.tsx +3 -3
  33. package/src/components/index.ts +1 -1
  34. package/src/components/usage-display.tsx +208 -0
  35. package/src/index.ts +250 -26
  36. package/src/models/settings-model.ts +1 -0
  37. package/src/providers/built-in-providers.ts +5 -0
  38. package/src/providers/generated-context-windows.ts +102 -0
  39. package/src/providers/model-info.ts +88 -0
  40. package/src/tokens.ts +46 -10
  41. package/src/widgets/ai-settings.tsx +42 -0
  42. package/src/widgets/main-area-chat.ts +12 -4
  43. package/src/widgets/provider-config-dialog.tsx +45 -5
  44. package/lib/components/token-usage-display.js +0 -72
  45. package/src/components/token-usage-display.tsx +0 -137
@@ -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
+ }
package/src/tokens.ts CHANGED
@@ -4,7 +4,7 @@ import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
4
4
  import { Token } from '@lumino/coreutils';
5
5
  import type { IDisposable } from '@lumino/disposable';
6
6
  import { ISignal } from '@lumino/signaling';
7
- import type { Tool, LanguageModel } from 'ai';
7
+ import type { Tool, LanguageModel, UserContent, ModelMessage } from 'ai';
8
8
  import { ISecretsManager } from 'jupyter-secrets-manager';
9
9
 
10
10
  import type { IModelOptions } from './providers/models';
@@ -30,6 +30,7 @@ export namespace CommandIds {
30
30
  export const openSettings = '@jupyterlite/ai:open-settings';
31
31
  export const reposition = '@jupyterlite/ai:reposition';
32
32
  export const openChat = '@jupyterlite/ai:open-chat';
33
+ export const openOrRevealChat = '@jupyterlite/ai:open-or-reveal-chat';
33
34
  export const moveChat = '@jupyterlite/ai:move-chat';
34
35
  export const refreshSkills = '@jupyterlite/ai:refresh-skills';
35
36
  export const saveChat = '@jupyterlite/ai:save-chat';
@@ -102,7 +103,7 @@ export interface IToolRegistry {
102
103
  * The tool registry token.
103
104
  */
104
105
  export const IToolRegistry = new Token<IToolRegistry>(
105
- '@jupyterlite/ai:tool-registry',
106
+ '@jupyterlite/ai:IToolRegistry',
106
107
  'Tool registry for AI agent functionality'
107
108
  );
108
109
 
@@ -145,7 +146,7 @@ export interface ISkillRegistry {
145
146
  * The skill registry token.
146
147
  */
147
148
  export const ISkillRegistry = new Token<ISkillRegistry>(
148
- '@jupyterlite/ai:skill-registry',
149
+ '@jupyterlite/ai:ISkillRegistry',
149
150
  'Skill registry for AI agent functionality'
150
151
  );
151
152
 
@@ -204,6 +205,13 @@ export interface IProviderToolCapabilities {
204
205
  /**
205
206
  * Provider information
206
207
  */
208
+ export interface IProviderModelInfo {
209
+ /**
210
+ * Default context window for the model in tokens.
211
+ */
212
+ contextWindow?: number;
213
+ }
214
+
207
215
  export interface IProviderInfo {
208
216
  /**
209
217
  * Unique identifier for the provider
@@ -228,6 +236,11 @@ export interface IProviderInfo {
228
236
  */
229
237
  defaultModels: string[];
230
238
 
239
+ /**
240
+ * Optional per-model metadata keyed by model ID.
241
+ */
242
+ modelInfo?: Record<string, IProviderModelInfo>;
243
+
231
244
  /**
232
245
  * Whether this provider supports custom base URLs
233
246
  */
@@ -311,7 +324,7 @@ export interface IProviderRegistry {
311
324
  * Token for the provider registry.
312
325
  */
313
326
  export const IProviderRegistry = new Token<IProviderRegistry>(
314
- '@jupyterlite/ai:provider-registry',
327
+ '@jupyterlite/ai:IProviderRegistry',
315
328
  'Registry for AI providers'
316
329
  );
317
330
 
@@ -321,6 +334,7 @@ export interface IProviderParameters {
321
334
  temperature?: number;
322
335
  maxOutputTokens?: number;
323
336
  maxTurns?: number;
337
+ contextWindow?: number;
324
338
  supportsFillInMiddle?: boolean;
325
339
  useFilterText?: boolean;
326
340
  }
@@ -368,6 +382,8 @@ export interface IAIConfig {
368
382
  sendWithShiftEnter: boolean;
369
383
  // Token usage display setting
370
384
  showTokenUsage: boolean;
385
+ // Context usage display setting
386
+ showContextUsage: boolean;
371
387
  // Commands that require approval before execution
372
388
  commandsRequiringApproval: string[];
373
389
  // Commands whose execute_command outputs may auto-render MIME bundles in chat
@@ -569,7 +585,7 @@ export interface IAgentManager {
569
585
  /**
570
586
  * Clears conversation history and resets agent state.
571
587
  */
572
- clearHistory(): void;
588
+ clearHistory(): Promise<void>;
573
589
  /**
574
590
  * Sets the conversation history with a list of messages from the chat.
575
591
  * @param messages The chat messages to set as history
@@ -596,7 +612,12 @@ export interface IAgentManager {
596
612
  * Handles the complete execution cycle including tool calls.
597
613
  * @param message The user message to respond to (may include processed attachment content)
598
614
  */
599
- generateResponse(message: string): Promise<void>;
615
+ generateResponse(message: UserContent): Promise<void>;
616
+ /**
617
+ * Create a transient language model to request a text response, which won't be added to history.
618
+ * @param messages - the messages sequence to send to the model.
619
+ */
620
+ textResponse(messages: ModelMessage[]): Promise<string>;
600
621
  /**
601
622
  * Initializes the AI agent with current settings and tools.
602
623
  * Sets up the agent with model configuration, tools, and MCP tools.
@@ -608,7 +629,7 @@ export interface IAgentManager {
608
629
  * Token for the agent manager.
609
630
  */
610
631
  export const IAgentManager = new Token<IAgentManager>(
611
- '@jupyterlite/ai:agent-manager'
632
+ '@jupyterlite/ai:IAgentManager'
612
633
  );
613
634
 
614
635
  /* The AGENT MANAGER FACTORY */
@@ -640,7 +661,7 @@ export interface IAgentManagerFactory {
640
661
  * Token for the agent manager factory.
641
662
  */
642
663
  export const IAgentManagerFactory = new Token<IAgentManagerFactory>(
643
- '@jupyterlite/ai:agent-manager-factory'
664
+ '@jupyterlite/ai:IAgentManagerFactory'
644
665
  );
645
666
 
646
667
  /* THE CHAT MODELS HANDLER */
@@ -680,12 +701,16 @@ export interface ICreateChatOptions {
680
701
  * Whether the chat is autosaved or not.
681
702
  */
682
703
  autosave?: boolean;
704
+ /**
705
+ * An optional title to the chat.
706
+ */
707
+ title?: string | null;
683
708
  }
684
709
  /**
685
710
  * Token for the chat model handler.
686
711
  */
687
712
  export const IChatModelHandler = new Token<IChatModelHandler>(
688
- '@jupyterlite/ai:chat-model-handler'
713
+ '@jupyterlite/ai:IChatModelHandler'
689
714
  );
690
715
 
691
716
  /* THE DIFF MANAGER */
@@ -760,7 +785,7 @@ export interface IDiffManager {
760
785
  * Token for the diff manager.
761
786
  */
762
787
  export const IDiffManager = new Token<IDiffManager>(
763
- '@jupyterlite/ai:diff-manager'
788
+ '@jupyterlite/ai:IDiffManager'
764
789
  );
765
790
 
766
791
  /**
@@ -776,6 +801,17 @@ export interface ITokenUsage {
776
801
  * Number of output tokens generated (completion tokens)
777
802
  */
778
803
  outputTokens: number;
804
+
805
+ /**
806
+ * Estimated prompt tokens used by the most recent model request.
807
+ * This is based on the final step of the latest request.
808
+ */
809
+ lastRequestInputTokens?: number;
810
+
811
+ /**
812
+ * Configured context window size for the active provider/model.
813
+ */
814
+ contextWindow?: number;
779
815
  }
780
816
 
781
817
  /**
@@ -45,6 +45,7 @@ import {
45
45
  createTheme
46
46
  } from '@mui/material';
47
47
  import React, { useEffect, useMemo, useState } from 'react';
48
+ import { getEffectiveContextWindow } from '../providers/model-info';
48
49
  import {
49
50
  type IAgentManagerFactory,
50
51
  type IAIConfig,
@@ -670,6 +671,10 @@ const AISettingsComponent: React.FC<IAISettingsComponentProps> = ({
670
671
  const providerToolCapabilities =
671
672
  providerInfo?.providerToolCapabilities;
672
673
  const params = provider.parameters;
674
+ const effectiveContextWindow = getEffectiveContextWindow(
675
+ provider,
676
+ providerRegistry
677
+ );
673
678
  const webSearchEnabled =
674
679
  !!providerToolCapabilities?.webSearch &&
675
680
  provider.customSettings?.webSearch?.enabled === true;
@@ -741,6 +746,7 @@ const AISettingsComponent: React.FC<IAISettingsComponentProps> = ({
741
746
  {(params?.temperature !== undefined ||
742
747
  params?.maxOutputTokens !== undefined ||
743
748
  params?.maxTurns !== undefined ||
749
+ effectiveContextWindow !== undefined ||
744
750
  webSearchEnabled ||
745
751
  webFetchEnabled) && (
746
752
  <Box
@@ -781,6 +787,16 @@ const AISettingsComponent: React.FC<IAISettingsComponentProps> = ({
781
787
  variant="outlined"
782
788
  />
783
789
  )}
790
+ {effectiveContextWindow !== undefined && (
791
+ <Chip
792
+ label={trans.__(
793
+ 'Context: %1',
794
+ effectiveContextWindow
795
+ )}
796
+ size="small"
797
+ variant="outlined"
798
+ />
799
+ )}
784
800
  {webSearchEnabled && (
785
801
  <Chip
786
802
  label={trans.__('Web Search')}
@@ -935,6 +951,32 @@ const AISettingsComponent: React.FC<IAISettingsComponentProps> = ({
935
951
  }
936
952
  />
937
953
 
954
+ <FormControlLabel
955
+ control={
956
+ <Switch
957
+ checked={config.showContextUsage}
958
+ onChange={e =>
959
+ handleConfigUpdate({
960
+ showContextUsage: e.target.checked
961
+ })
962
+ }
963
+ color="primary"
964
+ />
965
+ }
966
+ label={
967
+ <Box>
968
+ <Typography variant="body1">
969
+ {trans.__('Show Context Usage')}
970
+ </Typography>
971
+ <Typography variant="caption" color="text.secondary">
972
+ {trans.__(
973
+ 'Display estimated context usage in the chat toolbar'
974
+ )}
975
+ </Typography>
976
+ </Box>
977
+ }
978
+ />
979
+
938
980
  <FormControlLabel
939
981
  control={
940
982
  <Switch
@@ -6,7 +6,7 @@ import { CommandRegistry } from '@lumino/commands';
6
6
 
7
7
  import { AIChatModel } from '../chat-model';
8
8
  import { SaveComponentWidget } from '../components/save-button';
9
- import { TokenUsageWidget } from '../components/token-usage-display';
9
+ import { UsageWidget } from '../components/usage-display';
10
10
  import { RenderedMessageOutputAreaCompat } from '../rendered-message-outputarea';
11
11
  import { CommandIds, type IAISettingsModel } from '../tokens';
12
12
 
@@ -24,7 +24,8 @@ export namespace MainAreaChat {
24
24
  export class MainAreaChat extends MainAreaWidget<ChatWidget> {
25
25
  constructor(options: MainAreaChat.IOptions) {
26
26
  super(options);
27
- this.title.label = this.content.model.name;
27
+ this.title.label = this.model.name;
28
+ this.title.caption = this.model.title ?? this.model.name;
28
29
 
29
30
  const { trans } = options;
30
31
 
@@ -54,13 +55,13 @@ export class MainAreaChat extends MainAreaWidget<ChatWidget> {
54
55
  }
55
56
 
56
57
  // Add the token usage button.
57
- const tokenUsageWidget = new TokenUsageWidget({
58
+ const usageWidget = new UsageWidget({
58
59
  tokenUsageChanged: this.model.tokenUsageChanged,
59
60
  settingsModel: options.settingsModel,
60
61
  initialTokenUsage: this.model.agentManager.tokenUsage,
61
62
  translator: trans
62
63
  });
63
- this.toolbar.addItem('token-usage', tokenUsageWidget);
64
+ this.toolbar.addItem('usage', usageWidget);
64
65
 
65
66
  // Temporary compat: keep output-area CSS context for MIME renderers
66
67
  // until jupyter-chat provides it natively.
@@ -69,6 +70,8 @@ export class MainAreaChat extends MainAreaWidget<ChatWidget> {
69
70
  });
70
71
 
71
72
  this.model.writersChanged.connect(this._writersChanged);
73
+
74
+ this.model.titleChanged.connect(this._titleChanged);
72
75
  }
73
76
 
74
77
  dispose(): void {
@@ -76,6 +79,7 @@ export class MainAreaChat extends MainAreaWidget<ChatWidget> {
76
79
  // Dispose of the approval buttons widget when the chat is disposed.
77
80
  this._outputAreaCompat.dispose();
78
81
  this.model.writersChanged.disconnect(this._writersChanged);
82
+ this.model.titleChanged.disconnect(this._titleChanged);
79
83
  }
80
84
 
81
85
  /**
@@ -107,5 +111,9 @@ export class MainAreaChat extends MainAreaWidget<ChatWidget> {
107
111
  }
108
112
  };
109
113
 
114
+ private _titleChanged = () => {
115
+ this.title.caption = this.model.title ?? this.model.name;
116
+ };
117
+
110
118
  private _outputAreaCompat: RenderedMessageOutputAreaCompat;
111
119
  }
@@ -37,6 +37,7 @@ import type {
37
37
  IProviderRegistry,
38
38
  IProviderToolCapabilities
39
39
  } from '../tokens';
40
+ import { getProviderModelInfo } from '../providers/model-info';
40
41
 
41
42
  /**
42
43
  * Default parameter values for provider configuration
@@ -168,6 +169,10 @@ export const ProviderConfigDialog: React.FC<IProviderConfigDialogProps> = ({
168
169
  );
169
170
  const providerToolCapabilities =
170
171
  selectedProviderInfo?.providerToolCapabilities;
172
+ const selectedModelInfo = React.useMemo(
173
+ () => getProviderModelInfo(selectedProviderInfo, model),
174
+ [selectedProviderInfo, model]
175
+ );
171
176
  const webSearchImplementation =
172
177
  providerToolCapabilities?.webSearch?.implementation;
173
178
  const supportsWebSearch = !!providerToolCapabilities?.webSearch;
@@ -653,7 +658,7 @@ export const ProviderConfigDialog: React.FC<IProviderConfigDialogProps> = ({
653
658
  }
654
659
  placeholder={trans.__('Leave empty for provider default')}
655
660
  helperText={trans.__('Maximum length of AI responses')}
656
- inputProps={{ min: 1 }}
661
+ slotProps={{ htmlInput: { min: 1 } }}
657
662
  />
658
663
 
659
664
  <TextField
@@ -673,7 +678,42 @@ export const ProviderConfigDialog: React.FC<IProviderConfigDialogProps> = ({
673
678
  helperText={trans.__(
674
679
  'Maximum number of tool execution turns'
675
680
  )}
676
- inputProps={{ min: 1, max: 100 }}
681
+ slotProps={{ htmlInput: { min: 1, max: 100 } }}
682
+ />
683
+
684
+ <TextField
685
+ fullWidth
686
+ label={trans.__('Context Window (Optional)')}
687
+ type="number"
688
+ value={parameters.contextWindow ?? ''}
689
+ onChange={e =>
690
+ setParameters({
691
+ ...parameters,
692
+ contextWindow: e.target.value
693
+ ? Number(e.target.value)
694
+ : undefined
695
+ })
696
+ }
697
+ placeholder={
698
+ selectedModelInfo?.contextWindow !== undefined
699
+ ? trans.__(
700
+ 'Default: %1',
701
+ selectedModelInfo.contextWindow.toLocaleString()
702
+ )
703
+ : trans.__('e.g., 128000')
704
+ }
705
+ helperText={
706
+ selectedModelInfo?.contextWindow !== undefined &&
707
+ parameters.contextWindow === undefined
708
+ ? trans.__(
709
+ 'Using provider metadata default of %1 tokens for this model unless you override it here.',
710
+ selectedModelInfo.contextWindow.toLocaleString()
711
+ )
712
+ : trans.__(
713
+ 'Model context window size in tokens (used for context usage estimation)'
714
+ )
715
+ }
716
+ slotProps={{ htmlInput: { min: 1 } }}
677
717
  />
678
718
 
679
719
  <Typography
@@ -830,7 +870,7 @@ export const ProviderConfigDialog: React.FC<IProviderConfigDialogProps> = ({
830
870
  : undefined
831
871
  )
832
872
  }
833
- inputProps={{ min: 1 }}
873
+ slotProps={{ htmlInput: { min: 1 } }}
834
874
  />
835
875
  {renderDomainList(
836
876
  'webSearch.blockedDomains',
@@ -888,7 +928,7 @@ export const ProviderConfigDialog: React.FC<IProviderConfigDialogProps> = ({
888
928
  : undefined
889
929
  )
890
930
  }
891
- inputProps={{ min: 1 }}
931
+ slotProps={{ htmlInput: { min: 1 } }}
892
932
  />
893
933
  <TextField
894
934
  fullWidth
@@ -904,7 +944,7 @@ export const ProviderConfigDialog: React.FC<IProviderConfigDialogProps> = ({
904
944
  : undefined
905
945
  )
906
946
  }
907
- inputProps={{ min: 1 }}
947
+ slotProps={{ htmlInput: { min: 1 } }}
908
948
  />
909
949
  {renderDomainList(
910
950
  'webFetch.allowedDomains',
@@ -1,72 +0,0 @@
1
- import { ReactWidget, UseSignal } from '@jupyterlab/ui-components';
2
- import React from 'react';
3
- /**
4
- * React component that displays token usage information.
5
- * Shows input/output token counts with up/down arrows.
6
- * Only renders when token usage display is enabled in settings.
7
- */
8
- export const TokenUsageDisplay = ({ tokenUsageChanged, settingsModel, initialTokenUsage, translator: trans }) => {
9
- return (React.createElement(UseSignal, { signal: settingsModel.stateChanged, initialArgs: undefined }, () => {
10
- const config = settingsModel.config;
11
- if (!config.showTokenUsage) {
12
- return null;
13
- }
14
- return (React.createElement(UseSignal, { signal: tokenUsageChanged, initialArgs: initialTokenUsage }, (_, tokenUsage) => {
15
- if (!tokenUsage) {
16
- return null;
17
- }
18
- const total = tokenUsage.inputTokens + tokenUsage.outputTokens;
19
- if (total === 0) {
20
- return null;
21
- }
22
- return (React.createElement("div", { style: {
23
- display: 'flex',
24
- alignItems: 'center',
25
- gap: '6px',
26
- fontSize: '12px',
27
- color: 'var(--jp-ui-font-color2)',
28
- padding: '4px 8px',
29
- backgroundColor: 'var(--jp-layout-color1)',
30
- border: '1px solid var(--jp-border-color1)',
31
- borderRadius: '4px',
32
- whiteSpace: 'nowrap'
33
- }, title: trans.__('Token Usage - Sent: %1, Received: %2, Total: %3', tokenUsage.inputTokens.toLocaleString(), tokenUsage.outputTokens.toLocaleString(), total.toLocaleString()) },
34
- React.createElement("span", { style: {
35
- display: 'flex',
36
- alignItems: 'center',
37
- gap: '2px'
38
- } },
39
- React.createElement("span", null, "\u2191"),
40
- React.createElement("span", null, tokenUsage.inputTokens.toLocaleString())),
41
- React.createElement("span", { style: {
42
- display: 'flex',
43
- alignItems: 'center',
44
- gap: '2px'
45
- } },
46
- React.createElement("span", null, "\u2193"),
47
- React.createElement("span", null, tokenUsage.outputTokens.toLocaleString()))));
48
- }));
49
- }));
50
- };
51
- /**
52
- * JupyterLab widget wrapper for the TokenUsageDisplay component.
53
- * Extends ReactWidget to integrate with the JupyterLab widget system.
54
- */
55
- export class TokenUsageWidget extends ReactWidget {
56
- /**
57
- * Creates a new TokenUsageWidget instance.
58
- * @param options - Configuration options containing required models
59
- */
60
- constructor(options) {
61
- super();
62
- this._options = options;
63
- }
64
- /**
65
- * Renders the React component within the widget.
66
- * @returns The TokenUsageDisplay React element
67
- */
68
- render() {
69
- return React.createElement(TokenUsageDisplay, { ...this._options });
70
- }
71
- _options;
72
- }