@jupyterlite/ai 0.12.0 → 0.14.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 (49) hide show
  1. package/lib/agent.d.ts +36 -2
  2. package/lib/agent.js +249 -24
  3. package/lib/{chat-model-registry.d.ts → chat-model-handler.d.ts} +12 -11
  4. package/lib/{chat-model-registry.js → chat-model-handler.js} +6 -40
  5. package/lib/chat-model.d.ts +8 -0
  6. package/lib/chat-model.js +156 -8
  7. package/lib/completion/completion-provider.d.ts +1 -1
  8. package/lib/completion/completion-provider.js +14 -2
  9. package/lib/components/model-select.js +4 -4
  10. package/lib/components/tool-select.d.ts +11 -2
  11. package/lib/components/tool-select.js +77 -18
  12. package/lib/index.d.ts +3 -3
  13. package/lib/index.js +249 -117
  14. package/lib/models/settings-model.d.ts +2 -0
  15. package/lib/models/settings-model.js +2 -0
  16. package/lib/providers/built-in-providers.js +33 -32
  17. package/lib/providers/provider-tools.d.ts +36 -0
  18. package/lib/providers/provider-tools.js +93 -0
  19. package/lib/rendered-message-outputarea.d.ts +24 -0
  20. package/lib/rendered-message-outputarea.js +48 -0
  21. package/lib/tokens.d.ts +65 -7
  22. package/lib/tokens.js +1 -1
  23. package/lib/tools/commands.js +62 -22
  24. package/lib/tools/web.d.ts +8 -0
  25. package/lib/tools/web.js +196 -0
  26. package/lib/widgets/ai-settings.d.ts +4 -9
  27. package/lib/widgets/ai-settings.js +123 -69
  28. package/lib/widgets/main-area-chat.d.ts +6 -0
  29. package/lib/widgets/main-area-chat.js +28 -0
  30. package/lib/widgets/provider-config-dialog.js +211 -11
  31. package/package.json +17 -11
  32. package/schema/settings-model.json +89 -1
  33. package/src/agent.ts +327 -42
  34. package/src/{chat-model-registry.ts → chat-model-handler.ts} +16 -51
  35. package/src/chat-model.ts +223 -14
  36. package/src/completion/completion-provider.ts +26 -12
  37. package/src/components/model-select.tsx +4 -5
  38. package/src/components/tool-select.tsx +110 -7
  39. package/src/index.ts +359 -184
  40. package/src/models/settings-model.ts +6 -0
  41. package/src/providers/built-in-providers.ts +33 -32
  42. package/src/providers/provider-tools.ts +179 -0
  43. package/src/rendered-message-outputarea.ts +62 -0
  44. package/src/tokens.ts +82 -9
  45. package/src/tools/commands.ts +99 -31
  46. package/src/tools/web.ts +238 -0
  47. package/src/widgets/ai-settings.tsx +279 -124
  48. package/src/widgets/main-area-chat.ts +34 -1
  49. package/src/widgets/provider-config-dialog.tsx +504 -11
package/lib/agent.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { ISignal } from '@lumino/signaling';
2
2
  import { type Tool } from 'ai';
3
3
  import { ISecretsManager } from 'jupyter-secrets-manager';
4
+ import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
4
5
  import { AISettingsModel } from './models/settings-model';
5
6
  import type { IProviderRegistry } from './tokens';
6
7
  import { ISkillRegistry, ITool, IToolRegistry, ITokenUsage } from './tokens';
@@ -22,7 +23,7 @@ export declare namespace AgentManagerFactory {
22
23
  /**
23
24
  * The token used to request the secrets manager.
24
25
  */
25
- token: symbol;
26
+ token: symbol | null;
26
27
  }
27
28
  }
