@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/src/agent.ts CHANGED
@@ -7,14 +7,21 @@ import {
7
7
  type StreamTextResult,
8
8
  type Tool,
9
9
  type ToolApprovalRequestOutput,
10
+ type TypedToolError,
11
+ type TypedToolOutputDenied,
10
12
  type TypedToolResult
11
13
  } from 'ai';
12
14
  import { createMCPClient, type MCPClient } from '@ai-sdk/mcp';
13
15
  import { ISecretsManager } from 'jupyter-secrets-manager';
16
+ import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
14
17
 
15
18
  import { AISettingsModel } from './models/settings-model';
16
19
  import { createModel } from './providers/models';
17
- import type { IProviderRegistry } from './tokens';
20
+ import {
21
+ createProviderTools,
22
+ type IProviderCustomSettings
23
+ } from './providers/provider-tools';
24
+ import type { IProviderInfo, IProviderRegistry } from './tokens';
18
25
  import {
19
26
  ISkillRegistry,
20
27
  ISkillSummary,
@@ -65,7 +72,7 @@ export namespace AgentManagerFactory {
65
72
  /**
66
73
  * The token used to request the secrets manager.
67
74
  */
68
- token: symbol;
75
+ token: symbol | null;
69
76
  }
70
77
  }
71
78
  export class AgentManagerFactory {
@@ -90,6 +97,11 @@ export class AgentManagerFactory {
90
97
 
91
98
  // Listen for settings changes
92
99
  this._settingsModel.stateChanged.connect(this._onSettingsChanged, this);
100
+
101
+ // Disable the secrets manager if the token is empty.
102
+ if (!options.token) {
103
+ this._secretsManager = undefined;
104
+ }
93
105
  }
94
106
 
95
107
  createAgent(options: IAgentManagerOptions): AgentManager {
@@ -99,6 +111,20 @@ export class AgentManagerFactory {
99
111
  secretsManager: this._secretsManager
100
112
  });
101
113
  this._agentManagers.push(agentManager);
114
+
115
+ // New chats can be created before MCP setup finishes.
116
+ // Reinitialize them with connected MCP tools once it does.
117
+ this._initQueue
118
+ .then(() => this.getMCPTools())
119
+ .then(mcpTools => {
120
+ if (Object.keys(mcpTools).length > 0) {
121
+ agentManager.initializeAgent(mcpTools);
122
+ }
123
+ })
124
+ .catch(error =>
125
+ console.warn('Failed to pass MCP tools to new agent:', error)
126
+ );
127
+
102
128
  return agentManager;
103
129
  }
104
130
 
