@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.
- package/lib/agent.d.ts +36 -2
- package/lib/agent.js +249 -24
- package/lib/{chat-model-registry.d.ts → chat-model-handler.d.ts} +12 -11
- package/lib/{chat-model-registry.js → chat-model-handler.js} +6 -40
- package/lib/chat-model.d.ts +8 -0
- package/lib/chat-model.js +156 -8
- package/lib/completion/completion-provider.d.ts +1 -1
- package/lib/completion/completion-provider.js +14 -2
- package/lib/components/model-select.js +4 -4
- package/lib/components/tool-select.d.ts +11 -2
- package/lib/components/tool-select.js +77 -18
- package/lib/index.d.ts +3 -3
- package/lib/index.js +249 -117
- package/lib/models/settings-model.d.ts +2 -0
- package/lib/models/settings-model.js +2 -0
- package/lib/providers/built-in-providers.js +33 -32
- package/lib/providers/provider-tools.d.ts +36 -0
- package/lib/providers/provider-tools.js +93 -0
- package/lib/rendered-message-outputarea.d.ts +24 -0
- package/lib/rendered-message-outputarea.js +48 -0
- package/lib/tokens.d.ts +65 -7
- package/lib/tokens.js +1 -1
- package/lib/tools/commands.js +62 -22
- package/lib/tools/web.d.ts +8 -0
- package/lib/tools/web.js +196 -0
- package/lib/widgets/ai-settings.d.ts +4 -9
- package/lib/widgets/ai-settings.js +123 -69
- package/lib/widgets/main-area-chat.d.ts +6 -0
- package/lib/widgets/main-area-chat.js +28 -0
- package/lib/widgets/provider-config-dialog.js +211 -11
- package/package.json +17 -11
- package/schema/settings-model.json +89 -1
- package/src/agent.ts +327 -42
- package/src/{chat-model-registry.ts → chat-model-handler.ts} +16 -51
- package/src/chat-model.ts +223 -14
- package/src/completion/completion-provider.ts +26 -12
- package/src/components/model-select.tsx +4 -5
- package/src/components/tool-select.tsx +110 -7
- package/src/index.ts +359 -184
- package/src/models/settings-model.ts +6 -0
- package/src/providers/built-in-providers.ts +33 -32
- package/src/providers/provider-tools.ts +179 -0
- package/src/rendered-message-outputarea.ts +62 -0
- package/src/tokens.ts +82 -9
- package/src/tools/commands.ts +99 -31
- package/src/tools/web.ts +238 -0
- package/src/widgets/ai-settings.tsx +279 -124
- package/src/widgets/main-area-chat.ts +34 -1
- 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
|
-
|
|
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
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
|
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
|
-
|
|
685
|
-
|
|
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
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
10
|
+
* The chat model handler.
|
|
10
11
|
*/
|
|
11
|
-
export declare class
|
|
12
|
-
constructor(options:
|
|
13
|
-
createModel(name
|
|
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
|
|
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
|
|
3
|
+
* The chat model handler.
|
|
5
4
|
*/
|
|
6
|
-
export class
|
|
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
|
}
|
package/lib/chat-model.d.ts
CHANGED
|
@@ -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
|
*/
|