28
29
  export declare class AgentManagerFactory {
@@ -92,7 +93,7 @@ export interface IAgentEventTypeMap {
92
93
  tool_call_complete: {
93
94
  callId: string;
94
95
  toolName: string;
95
- output: string;
96
+ outputData: unknown;
96
97
  isError: boolean;
97
98
  };
98
99
  tool_approval_request: {
@@ -148,6 +149,10 @@ export interface IAgentManagerOptions {
148
149
  * Initial token usage.
149
150
  */
150
151
  tokenUsage?: ITokenUsage;
152
+ /**
153
+ * JupyterLab render mime registry for discovering supported MIME types.
154
+ */
155
+ renderMimeRegistry?: IRenderMimeRegistry;
151
156
  }
152
157
  /**
153
158
  * Manages the AI agent lifecycle and execution loop.
@@ -245,6 +250,10 @@ export declare class AgentManager {
245
250
  * Prepare model, tools, and settings needed to (re)build the agent.
246
251
  */
247
252
  private _prepareAgentConfig;
253
+ /**
254
+ * Build the runtime tool map used by the agent.
255
+ */
256
+ private _buildRuntimeTools;
248
257
  /**
249
258
  * Rebuild the agent using cached resources and the current skills snapshot.
250
259
  */
@@ -264,6 +273,14 @@ export declare class AgentManager {
264
273
  * Handles tool-result stream parts.
265
274
  */
266
275
  private _handleToolResult;
276
+ /**
277
+ * Handles tool-error stream parts.
278
+ */
279
+ private _handleToolError;
280
+ /**
281
+ * Handles tool-output-denied stream parts.
282
+ */
283
+ private _handleToolOutputDenied;
267
284
  /**
268
285
  * Handles tool-approval-request stream parts.
269
286
  */
@@ -296,6 +313,22 @@ export declare class AgentManager {
296
313
  * @returns The enhanced system prompt with dynamic additions
297
314
  */
298
315
  private _getEnhancedSystemPrompt;
316
+ /**
317
+ * Build an instruction line describing MIME types supported by this session.
318
+ */
319
+ private _getSupportedMimeTypesInstruction;
320
+ /**
321
+ * Sanitizes history to ensure it's in a valid state in case of abort or error.
322
+ */
323
+ private _sanitizeHistory;
324
+ /**
325
+ * Extracts tool call IDs from a message
326
+ */
327
+ private _getToolCallIds;
328
+ /**
329
+ * Checks if a tool message contains results for all specified tool call IDs
330
+ */
331
+ private _matchesAllToolCalls;
299
332
  private _settingsModel;
300
333
  private _toolRegistry?;
301
334
  private _providerRegistry?;
@@ -312,6 +345,7 @@ export declare class AgentManager {
312
345
  private _activeProvider;
313
346
  private _activeProviderChanged;
314
347
  private _skills;
348
+ private _renderMimeRegistry?;
315
349
  private _initQueue;
316
350
  private _agentConfig;
317
351
  private _pendingApprovals;
package/lib/agent.js CHANGED
@@ -2,6 +2,7 @@ import { Signal } from '@lumino/signaling';
2
2
  import { ToolLoopAgent, stepCountIs } from 'ai';
3
3
  import { createMCPClient } from '@ai-sdk/mcp';
4
4
  import { createModel } from './providers/models';
5
+ import { createProviderTools } from './providers/provider-tools';
5
6
  import { SECRETS_NAMESPACE } from './tokens';
6
7
  export class AgentManagerFactory {
7
8
  constructor(options) {
@@ -20,6 +21,10 @@ export class AgentManagerFactory {
20
21
  this._initializeAgents().catch(error => console.warn('Failed to initialize agent in constructor:', error));
21
22
  // Listen for settings changes
22
23
  this._settingsModel.stateChanged.connect(this._onSettingsChanged, this);
24
+ // Disable the secrets manager if the token is empty.
25
+ if (!options.token) {
26
+ this._secretsManager = undefined;
27
+ }
23
28
  }
24
29
  createAgent(options) {
25
30
  const agentManager = new AgentManager({
@@ -28,6 +33,16 @@ export class AgentManagerFactory {
28
33
  secretsManager: this._secretsManager
29
34
  });
30
35
  this._agentManagers.push(agentManager);
36
+ // New chats can be created before MCP setup finishes.
37
+ // Reinitialize them with connected MCP tools once it does.
38
+ this._initQueue
39
+ .then(() => this.getMCPTools())
40
+ .then(mcpTools => {
41
+ if (Object.keys(mcpTools).length > 0) {
42
+ agentManager.initializeAgent(mcpTools);
43
+ }
44
+ })
45
+ .catch(error => console.warn('Failed to pass MCP tools to new agent:', error));
31
46
  return agentManager;
32
47
  }
33
48
  /**
@@ -180,6 +195,7 @@ export class AgentManager {
180
195
  this._tokenUsageChanged = new Signal(this);
181
196
  this._skills = [];
182
197
  this._agentConfig = null;
198
+ this._renderMimeRegistry = options.renderMimeRegistry;
183
199
  this.activeProvider =
184
200
  options.activeProvider ?? this._settingsModel.config.defaultProvider;
185
201
  // Initialize selected tools to all available tools by default
@@ -372,7 +388,7 @@ export class AgentManager {
372
388
  this._updateTokenUsage(await result.usage);
373
389
  // Add response messages to history
374
390
  if (responseMessages.messages?.length) {
375
- this._history.push(...responseMessages.messages);
391
+ this._history.push(...Private.sanitizeModelMessages(responseMessages.messages));
376
392
  }
377
393
  // Add approval response if processed
378
394
  if (streamResult.approvalResponse) {
@@ -400,6 +416,9 @@ export class AgentManager {
400
416
  data: { error: error }
401
417
  });
402
418
  }
419
+ // After an error (including AbortError), sanitize the history
420
+ // to remove any trailing assistant messages without tool results
421
+ this._sanitizeHistory();
403
422
  }
404
423
  finally {
405
424
  this._controller = null;
@@ -454,18 +473,27 @@ export class AgentManager {
454
473
  this._mcpTools = mcpTools;
455
474
  }
456
475
  const model = await this._createModel();
457
- const shouldUseTools = !!(config.toolsEnabled &&
458
- this._selectedToolNames.length > 0 &&
459
- this._toolRegistry &&
460
- Object.keys(this._toolRegistry.tools).length > 0 &&
461
- this._supportsToolCalling());
462
- const tools = shouldUseTools
463
- ? { ...this.selectedAgentTools, ...this._mcpTools }
464
- : this._mcpTools;
476
+ const supportsToolCalling = this._supportsToolCalling();
477
+ const canUseTools = config.toolsEnabled && supportsToolCalling;
478
+ const hasFunctionToolRegistry = !!(this._toolRegistry && Object.keys(this._toolRegistry.tools).length > 0);
479
+ const selectedFunctionTools = canUseTools && hasFunctionToolRegistry ? this.selectedAgentTools : {};
480
+ const functionTools = canUseTools
481
+ ? { ...selectedFunctionTools, ...this._mcpTools }
482
+ : {};
465
483
  const activeProviderConfig = this._settingsModel.getProvider(this._activeProvider);
484
+ const activeProviderInfo = activeProviderConfig && this._providerRegistry
485
+ ? this._providerRegistry.getProviderInfo(activeProviderConfig.provider)
486
+ : null;
466
487
  const temperature = activeProviderConfig?.parameters?.temperature ?? DEFAULT_TEMPERATURE;
467
488
  const maxTokens = activeProviderConfig?.parameters?.maxOutputTokens;
468
489
  const maxTurns = activeProviderConfig?.parameters?.maxTurns ?? DEFAULT_MAX_TURNS;
490
+ const tools = this._buildRuntimeTools({
491
+ providerInfo: activeProviderInfo,
492
+ customSettings: activeProviderConfig?.customSettings,
493
+ functionTools,
494
+ includeProviderTools: canUseTools
495
+ });
496
+ const shouldUseTools = canUseTools && Object.keys(tools).length > 0;
469
497
  this._agentConfig = {
470
498
  model,
471
499
  tools,
@@ -476,6 +504,22 @@ export class AgentManager {
476
504
  shouldUseTools
477
505
  };
478
506
  }
507
+ /**
508
+ * Build the runtime tool map used by the agent.
509
+ */
510
+ _buildRuntimeTools(options) {
511
+ const providerTools = options.includeProviderTools
512
+ ? createProviderTools({
513
+ providerInfo: options.providerInfo,
514
+ customSettings: options.customSettings,
515
+ hasFunctionTools: Object.keys(options.functionTools).length > 0
516
+ })
517
+ : {};
518
+ return {
519
+ ...providerTools,
520
+ ...options.functionTools
521
+ };
522
+ }
479
523
  /**
480
524
  * Rebuild the agent using cached resources and the current skills snapshot.
481
525
  */
@@ -485,9 +529,21 @@ export class AgentManager {
485
529
  return;
486
530
  }
487
531
  const { model, tools, temperature, maxOutputTokens, maxTurns, baseSystemPrompt, shouldUseTools } = this._agentConfig;
488
- const instructions = shouldUseTools
489
- ? this._getEnhancedSystemPrompt(baseSystemPrompt)
532
+ const baseInstructions = shouldUseTools
533
+ ? this._getEnhancedSystemPrompt(baseSystemPrompt, tools)
490
534
  : baseSystemPrompt || 'You are a helpful assistant.';
535
+ const richOutputWorkflowInstruction = shouldUseTools
536
+ ? '- When the user asks for visual or rich outputs, prefer running code/commands that produce those outputs and describe that they will be rendered in chat.'
537
+ : '- When tools are unavailable, explain the limitation clearly and provide concrete steps the user can run to produce the desired rich outputs.';
538
+ const supportedMimeTypesInstruction = this._getSupportedMimeTypesInstruction();
539
+ const instructions = `${baseInstructions}
540
+
541
+ RICH OUTPUT RENDERING:
542
+ - The chat UI can render rich MIME outputs as separate assistant messages.
543
+ - ${supportedMimeTypesInstruction}
544
+ - Use only MIME types from the supported list when creating MIME bundles. Do not invent MIME keys.
545
+ - Do not claim that you cannot display maps, images, or rich outputs in chat.
546
+ ${richOutputWorkflowInstruction}`;
491
547
  this._agent = new ToolLoopAgent({
492
548
  model,
493
549
  instructions,
@@ -546,6 +602,12 @@ export class AgentManager {
546
602
  case 'tool-result':
547
603
  this._handleToolResult(part);
548
604
  break;
605
+ case 'tool-error':
606
+ this._handleToolError(part);
607
+ break;
608
+ case 'tool-output-denied':
609
+ this._handleToolOutputDenied(part);
610
+ break;
549
611
  case 'tool-approval-request':
550
612
  // Complete current message before approval
551
613
  if (currentMessageId && fullResponse) {
@@ -579,9 +641,6 @@ export class AgentManager {
579
641
  * Handles tool-result stream parts.
580
642
  */
581
643
  _handleToolResult(part) {
582
- const output = typeof part.output === 'string'
583
- ? part.output
584
- : JSON.stringify(part.output, null, 2);
585
644
  const isError = typeof part.output === 'object' &&
586
645
  part.output !== null &&
587
646
  'success' in part.output &&
@@ -591,11 +650,44 @@ export class AgentManager {
591
650
  data: {
592
651
  callId: part.toolCallId,
593
652
  toolName: part.toolName,
594
- output,
653
+ outputData: part.output,
595
654
  isError
596
655
  }
597
656
  });
598
657
  }
658
+ /**
659
+ * Handles tool-error stream parts.
660
+ */
661
+ _handleToolError(part) {
662
+ const output = typeof part.error === 'string'
663
+ ? part.error
664
+ : part.error instanceof Error
665
+ ? part.error.message
666
+ : JSON.stringify(part.error, null, 2);
667
+ this._agentEvent.emit({
668
+ type: 'tool_call_complete',
669
+ data: {
670
+ callId: part.toolCallId,
671
+ toolName: part.toolName,
672
+ outputData: output,
673
+ isError: true
674
+ }
675
+ });
676
+ }
677
+ /**
678
+ * Handles tool-output-denied stream parts.
679
+ */
680
+ _handleToolOutputDenied(part) {
681
+ this._agentEvent.emit({
682
+ type: 'tool_call_complete',
683
+ data: {
684
+ callId: part.toolCallId,
685
+ toolName: part.toolName,
686
+ outputData: 'Tool output was denied.',
687
+ isError: true
688
+ }
689
+ });
690
+ }
599
691
  /**
600
692
  * Handles tool-approval-request stream parts.
601
693
  */
@@ -681,8 +773,16 @@ export class AgentManager {
681
773
  const baseURL = activeProviderConfig.baseURL;
682
774
  let apiKey;
683
775
  if (this._secretsManager && this._settingsModel.config.useSecretsManager) {
684
- apiKey =
685
- (await this._secretsManager.get(Private.getToken(), SECRETS_NAMESPACE, `${provider}:apiKey`))?.value ?? '';
776
+ const token = Private.getToken();
777
+ if (!token) {
778
+ // This should never happen, the secrets manager should be disabled.
779
+ console.error('@jupyterlite/ai::AgentManager error: the settings manager token is not set.\nYou should disable the the secrets manager from the AI settings.');
780
+ apiKey = '';
781
+ }
782
+ else {
783
+ apiKey =
784
+ (await this._secretsManager.get(token, SECRETS_NAMESPACE, `${provider}:apiKey`))?.value ?? '';
785
+ }
686
786
  }
687
787
  else {
688
788
  apiKey = this._settingsModel.getApiKey(activeProviderConfig.id);
@@ -699,12 +799,11 @@ export class AgentManager {
699
799
  * @param baseSystemPrompt The base system prompt from settings
700
800
  * @returns The enhanced system prompt with dynamic additions
701
801
  */
702
- _getEnhancedSystemPrompt(baseSystemPrompt) {
703
- if (this._skills.length === 0) {
704
- return baseSystemPrompt;
705
- }
706
- const lines = this._skills.map(skill => `- ${skill.name}: ${skill.description}`);
707
- const skillsPrompt = `
802
+ _getEnhancedSystemPrompt(baseSystemPrompt, tools) {
803
+ let prompt = baseSystemPrompt;
804
+ if (this._skills.length > 0) {
805
+ const lines = this._skills.map(skill => `- ${skill.name}: ${skill.description}`);
806
+ const skillsPrompt = `
708
807
 
709
808
  AGENT SKILLS:
710
809
  Skills are provided via the skills registry and accessed through tools (not commands).
@@ -716,7 +815,116 @@ If the load_skill result includes a non-empty "resources" array, those are bundl
716
815
  AVAILABLE SKILLS (preloaded snapshot):
717
816
  ${lines.join('\n')}
718
817
  `;
719
- return baseSystemPrompt + skillsPrompt;
818
+ prompt += skillsPrompt;
819
+ }
820
+ const toolNames = new Set(Object.keys(tools));
821
+ const hasBrowserFetch = toolNames.has('browser_fetch');
822
+ const hasWebFetch = toolNames.has('web_fetch');
823
+ const hasWebSearch = toolNames.has('web_search');
824
+ if (hasBrowserFetch || hasWebFetch || hasWebSearch) {
825
+ const webRetrievalPrompt = `
826
+
827
+ WEB RETRIEVAL POLICY:
828
+ - If the user asks about a specific URL and browser_fetch is available, call browser_fetch first for that URL.
829
+ - If browser_fetch fails due to CORS/network/access, try web_fetch (if available) for that same URL.
830
+ - If web_fetch fails with access/policy errors (for example: url_not_accessible or url_not_allowed) and browser_fetch is available, you MUST call browser_fetch for that same URL before searching.
831
+ - If either fetch method fails with temporary access/network issues (for example: network_or_cors), try the other fetch method if available before searching.
832
+ - Only fall back to web_search after both fetch methods fail or are unavailable.
833
+ - If the user explicitly asks to inspect one exact URL, do not skip directly to search unless both fetch methods fail or are unavailable.
834
+ - In your final response, state which retrieval method succeeded (browser_fetch, web_fetch, or web_search) and mention relevant limitations.
835
+ `;
836
+ prompt += webRetrievalPrompt;
837
+ }
838
+ return prompt;
839
+ }
840
+ /**
841
+ * Build an instruction line describing MIME types supported by this session.
842
+ */
843
+ _getSupportedMimeTypesInstruction() {
844
+ const mimeTypes = this._renderMimeRegistry?.mimeTypes ?? [];
845
+ const safeMimeTypes = mimeTypes.filter(mimeType => {
846
+ const factory = this._renderMimeRegistry?.getFactory(mimeType);
847
+ return !!factory?.safe;
848
+ });
849
+ if (safeMimeTypes.length === 0) {
850
+ return 'Supported MIME types are determined by the active JupyterLab renderers in this session.';
851
+ }
852
+ return `Supported MIME types in this session: ${safeMimeTypes.join(', ')}`;
853
+ }
854
+ /**
855
+ * Sanitizes history to ensure it's in a valid state in case of abort or error.
856
+ */
857
+ _sanitizeHistory() {
858
+ if (this._history.length === 0) {
859
+ return;
860
+ }
861
+ const newHistory = [];
862
+ for (let i = 0; i < this._history.length; i++) {
863
+ const msg = this._history[i];
864
+ if (msg.role === 'assistant') {
865
+ const toolCallIds = this._getToolCallIds(msg);
866
+ if (toolCallIds.length > 0) {
867
+ // Find if there's a following tool message with results for these calls
868
+ const nextMsg = this._history[i + 1];
869
+ if (nextMsg &&
870
+ nextMsg.role === 'tool' &&
871
+ this._matchesAllToolCalls(nextMsg, toolCallIds)) {
872
+ newHistory.push(msg);
873
+ }
874
+ else {
875
+ // Message has unmatched tool calls drop it and everything after it
876
+ break;
877
+ }
878
+ }
879
+ else {
880
+ newHistory.push(msg);
881
+ }
882
+ }
883
+ else if (msg.role === 'tool') {
884
+ // Tool messages are valid if they were preceded by a valid assistant message
885
+ newHistory.push(msg);
886
+ }
887
+ else {
888
+ newHistory.push(msg);
889
+ }
890
+ }
891
+ this._history = newHistory;
892
+ }
893
+ /**
894
+ * Extracts tool call IDs from a message
895
+ */
896
+ _getToolCallIds(message) {
897
+ const ids = [];
898
+ // Check content array for tool-call parts
899
+ if (Array.isArray(message.content)) {
900
+ for (const part of message.content) {
901
+ if (typeof part === 'object' &&
902
+ part !== null &&
903
+ 'type' in part &&
904
+ part.type === 'tool-call') {
905
+ ids.push(part.toolCallId);
906
+ }
907
+ }
908
+ }
909
+ return ids;
910
+ }
911
+ /**
912
+ * Checks if a tool message contains results for all specified tool call IDs
913
+ */
914
+ _matchesAllToolCalls(message, callIds) {
915
+ if (message.role !== 'tool' || !Array.isArray(message.content)) {
916
+ return false;
917
+ }
918
+ const resultIds = new Set();
919
+ for (const part of message.content) {
920
+ if (typeof part === 'object' &&
921
+ part !== null &&
922
+ 'type' in part &&
923
+ part.type === 'tool-result') {
924
+ resultIds.add(part.toolCallId);
925
+ }
926
+ }
927
+ return callIds.every(id => resultIds.has(id));
720
928
  }
721
929
  // Private attributes
722
930
  _settingsModel;
@@ -735,12 +943,29 @@ ${lines.join('\n')}
735
943
  _activeProvider = '';
736
944
  _activeProviderChanged = new Signal(this);
737
945
  _skills;
946
+ _renderMimeRegistry;
738
947
  _initQueue = Promise.resolve();
739
948
  _agentConfig;
740
949
  _pendingApprovals = new Map();
741
950
  }
742
951
  var Private;
743
952
  (function (Private) {
953
+ /**
954
+ * Keep only serializable messages by doing a JSON round-trip.
955
+ * Messages that cannot be serialized are dropped.
956
+ */
957
+ Private.sanitizeModelMessages = (messages) => {
958
+ const sanitized = [];
959
+ for (const message of messages) {
960
+ try {
961
+ sanitized.push(JSON.parse(JSON.stringify(message)));
962
+ }
963
+ catch {
964
+ // Drop messages that cannot be serialized
965
+ }
966
+ }
967
+ return sanitized;
968
+ };
744
969
  /**
745
970
  * The token to use with the secrets manager, setter and getter.
746
971
  */
@@ -3,33 +3,30 @@ import { TranslationBundle } from '@jupyterlab/translation';
3
3
  import { AgentManagerFactory } from './agent';
4
4
  import { AIChatModel } from './chat-model';
5
5
  import { AISettingsModel } from './models/settings-model';
6
- import { IChatModelRegistry, IProviderRegistry, ITokenUsage, IToolRegistry } from './tokens';
6
+ import { IChatModelHandler, IProviderRegistry, ITokenUsage, IToolRegistry } from './tokens';
7
7
  import { IDocumentManager } from '@jupyterlab/docmanager';
8
+ import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
8
9
  /**
9
- * The chat model registry.
10
+ * The chat model handler.
10
11
  */
11
- export declare class ChatModelRegistry implements IChatModelRegistry {
12
- constructor(options: ChatModelRegistry.IOptions);
13
- createModel(name?: string, activeProvider?: string, tokenUsage?: ITokenUsage): AIChatModel;
14
- add(model: AIChatModel): void;
15
- get(name: string): AIChatModel | undefined;
16
- getAll(): AIChatModel[];
17
- remove(name: string): void;
12
+ export declare class ChatModelHandler implements IChatModelHandler {
13
+ constructor(options: ChatModelHandler.IOptions);
14
+ createModel(name: string, activeProvider: string, tokenUsage?: ITokenUsage): AIChatModel;
18
15
  /**
19
16
  * Getter/setter for the active cell manager.
20
17
  */
21
18
  get activeCellManager(): ActiveCellManager | undefined;
22
19
  set activeCellManager(manager: ActiveCellManager | undefined);
23
- private _models;
24
20
  private _docManager;
25
21
  private _agentManagerFactory;
26
22
  private _settingsModel;
27
23
  private _toolRegistry?;
28
24
  private _providerRegistry?;
25
+ private _rmRegistry;
29
26
  private _activeCellManager?;
30
27
  private _trans;
31
28
  }
32
- export declare namespace ChatModelRegistry {
29
+ export declare namespace ChatModelHandler {
33
30
  interface IOptions {
34
31
  /**
35
32
  * The document manager.
@@ -51,6 +48,10 @@ export declare namespace ChatModelRegistry {
51
48
  * Optional provider registry for model creation
52
49
  */
53
50
  providerRegistry?: IProviderRegistry;
51
+ /**
52
+ * Render mime registry.
53
+ */
54
+ rmRegistry: IRenderMimeRegistry;
54
55
  /**
55
56
  * The active cell manager.
56
57
  */
@@ -1,15 +1,15 @@
1
1
  import { AIChatModel } from './chat-model';
2
- import { UUID } from '@lumino/coreutils';
3
2
  /**
4
- * The chat model registry.
3
+ * The chat model handler.
5
4
  */
6
- export class ChatModelRegistry {
5
+ export class ChatModelHandler {
7
6
  constructor(options) {
8
7
  this._docManager = options.docManager;
9
8
  this._agentManagerFactory = options.agentManagerFactory;
10
9
  this._settingsModel = options.settingsModel;
11
10
  this._toolRegistry = options.toolRegistry;
12
11
  this._providerRegistry = options.providerRegistry;
12
+ this._rmRegistry = options.rmRegistry;
13
13
  this._activeCellManager = options.activeCellManager;
14
14
  this._trans = options.trans;
15
15
  }
@@ -20,7 +20,8 @@ export class ChatModelRegistry {
20
20
  toolRegistry: this._toolRegistry,
21
21
  providerRegistry: this._providerRegistry,
22
22
  activeProvider,
23
- tokenUsage
23
+ tokenUsage,
24
+ renderMimeRegistry: this._rmRegistry
24
25
  });
25
26
  // Create AI chat model
26
27
  const model = new AIChatModel({
@@ -31,44 +32,9 @@ export class ChatModelRegistry {
31
32
  documentManager: this._docManager,
32
33
  trans: this._trans
33
34
  });
34
- // Set the name of the chat if not provided.
35
- // The name will be the name set by the user to the model if not already used by
36
- // another chat.
37
- if (!name || this._models.findIndex(m => m.name === name) !== -1) {
38
- const existingName = this.getAll().map(model => model.name);
39
- const modelName = this._settingsModel.getProvider(agentManager.activeProvider)?.name ||
40
- UUID.uuid4();
41
- name = modelName;
42
- let i = 1;
43
- while (existingName.includes(name)) {
44
- name = `${modelName}-${i}`;
45
- i += 1;
46
- }
47
- }
48
35
  model.name = name;
49
- this.add(model);
50
36
  return model;
51
37
  }
52
- add(model) {
53
- if (!this._models.find(m => m.name === model.name)) {
54
- this._models.push(model);
55
- model.disposed.connect(() => {
56
- this.remove(model.name);
57
- });
58
- }
59
- }
60
- get(name) {
61
- return this._models.find(m => m.name === name);
62
- }
63
- getAll() {
64
- return this._models;
65
- }
66
- remove(name) {
67
- const index = this._models.findIndex(m => m.name === name);
68
- if (index !== -1) {
69
- this._models.splice(index, 1);
70
- }
71
- }
72
38
  /**
73
39
  * Getter/setter for the active cell manager.
74
40
  */
@@ -78,12 +44,12 @@ export class ChatModelRegistry {
78
44
  set activeCellManager(manager) {
79
45
  this._activeCellManager = manager;
80
46
  }
81
- _models = [];
82
47
  _docManager;
83
48
  _agentManagerFactory;
84
49
  _settingsModel;
85
50
  _toolRegistry;
86
51
  _providerRegistry;
52
+ _rmRegistry;
87
53
  _activeCellManager;
88
54
  _trans;
89
55
  }
@@ -92,6 +92,10 @@ export declare class AIChatModel extends AbstractChatModel {
92
92
  * @returns A short summary string or empty string if none available
93
93
  */
94
94
  private _extractToolSummary;
95
+ /**
96
+ * Determine whether this tool call should auto-render trusted MIME bundles.
97
+ */
98
+ private _computeShouldAutoRenderMimeBundles;
95
99
  /**
96
100
  * Handles the start of a tool call execution.
97
101
  * @param event Event containing the tool call start data
@@ -101,6 +105,10 @@ export declare class AIChatModel extends AbstractChatModel {
101
105
  * Handles the completion of a tool call execution.
102
106
  */
103
107
  private _handleToolCallCompleteEvent;
108
+ /**
109
+ * Determine whether a tool call output should auto-render MIME bundles.
110
+ */
111
+ private _shouldAutoRenderMimeBundles;
104
112
  /**
105
113
  * Handles error events from the AI agent.
106
114
  */