@@ -266,7 +292,7 @@ export interface IAgentEventTypeMap {
266
292
  tool_call_complete: {
267
293
  callId: string;
268
294
  toolName: string;
269
- output: string;
295
+ outputData: unknown;
270
296
  isError: boolean;
271
297
  };
272
298
  tool_approval_request: {
@@ -347,6 +373,11 @@ export interface IAgentManagerOptions {
347
373
  * Initial token usage.
348
374
  */
349
375
  tokenUsage?: ITokenUsage;
376
+
377
+ /**
378
+ * JupyterLab render mime registry for discovering supported MIME types.
379
+ */
380
+ renderMimeRegistry?: IRenderMimeRegistry;
350
381
  }
351
382
 
352
383
  /**
@@ -379,6 +410,7 @@ export class AgentManager {
379
410
  this._tokenUsageChanged = new Signal<this, ITokenUsage>(this);
380
411
  this._skills = [];
381
412
  this._agentConfig = null;
413
+ this._renderMimeRegistry = options.renderMimeRegistry;
382
414
 
383
415
  this.activeProvider =
384
416
  options.activeProvider ?? this._settingsModel.config.defaultProvider;
@@ -607,7 +639,9 @@ export class AgentManager {
607
639
 
608
640
  // Add response messages to history
609
641
  if (responseMessages.messages?.length) {
610
- this._history.push(...responseMessages.messages);
642
+ this._history.push(
643
+ ...Private.sanitizeModelMessages(responseMessages.messages)
644
+ );
611
645
  }
612
646
 
613
647
  // Add approval response if processed
@@ -637,6 +671,9 @@ export class AgentManager {
637
671
  data: { error: error as Error }
638
672
  });
639
673
  }
674
+ // After an error (including AbortError), sanitize the history
675
+ // to remove any trailing assistant messages without tool results
676
+ this._sanitizeHistory();
640
677
  } finally {
641
678
  this._controller = null;
642
679
  }
@@ -697,21 +734,24 @@ export class AgentManager {
697
734
 
698
735
  const model = await this._createModel();
699
736
 
700
- const shouldUseTools = !!(
701
- config.toolsEnabled &&
702
- this._selectedToolNames.length > 0 &&
703
- this._toolRegistry &&
704
- Object.keys(this._toolRegistry.tools).length > 0 &&
705
- this._supportsToolCalling()
737
+ const supportsToolCalling = this._supportsToolCalling();
738
+ const canUseTools = config.toolsEnabled && supportsToolCalling;
739
+ const hasFunctionToolRegistry = !!(
740
+ this._toolRegistry && Object.keys(this._toolRegistry.tools).length > 0
706
741
  );
707
-
708
- const tools = shouldUseTools
709
- ? { ...this.selectedAgentTools, ...this._mcpTools }
710
- : this._mcpTools;
742
+ const selectedFunctionTools =
743
+ canUseTools && hasFunctionToolRegistry ? this.selectedAgentTools : {};
744
+ const functionTools = canUseTools
745
+ ? { ...selectedFunctionTools, ...this._mcpTools }
746
+ : {};
711
747
 
712
748
  const activeProviderConfig = this._settingsModel.getProvider(
713
749
  this._activeProvider
714
750
  );
751
+ const activeProviderInfo =
752
+ activeProviderConfig && this._providerRegistry
753
+ ? this._providerRegistry.getProviderInfo(activeProviderConfig.provider)
754
+ : null;
715
755
 
716
756
  const temperature =
717
757
  activeProviderConfig?.parameters?.temperature ?? DEFAULT_TEMPERATURE;
@@ -719,6 +759,15 @@ export class AgentManager {
719
759
  const maxTurns =
720
760
  activeProviderConfig?.parameters?.maxTurns ?? DEFAULT_MAX_TURNS;
721
761
 
762
+ const tools = this._buildRuntimeTools({
763
+ providerInfo: activeProviderInfo,
764
+ customSettings: activeProviderConfig?.customSettings,
765
+ functionTools,
766
+ includeProviderTools: canUseTools
767
+ });
768
+
769
+ const shouldUseTools = canUseTools && Object.keys(tools).length > 0;
770
+
722
771
  this._agentConfig = {
723
772
  model,
724
773
  tools,
@@ -730,6 +779,29 @@ export class AgentManager {
730
779
  };
731
780
  }
732
781
 
782
+ /**
783
+ * Build the runtime tool map used by the agent.
784
+ */
785
+ private _buildRuntimeTools(options: {
786
+ providerInfo?: IProviderInfo | null;
787
+ customSettings?: IProviderCustomSettings;
788
+ functionTools: ToolMap;
789
+ includeProviderTools: boolean;
790
+ }): ToolMap {
791
+ const providerTools = options.includeProviderTools
792
+ ? createProviderTools({
793
+ providerInfo: options.providerInfo,
794
+ customSettings: options.customSettings,
795
+ hasFunctionTools: Object.keys(options.functionTools).length > 0
796
+ })
797
+ : {};
798
+
799
+ return {
800
+ ...providerTools,
801
+ ...options.functionTools
802
+ };
803
+ }
804
+
733
805
  /**
734
806
  * Rebuild the agent using cached resources and the current skills snapshot.
735
807
  */
@@ -749,9 +821,22 @@ export class AgentManager {
749
821
  shouldUseTools
750
822
  } = this._agentConfig;
751
823
 
752
- const instructions = shouldUseTools
753
- ? this._getEnhancedSystemPrompt(baseSystemPrompt)
824
+ const baseInstructions = shouldUseTools
825
+ ? this._getEnhancedSystemPrompt(baseSystemPrompt, tools)
754
826
  : baseSystemPrompt || 'You are a helpful assistant.';
827
+ const richOutputWorkflowInstruction = shouldUseTools
828
+ ? '- 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.'
829
+ : '- When tools are unavailable, explain the limitation clearly and provide concrete steps the user can run to produce the desired rich outputs.';
830
+ const supportedMimeTypesInstruction =
831
+ this._getSupportedMimeTypesInstruction();
832
+ const instructions = `${baseInstructions}
833
+
834
+ RICH OUTPUT RENDERING:
835
+ - The chat UI can render rich MIME outputs as separate assistant messages.
836
+ - ${supportedMimeTypesInstruction}
837
+ - Use only MIME types from the supported list when creating MIME bundles. Do not invent MIME keys.
838
+ - Do not claim that you cannot display maps, images, or rich outputs in chat.
839
+ ${richOutputWorkflowInstruction}`;
755
840
 
756
841
  this._agent = new ToolLoopAgent({
757
842
  model,
@@ -818,6 +903,14 @@ export class AgentManager {
818
903
  this._handleToolResult(part);
819
904
  break;
820
905
 
906
+ case 'tool-error':
907
+ this._handleToolError(part);
908
+ break;
909
+
910
+ case 'tool-output-denied':
911
+ this._handleToolOutputDenied(part);
912
+ break;
913
+
821
914
  case 'tool-approval-request':
822
915
  // Complete current message before approval
823
916
  if (currentMessageId && fullResponse) {
@@ -856,10 +949,6 @@ export class AgentManager {
856
949
  * Handles tool-result stream parts.
857
950
  */
858
951
  private _handleToolResult(part: TypedToolResult<ToolMap>): void {
859
- const output =
860
- typeof part.output === 'string'
861
- ? part.output
862
- : JSON.stringify(part.output, null, 2);
863
952
  const isError =
864
953
  typeof part.output === 'object' &&
865
954
  part.output !== null &&
@@ -871,12 +960,49 @@ export class AgentManager {
871
960
  data: {
872
961
  callId: part.toolCallId,
873
962
  toolName: part.toolName,
874
- output,
963
+ outputData: part.output,
875
964
  isError
876
965
  }
877
966
  });
878
967
  }
879
968
 
969
+ /**
970
+ * Handles tool-error stream parts.
971
+ */
972
+ private _handleToolError(part: TypedToolError<ToolMap>): void {
973
+ const output =
974
+ typeof part.error === 'string'
975
+ ? part.error
976
+ : part.error instanceof Error
977
+ ? part.error.message
978
+ : JSON.stringify(part.error, null, 2);
979
+
980
+ this._agentEvent.emit({
981
+ type: 'tool_call_complete',
982
+ data: {
983
+ callId: part.toolCallId,
984
+ toolName: part.toolName,
985
+ outputData: output,
986
+ isError: true
987
+ }
988
+ });
989
+ }
990
+
991
+ /**
992
+ * Handles tool-output-denied stream parts.
993
+ */
994
+ private _handleToolOutputDenied(part: TypedToolOutputDenied<ToolMap>): void {
995
+ this._agentEvent.emit({
996
+ type: 'tool_call_complete',
997
+ data: {
998
+ callId: part.toolCallId,
999
+ toolName: part.toolName,
1000
+ outputData: 'Tool output was denied.',
1001
+ isError: true
1002
+ }
1003
+ });
1004
+ }
1005
+
880
1006
  /**
881
1007
  * Handles tool-approval-request stream parts.
882
1008
  */
@@ -980,14 +1106,23 @@ export class AgentManager {
980
1106
 
981
1107
  let apiKey: string;
982
1108
  if (this._secretsManager && this._settingsModel.config.useSecretsManager) {
983
- apiKey =
984
- (
985
- await this._secretsManager.get(
986
- Private.getToken(),
987
- SECRETS_NAMESPACE,
988
- `${provider}:apiKey`
989
- )
990
- )?.value ?? '';
1109
+ const token = Private.getToken();
1110
+ if (!token) {
1111
+ // This should never happen, the secrets manager should be disabled.
1112
+ console.error(
1113
+ '@jupyterlite/ai::AgentManager error: the settings manager token is not set.\nYou should disable the the secrets manager from the AI settings.'
1114
+ );
1115
+ apiKey = '';
1116
+ } else {
1117
+ apiKey =
1118
+ (
1119
+ await this._secretsManager.get(
1120
+ token,
1121
+ SECRETS_NAMESPACE,
1122
+ `${provider}:apiKey`
1123
+ )
1124
+ )?.value ?? '';
1125
+ }
991
1126
  } else {
992
1127
  apiKey = this._settingsModel.getApiKey(activeProviderConfig.id);
993
1128
  }
@@ -1008,15 +1143,17 @@ export class AgentManager {
1008
1143
  * @param baseSystemPrompt The base system prompt from settings
1009
1144
  * @returns The enhanced system prompt with dynamic additions
1010
1145
  */
1011
- private _getEnhancedSystemPrompt(baseSystemPrompt: string): string {
1012
- if (this._skills.length === 0) {
1013
- return baseSystemPrompt;
1014
- }
1015
-
1016
- const lines = this._skills.map(
1017
- skill => `- ${skill.name}: ${skill.description}`
1018
- );
1019
- const skillsPrompt = `
1146
+ private _getEnhancedSystemPrompt(
1147
+ baseSystemPrompt: string,
1148
+ tools: ToolMap
1149
+ ): string {
1150
+ let prompt = baseSystemPrompt;
1151
+
1152
+ if (this._skills.length > 0) {
1153
+ const lines = this._skills.map(
1154
+ skill => `- ${skill.name}: ${skill.description}`
1155
+ );
1156
+ const skillsPrompt = `
1020
1157
 
1021
1158
  AGENT SKILLS:
1022
1159
  Skills are provided via the skills registry and accessed through tools (not commands).
@@ -1028,8 +1165,137 @@ If the load_skill result includes a non-empty "resources" array, those are bundl
1028
1165
  AVAILABLE SKILLS (preloaded snapshot):
1029
1166
  ${lines.join('\n')}
1030
1167
  `;
1168
+ prompt += skillsPrompt;
1169
+ }
1170
+
1171
+ const toolNames = new Set(Object.keys(tools));
1172
+ const hasBrowserFetch = toolNames.has('browser_fetch');
1173
+ const hasWebFetch = toolNames.has('web_fetch');
1174
+ const hasWebSearch = toolNames.has('web_search');
1175
+
1176
+ if (hasBrowserFetch || hasWebFetch || hasWebSearch) {
1177
+ const webRetrievalPrompt = `
1178
+
1179
+ WEB RETRIEVAL POLICY:
1180
+ - If the user asks about a specific URL and browser_fetch is available, call browser_fetch first for that URL.
1181
+ - If browser_fetch fails due to CORS/network/access, try web_fetch (if available) for that same URL.
1182
+ - 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.
1183
+ - If either fetch method fails with temporary access/network issues (for example: network_or_cors), try the other fetch method if available before searching.
1184
+ - Only fall back to web_search after both fetch methods fail or are unavailable.
1185
+ - If the user explicitly asks to inspect one exact URL, do not skip directly to search unless both fetch methods fail or are unavailable.
1186
+ - In your final response, state which retrieval method succeeded (browser_fetch, web_fetch, or web_search) and mention relevant limitations.
1187
+ `;
1188
+ prompt += webRetrievalPrompt;
1189
+ }
1190
+
1191
+ return prompt;
1192
+ }
1193
+
1194
+ /**
1195
+ * Build an instruction line describing MIME types supported by this session.
1196
+ */
1197
+ private _getSupportedMimeTypesInstruction(): string {
1198
+ const mimeTypes = this._renderMimeRegistry?.mimeTypes ?? [];
1199
+ const safeMimeTypes = mimeTypes.filter(mimeType => {
1200
+ const factory = this._renderMimeRegistry?.getFactory(mimeType);
1201
+ return !!factory?.safe;
1202
+ });
1031
1203
 
1032
- return baseSystemPrompt + skillsPrompt;
1204
+ if (safeMimeTypes.length === 0) {
1205
+ return 'Supported MIME types are determined by the active JupyterLab renderers in this session.';
1206
+ }
1207
+
1208
+ return `Supported MIME types in this session: ${safeMimeTypes.join(', ')}`;
1209
+ }
1210
+
1211
+ /**
1212
+ * Sanitizes history to ensure it's in a valid state in case of abort or error.
1213
+ */
1214
+ private _sanitizeHistory(): void {
1215
+ if (this._history.length === 0) {
1216
+ return;
1217
+ }
1218
+
1219
+ const newHistory: ModelMessage[] = [];
1220
+ for (let i = 0; i < this._history.length; i++) {
1221
+ const msg = this._history[i];
1222
+
1223
+ if (msg.role === 'assistant') {
1224
+ const toolCallIds = this._getToolCallIds(msg);
1225
+ if (toolCallIds.length > 0) {
1226
+ // Find if there's a following tool message with results for these calls
1227
+ const nextMsg = this._history[i + 1];
1228
+ if (
1229
+ nextMsg &&
1230
+ nextMsg.role === 'tool' &&
1231
+ this._matchesAllToolCalls(nextMsg, toolCallIds)
1232
+ ) {
1233
+ newHistory.push(msg);
1234
+ } else {
1235
+ // Message has unmatched tool calls drop it and everything after it
1236
+ break;
1237
+ }
1238
+ } else {
1239
+ newHistory.push(msg);
1240
+ }
1241
+ } else if (msg.role === 'tool') {
1242
+ // Tool messages are valid if they were preceded by a valid assistant message
1243
+ newHistory.push(msg);
1244
+ } else {
1245
+ newHistory.push(msg);
1246
+ }
1247
+ }
1248
+
1249
+ this._history = newHistory;
1250
+ }
1251
+
1252
+ /**
1253
+ * Extracts tool call IDs from a message
1254
+ */
1255
+ private _getToolCallIds(message: ModelMessage): string[] {
1256
+ const ids: string[] = [];
1257
+
1258
+ // Check content array for tool-call parts
1259
+ if (Array.isArray(message.content)) {
1260
+ for (const part of message.content) {
1261
+ if (
1262
+ typeof part === 'object' &&
1263
+ part !== null &&
1264
+ 'type' in part &&
1265
+ part.type === 'tool-call'
1266
+ ) {
1267
+ ids.push(part.toolCallId);
1268
+ }
1269
+ }
1270
+ }
1271
+
1272
+ return ids;
1273
+ }
1274
+
1275
+ /**
1276
+ * Checks if a tool message contains results for all specified tool call IDs
1277
+ */
1278
+ private _matchesAllToolCalls(
1279
+ message: ModelMessage,
1280
+ callIds: string[]
1281
+ ): boolean {
1282
+ if (message.role !== 'tool' || !Array.isArray(message.content)) {
1283
+ return false;
1284
+ }
1285
+
1286
+ const resultIds = new Set<string>();
1287
+ for (const part of message.content) {
1288
+ if (
1289
+ typeof part === 'object' &&
1290
+ part !== null &&
1291
+ 'type' in part &&
1292
+ part.type === 'tool-result'
1293
+ ) {
1294
+ resultIds.add(part.toolCallId);
1295
+ }
1296
+ }
1297
+
1298
+ return callIds.every(id => resultIds.has(id));
1033
1299
  }
1034
1300
 
1035
1301
  // Private attributes
@@ -1049,6 +1315,7 @@ ${lines.join('\n')}
1049
1315
  private _activeProvider: string = '';
1050
1316
  private _activeProviderChanged = new Signal<this, string | undefined>(this);
1051
1317
  private _skills: ISkillSummary[];
1318
+ private _renderMimeRegistry?: IRenderMimeRegistry;
1052
1319
  private _initQueue: Promise<void> = Promise.resolve();
1053
1320
  private _agentConfig: IAgentConfig | null;
1054
1321
  private _pendingApprovals: Map<
@@ -1058,14 +1325,32 @@ ${lines.join('\n')}
1058
1325
  }
1059
1326
 
1060
1327
  namespace Private {
1328
+ /**
1329
+ * Keep only serializable messages by doing a JSON round-trip.
1330
+ * Messages that cannot be serialized are dropped.
1331
+ */
1332
+ export const sanitizeModelMessages = (
1333
+ messages: ModelMessage[]
1334
+ ): ModelMessage[] => {
1335
+ const sanitized: ModelMessage[] = [];
1336
+ for (const message of messages) {
1337
+ try {
1338
+ sanitized.push(JSON.parse(JSON.stringify(message)));
1339
+ } catch {
1340
+ // Drop messages that cannot be serialized
1341
+ }
1342
+ }
1343
+ return sanitized;
1344
+ };
1345
+
1061
1346
  /**
1062
1347
  * The token to use with the secrets manager, setter and getter.
1063
1348
  */
1064
- let secretsToken: symbol;
1065
- export function setToken(value: symbol): void {
1349
+ let secretsToken: symbol | null;
1350
+ export function setToken(value: symbol | null): void {
1066
1351
  secretsToken = value;
1067
1352
  }
1068
- export function getToken(): symbol {
1353
+ export function getToken(): symbol | null {
1069
1354
  return secretsToken;
1070
1355
  }
1071
1356
  }
@@ -4,31 +4,32 @@ import { AgentManagerFactory } from './agent';
4
4
  import { AIChatModel } from './chat-model';
5
5
  import { AISettingsModel } from './models/settings-model';
6
6
  import {
7
- IChatModelRegistry,
7
+ IChatModelHandler,
8
8
  IProviderRegistry,
9
9
  ITokenUsage,
10
10
  IToolRegistry
11
11
  } from './tokens';
12
12
  import { IDocumentManager } from '@jupyterlab/docmanager';
13
- import { UUID } from '@lumino/coreutils';
13
+ import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
14
14
 
15
15
  /**
16
- * The chat model registry.
16
+ * The chat model handler.
17
17
  */
18
- export class ChatModelRegistry implements IChatModelRegistry {
19
- constructor(options: ChatModelRegistry.IOptions) {
18
+ export class ChatModelHandler implements IChatModelHandler {
19
+ constructor(options: ChatModelHandler.IOptions) {
20
20
  this._docManager = options.docManager;
21
21
  this._agentManagerFactory = options.agentManagerFactory;
22
22
  this._settingsModel = options.settingsModel;
23
23
  this._toolRegistry = options.toolRegistry;
24
24
  this._providerRegistry = options.providerRegistry;
25
+ this._rmRegistry = options.rmRegistry;
25
26
  this._activeCellManager = options.activeCellManager;
26
27
  this._trans = options.trans;
27
28
  }
28
29
 
29
30
  createModel(
30
- name?: string,
31
- activeProvider?: string,
31
+ name: string,
32
+ activeProvider: string,
32
33
  tokenUsage?: ITokenUsage
33
34
  ): AIChatModel {
34
35
  // Create Agent Manager first so it can be shared
@@ -37,7 +38,8 @@ export class ChatModelRegistry implements IChatModelRegistry {
37
38
  toolRegistry: this._toolRegistry,
38
39
  providerRegistry: this._providerRegistry,
39
40
  activeProvider,
40
- tokenUsage
41
+ tokenUsage,
42
+ renderMimeRegistry: this._rmRegistry
41
43
  });
42
44
 
43
45
  // Create AI chat model
@@ -50,52 +52,11 @@ export class ChatModelRegistry implements IChatModelRegistry {
50
52
  trans: this._trans
51
53
  });
52
54
 
53
- // Set the name of the chat if not provided.
54
- // The name will be the name set by the user to the model if not already used by
55
- // another chat.
56
- if (!name || this._models.findIndex(m => m.name === name) !== -1) {
57
- const existingName = this.getAll().map(model => model.name);
58
-
59
- const modelName =
60
- this._settingsModel.getProvider(agentManager.activeProvider)?.name ||
61
- UUID.uuid4();
62
- name = modelName;
63
- let i = 1;
64
- while (existingName.includes(name)) {
65
- name = `${modelName}-${i}`;
66
- i += 1;
67
- }
68
- }
69
55
  model.name = name;
70
- this.add(model);
71
56
 
72
57
  return model;
73
58
  }
74
59
 
75
- add(model: AIChatModel): void {
76
- if (!this._models.find(m => m.name === model.name)) {
77
- this._models.push(model);
78
- model.disposed.connect(() => {
79
- this.remove(model.name);
80
- });
81
- }
82
- }
83
-
84
- get(name: string): AIChatModel | undefined {
85
- return this._models.find(m => m.name === name);
86
- }
87
-
88
- getAll(): AIChatModel[] {
89
- return this._models;
90
- }
91
-
92
- remove(name: string): void {
93
- const index = this._models.findIndex(m => m.name === name);
94
- if (index !== -1) {
95
- this._models.splice(index, 1);
96
- }
97
- }
98
-
99
60
  /**
100
61
  * Getter/setter for the active cell manager.
101
62
  */
@@ -106,17 +67,17 @@ export class ChatModelRegistry implements IChatModelRegistry {
106
67
  this._activeCellManager = manager;
107
68
  }
108
69
 
109
- private _models: AIChatModel[] = [];
110
70
  private _docManager: IDocumentManager;
111
71
  private _agentManagerFactory: AgentManagerFactory;
112
72
  private _settingsModel: AISettingsModel;
113
73
  private _toolRegistry?: IToolRegistry;
114
74
  private _providerRegistry?: IProviderRegistry;
75
+ private _rmRegistry: IRenderMimeRegistry;
115
76
  private _activeCellManager?: ActiveCellManager;
116
77
  private _trans: TranslationBundle;
117
78
  }
118
79
 
119
- export namespace ChatModelRegistry {
80
+ export namespace ChatModelHandler {
120
81
  export interface IOptions {
121
82
  /**
122
83
  * The document manager.
@@ -138,6 +99,10 @@ export namespace ChatModelRegistry {
138
99
  * Optional provider registry for model creation
139
100
  */
140
101
  providerRegistry?: IProviderRegistry;
102
+ /**
103
+ * Render mime registry.
104
+ */
105
+ rmRegistry: IRenderMimeRegistry;
141
106
  /**
142
107
  * The active cell manager.
143
108
  */