@jupyterlite/ai 0.18.0 → 0.19.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 (108) hide show
  1. package/lib/chat-commands/clear.d.ts +1 -0
  2. package/lib/chat-commands/index.d.ts +1 -0
  3. package/lib/chat-commands/skills.d.ts +2 -1
  4. package/lib/chat-model-handler.d.ts +3 -1
  5. package/lib/chat-model.d.ts +46 -8
  6. package/lib/chat-model.js +51 -21
  7. package/lib/completion/completion-provider.d.ts +3 -1
  8. package/lib/completion/completion-provider.js +1 -2
  9. package/lib/completion/index.d.ts +1 -0
  10. package/lib/components/clear-button.d.ts +1 -0
  11. package/lib/components/clear-button.js +3 -4
  12. package/lib/components/completion-status.d.ts +1 -0
  13. package/lib/components/completion-status.js +5 -4
  14. package/lib/components/index.d.ts +1 -0
  15. package/lib/components/model-select.d.ts +1 -0
  16. package/lib/components/model-select.js +62 -67
  17. package/lib/components/save-button.d.ts +1 -0
  18. package/lib/components/save-button.js +4 -5
  19. package/lib/components/stop-button.d.ts +1 -0
  20. package/lib/components/stop-button.js +3 -4
  21. package/lib/components/tool-select.d.ts +3 -1
  22. package/lib/components/tool-select.js +47 -60
  23. package/lib/components/usage-display.d.ts +4 -2
  24. package/lib/components/usage-display.js +50 -61
  25. package/lib/diff-manager.d.ts +3 -1
  26. package/lib/index.d.ts +3 -2
  27. package/lib/index.js +28 -17
  28. package/lib/models/settings-model.d.ts +3 -1
  29. package/lib/rendered-message-outputarea.d.ts +1 -0
  30. package/lib/tokens.d.ts +18 -640
  31. package/lib/tokens.js +2 -31
  32. package/lib/widgets/ai-settings.d.ts +3 -1
  33. package/lib/widgets/ai-settings.js +185 -349
  34. package/lib/widgets/main-area-chat.d.ts +1 -0
  35. package/lib/widgets/provider-config-dialog.d.ts +2 -1
  36. package/lib/widgets/provider-config-dialog.js +102 -167
  37. package/package.json +111 -258
  38. package/src/chat-commands/skills.ts +2 -2
  39. package/src/chat-model-handler.ts +6 -4
  40. package/src/chat-model.ts +66 -19
  41. package/src/completion/completion-provider.ts +6 -6
  42. package/src/components/clear-button.tsx +0 -2
  43. package/src/components/completion-status.tsx +2 -2
  44. package/src/components/model-select.tsx +1 -1
  45. package/src/components/stop-button.tsx +0 -2
  46. package/src/components/tool-select.tsx +10 -9
  47. package/src/components/usage-display.tsx +4 -2
  48. package/src/diff-manager.ts +4 -3
  49. package/src/index.ts +62 -44
  50. package/src/models/settings-model.ts +6 -6
  51. package/src/tokens.ts +23 -788
  52. package/src/widgets/ai-settings.tsx +14 -11
  53. package/src/widgets/provider-config-dialog.tsx +8 -8
  54. package/LICENSE +0 -30
  55. package/README.md +0 -49
  56. package/lib/agent.d.ts +0 -280
  57. package/lib/agent.js +0 -1103
  58. package/lib/icons.d.ts +0 -3
  59. package/lib/icons.js +0 -8
  60. package/lib/providers/built-in-providers.d.ts +0 -21
  61. package/lib/providers/built-in-providers.js +0 -233
  62. package/lib/providers/generated-model-info.d.ts +0 -8
  63. package/lib/providers/generated-model-info.js +0 -502
  64. package/lib/providers/model-info.d.ts +0 -6
  65. package/lib/providers/model-info.js +0 -91
  66. package/lib/providers/models.d.ts +0 -37
  67. package/lib/providers/models.js +0 -28
  68. package/lib/providers/provider-registry.d.ts +0 -49
  69. package/lib/providers/provider-registry.js +0 -72
  70. package/lib/providers/provider-tools.d.ts +0 -36
  71. package/lib/providers/provider-tools.js +0 -93
  72. package/lib/skills/index.d.ts +0 -4
  73. package/lib/skills/index.js +0 -7
  74. package/lib/skills/parse-skill.d.ts +0 -25
  75. package/lib/skills/parse-skill.js +0 -69
  76. package/lib/skills/skill-loader.d.ts +0 -25
  77. package/lib/skills/skill-loader.js +0 -133
  78. package/lib/skills/skill-registry.d.ts +0 -31
  79. package/lib/skills/skill-registry.js +0 -100
  80. package/lib/skills/types.d.ts +0 -29
  81. package/lib/skills/types.js +0 -5
  82. package/lib/tools/commands.d.ts +0 -11
  83. package/lib/tools/commands.js +0 -154
  84. package/lib/tools/skills.d.ts +0 -9
  85. package/lib/tools/skills.js +0 -73
  86. package/lib/tools/tool-registry.d.ts +0 -35
  87. package/lib/tools/tool-registry.js +0 -55
  88. package/lib/tools/web.d.ts +0 -8
  89. package/lib/tools/web.js +0 -196
  90. package/src/agent.ts +0 -1431
  91. package/src/icons.ts +0 -11
  92. package/src/providers/built-in-providers.ts +0 -241
  93. package/src/providers/generated-model-info.ts +0 -508
  94. package/src/providers/model-info.ts +0 -145
  95. package/src/providers/models.ts +0 -76
  96. package/src/providers/provider-registry.ts +0 -88
  97. package/src/providers/provider-tools.ts +0 -179
  98. package/src/skills/index.ts +0 -14
  99. package/src/skills/parse-skill.ts +0 -91
  100. package/src/skills/skill-loader.ts +0 -175
  101. package/src/skills/skill-registry.ts +0 -137
  102. package/src/skills/types.ts +0 -37
  103. package/src/tools/commands.ts +0 -210
  104. package/src/tools/skills.ts +0 -84
  105. package/src/tools/tool-registry.ts +0 -63
  106. package/src/tools/web.ts +0 -238
  107. package/src/types.d.ts +0 -4
  108. package/style/icons/jupyternaut-lite.svg +0 -7
package/src/agent.ts DELETED
@@ -1,1431 +0,0 @@
1
- import { createMCPClient, type MCPClient } from '@ai-sdk/mcp';
2
- import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
3
- import { PromiseDelegate } from '@lumino/coreutils';
4
- import { ISignal, Signal } from '@lumino/signaling';
5
- import {
6
- generateText,
7
- ToolLoopAgent,
8
- type ModelMessage,
9
- type LanguageModel,
10
- stepCountIs,
11
- type StreamTextResult,
12
- type ToolApprovalRequestOutput,
13
- type TypedToolError,
14
- type TypedToolOutputDenied,
15
- type TypedToolResult,
16
- type UserContent,
17
- type AssistantModelMessage,
18
- APICallError
19
- } from 'ai';
20
- import { ISecretsManager } from 'jupyter-secrets-manager';
21
-
22
- import { createModel } from './providers/models';
23
- import { getEffectiveContextWindow } from './providers/model-info';
24
- import {
25
- createProviderTools,
26
- type IProviderCustomSettings
27
- } from './providers/provider-tools';
28
- import {
29
- type IAgentManager,
30
- type IAgentManagerFactory,
31
- type IAISettingsModel,
32
- type IProviderInfo,
33
- type IProviderRegistry,
34
- type ISkillRegistry,
35
- type ISkillSummary,
36
- type ITool,
37
- type IToolRegistry,
38
- type ITokenUsage,
39
- type ToolMap,
40
- SECRETS_NAMESPACE
41
- } from './tokens';
42
-
43
- /**
44
- * Interface for MCP client wrapper to track connection state
45
- */
46
- interface IMCPClientWrapper {
47
- name: string;
48
- client: MCPClient;
49
- }
50
-
51
- /**
52
- * Result from processing a stream, including approval info if applicable.
53
- */
54
- interface IStreamProcessResult {
55
- /**
56
- * Whether an approval request was encountered and processed.
57
- */
58
- approvalProcessed: boolean;
59
- /**
60
- * Whether the stream was aborted before completion.
61
- */
62
- aborted: boolean;
63
- /**
64
- * The approval response message to add to history (if approval was processed).
65
- */
66
- approvalResponse?: ModelMessage;
67
- }
68
-
69
- /**
70
- * The agent manager factory namespace.
71
- */
72
- export namespace AgentManagerFactory {
73
- export interface IOptions {
74
- /**
75
- * The settings model.
76
- */
77
- settingsModel: IAISettingsModel;
78
- /**
79
- * The skill registry for discovering skills.
80
- */
81
- skillRegistry?: ISkillRegistry;
82
- /**
83
- * The secrets manager.
84
- */
85
- secretsManager?: ISecretsManager;
86
- /**
87
- * The token used to request the secrets manager.
88
- */
89
- token: symbol | null;
90
- }
91
- }
92
-
93
- /**
94
- * The agent manager factory.
95
- */
96
- export class AgentManagerFactory implements IAgentManagerFactory {
97
- constructor(options: AgentManagerFactory.IOptions) {
98
- Private.setToken(options.token);
99
- this._settingsModel = options.settingsModel;
100
- this._skillRegistry = options.skillRegistry;
101
- this._secretsManager = options.secretsManager;
102
- this._mcpClients = [];
103
- this._mcpConnectionChanged = new Signal<this, boolean>(this);
104
-
105
- if (this._skillRegistry) {
106
- this._skillRegistry.skillsChanged.connect(() => {
107
- this.refreshSkillSnapshots();
108
- });
109
- }
110
-
111
- // Initialize agent on construction
112
- this._initializeAgents().catch(error =>
113
- console.warn('Failed to initialize agent in constructor:', error)
114
- );
115
-
116
- // Listen for settings changes
117
- this._settingsModel.stateChanged.connect(this._onSettingsChanged, this);
118
-
119
- // Disable the secrets manager if the token is empty.
120
- if (!options.token) {
121
- this._secretsManager = undefined;
122
- }
123
- }
124
-
125
- /**
126
- * Create a new agent.
127
- */
128
- createAgent(options: IAgentManager.IOptions): IAgentManager {
129
- const agentManager = new AgentManager({
130
- ...options,
131
- skillRegistry: this._skillRegistry,
132
- secretsManager: this._secretsManager
133
- });
134
- this._agentManagers.push(agentManager);
135
-
136
- // New chats can be created before MCP setup finishes.
137
- // Reinitialize them with connected MCP tools once it does.
138
- this._initQueue
139
- .then(() => this.getMCPTools())
140
- .then(mcpTools => {
141
- if (Object.keys(mcpTools).length > 0) {
142
- agentManager.initializeAgent(mcpTools);
143
- }
144
- })
145
- .catch(error =>
146
- console.warn('Failed to pass MCP tools to new agent:', error)
147
- );
148
-
149
- return agentManager;
150
- }
151
-
152
- /**
153
- * Signal emitted when MCP connection status changes
154
- */
155
- get mcpConnectionChanged(): ISignal<this, boolean> {
156
- return this._mcpConnectionChanged;
157
- }
158
-
159
- /**
160
- * Checks whether a specific MCP server is connected.
161
- * @param serverName The name of the MCP server to check
162
- * @returns True if the server is connected, false otherwise
163
- */
164
- isMCPServerConnected(serverName: string): boolean {
165
- return this._mcpClients.some(wrapper => wrapper.name === serverName);
166
- }
167
-
168
- /**
169
- * Gets the MCP tools from connected servers
170
- */
171
- async getMCPTools(): Promise<ToolMap> {
172
- const mcpTools: ToolMap = {};
173
-
174
- for (const wrapper of this._mcpClients) {
175
- try {
176
- const tools = await wrapper.client.tools();
177
- Object.assign(mcpTools, tools);
178
- } catch (error) {
179
- console.warn(
180
- `Failed to get tools from MCP server ${wrapper.name}:`,
181
- error
182
- );
183
- }
184
- }
185
-
186
- return mcpTools;
187
- }
188
-
189
- /**
190
- * Handles settings changes and reinitializes the agent.
191
- */
192
- private _onSettingsChanged(): void {
193
- this._initializeAgents().catch(error =>
194
- console.warn('Failed to initialize agent on settings change:', error)
195
- );
196
- }
197
-
198
- /**
199
- * Initializes MCP (Model Context Protocol) clients based on current settings.
200
- * Closes existing clients and connects to enabled servers from configuration.
201
- */
202
- private async _initializeMCPClients(): Promise<void> {
203
- const config = this._settingsModel.config;
204
- const enabledServers = config.mcpServers.filter(server => server.enabled);
205
- let connectionChanged = false;
206
-
207
- // Close existing clients
208
- for (const wrapper of this._mcpClients) {
209
- try {
210
- await wrapper.client.close();
211
- connectionChanged = true;
212
- } catch (error) {
213
- console.warn('Error closing MCP client:', error);
214
- }
215
- }
216
- this._mcpClients = [];
217
-
218
- for (const serverConfig of enabledServers) {
219
- try {
220
- const client = await createMCPClient({
221
- transport: {
222
- type: 'http',
223
- url: serverConfig.url
224
- }
225
- });
226
-
227
- this._mcpClients.push({
228
- name: serverConfig.name,
229
- client
230
- });
231
- connectionChanged = true;
232
- } catch (error) {
233
- console.warn(
234
- `Failed to connect to MCP server "${serverConfig.name}" at ${serverConfig.url}:`,
235
- error
236
- );
237
- }
238
- }
239
-
240
- // Emit connection change signal if there were any changes
241
- if (connectionChanged) {
242
- this._mcpConnectionChanged.emit(this._mcpClients.length > 0);
243
- }
244
- }
245
-
246
- /**
247
- * Initializes the AI agent with current settings and tools.
248
- * Sets up the agent with model configuration, tools, and MCP servers.
249
- */
250
- private async _initializeAgents(): Promise<void> {
251
- this._initQueue = this._initQueue
252
- .catch(() => undefined)
253
- .then(async () => {
254
- try {
255
- await this._initializeMCPClients();
256
- const mcpTools = await this.getMCPTools();
257
-
258
- this._agentManagers.forEach(manager => {
259
- manager.initializeAgent(mcpTools);
260
- });
261
- } catch (error) {
262
- console.warn('Failed to initialize agents:', error);
263
- }
264
- });
265
- return this._initQueue;
266
- }
267
-
268
- /**
269
- * Refresh skill snapshots across all agents.
270
- */
271
- refreshSkillSnapshots(): void {
272
- this._agentManagers.forEach(manager => {
273
- manager.refreshSkills();
274
- });
275
- }
276
-
277
- private _agentManagers: IAgentManager[] = [];
278
- private _settingsModel: IAISettingsModel;
279
- private _skillRegistry?: ISkillRegistry;
280
- private _secretsManager?: ISecretsManager;
281
- private _mcpClients: IMCPClientWrapper[];
282
- private _mcpConnectionChanged: Signal<this, boolean>;
283
- private _initQueue: Promise<void> = Promise.resolve();
284
- }
285
-
286
- /**
287
- * Default parameter values for agent configuration
288
- */
289
- const DEFAULT_TEMPERATURE = 0.7;
290
- const DEFAULT_MAX_TURNS = 25;
291
-
292
- /**
293
- * Cached configuration used to (re)build the agent.
294
- */
295
- interface IAgentConfig {
296
- model: LanguageModel;
297
- tools: ToolMap;
298
- temperature: number;
299
- maxOutputTokens?: number;
300
- maxTurns: number;
301
- baseSystemPrompt: string;
302
- shouldUseTools: boolean;
303
- }
304
-
305
- /**
306
- * Manages the AI agent lifecycle and execution loop.
307
- * Provides agent initialization, tool management, MCP server integration,
308
- * and handles the complete agent execution cycle.
309
- * Emits events for UI updates instead of directly manipulating the chat interface.
310
- */
311
- export class AgentManager implements IAgentManager {
312
- /**
313
- * Creates a new AgentManager instance.
314
- * @param options Configuration options for the agent manager
315
- */
316
- constructor(options: IAgentManager.IOptions) {
317
- this._settingsModel = options.settingsModel;
318
- this._toolRegistry = options.toolRegistry;
319
- this._providerRegistry = options.providerRegistry;
320
- this._skillRegistry = options.skillRegistry;
321
- this._secretsManager = options.secretsManager;
322
- this._selectedToolNames = [];
323
- this._agent = null;
324
- this._history = [];
325
- this._mcpTools = {};
326
- this._controller = null;
327
- this._agentEvent = new Signal<this, IAgentManager.IAgentEvent>(this);
328
- this._tokenUsage = options.tokenUsage ?? {
329
- inputTokens: 0,
330
- outputTokens: 0
331
- };
332
- this._tokenUsageChanged = new Signal<this, ITokenUsage>(this);
333
- this._skills = [];
334
- this._agentConfig = null;
335
- this._renderMimeRegistry = options.renderMimeRegistry;
336
- this._streaming.resolve();
337
-
338
- this.activeProvider =
339
- options.activeProvider ?? this._settingsModel.config.defaultProvider;
340
-
341
- // Initialize selected tools to all available tools by default
342
- if (this._toolRegistry) {
343
- this._selectedToolNames = Object.keys(this._toolRegistry.tools);
344
- }
345
- }
346
-
347
- /**
348
- * Signal emitted when agent events occur
349
- */
350
- get agentEvent(): ISignal<this, IAgentManager.IAgentEvent> {
351
- return this._agentEvent;
352
- }
353
-
354
- /**
355
- * Signal emitted when the active provider has changed.
356
- */
357
- get activeProviderChanged(): ISignal<this, string | undefined> {
358
- return this._activeProviderChanged;
359
- }
360
-
361
- /**
362
- * Gets the current token usage statistics.
363
- */
364
- get tokenUsage(): ITokenUsage {
365
- return this._tokenUsage;
366
- }
367
-
368
- /**
369
- * Signal emitted when token usage statistics change.
370
- */
371
- get tokenUsageChanged(): ISignal<this, ITokenUsage> {
372
- return this._tokenUsageChanged;
373
- }
374
-
375
- /**
376
- * Refresh the skills snapshot and rebuild the agent if resources are ready.
377
- */
378
- refreshSkills(): void {
379
- this._initQueue = this._initQueue
380
- .catch(() => undefined)
381
- .then(async () => {
382
- this._refreshSkills();
383
- if (!this._agentConfig) {
384
- return;
385
- }
386
- this._rebuildAgent();
387
- });
388
- }
389
-
390
- /**
391
- * The active provider for this agent.
392
- */
393
- get activeProvider(): string {
394
- return this._activeProvider;
395
- }
396
- set activeProvider(value: string) {
397
- const previousProvider = this._activeProvider;
398
- this._activeProvider = value;
399
-
400
- // Reset request-level context estimate only when switching between providers.
401
- if (previousProvider && previousProvider !== value) {
402
- this._tokenUsage.lastRequestInputTokens = undefined;
403
- }
404
-
405
- this._tokenUsage.contextWindow = this._getActiveContextWindow();
406
-
407
- this._tokenUsageChanged.emit(this._tokenUsage);
408
- this.initializeAgent();
409
- this._activeProviderChanged.emit(this._activeProvider);
410
- }
411
-
412
- /**
413
- * Sets the selected tools by name and reinitializes the agent.
414
- * @param toolNames Array of tool names to select
415
- */
416
- setSelectedTools(toolNames: string[]): void {
417
- this._selectedToolNames = [...toolNames];
418
- this.initializeAgent().catch(error =>
419
- console.warn('Failed to initialize agent on tools change:', error)
420
- );
421
- }
422
-
423
- /**
424
- * Gets the currently selected tools as a record.
425
- * @returns Record of selected tools
426
- */
427
- get selectedAgentTools(): ToolMap {
428
- if (!this._toolRegistry) {
429
- return {};
430
- }
431
-
432
- const result: ToolMap = {};
433
- for (const name of this._selectedToolNames) {
434
- const tool: ITool | null = this._toolRegistry.get(name);
435
- if (tool) {
436
- result[name] = tool;
437
- }
438
- }
439
-
440
- return result;
441
- }
442
-
443
- /**
444
- * Checks if the current configuration is valid for agent operations.
445
- * Uses the provider registry to determine if an API key is required.
446
- * @returns True if the configuration is valid, false otherwise
447
- */
448
- hasValidConfig(): boolean {
449
- const activeProviderConfig = this._settingsModel.getProvider(
450
- this._activeProvider
451
- );
452
- if (!activeProviderConfig) {
453
- return false;
454
- }
455
-
456
- if (!activeProviderConfig.model) {
457
- return false;
458
- }
459
-
460
- if (this._providerRegistry) {
461
- const providerInfo = this._providerRegistry.getProviderInfo(
462
- activeProviderConfig.provider
463
- );
464
- if (providerInfo?.apiKeyRequirement === 'required') {
465
- return !!activeProviderConfig.apiKey;
466
- }
467
- }
468
-
469
- return true;
470
- }
471
-
472
- /**
473
- * Clears conversation history and resets agent state.
474
- */
475
- async clearHistory(): Promise<void> {
476
- // Stop any ongoing streaming
477
- this.stopStreaming('Chat cleared');
478
-
479
- await this._streaming.promise;
480
-
481
- // Clear history and token usage
482
- this._history = [];
483
- this._tokenUsage = {
484
- inputTokens: 0,
485
- outputTokens: 0,
486
- contextWindow: this._getActiveContextWindow()
487
- };
488
- this._tokenUsageChanged.emit(this._tokenUsage);
489
- }
490
-
491
- /**
492
- * Sets the history from already-processed model messages.
493
- * @param messages Pre-built model messages (may include binary content)
494
- */
495
- setHistory(messages: ModelMessage[]): void {
496
- this.stopStreaming('Chat history changed');
497
- this._history = Private.sanitizeModelMessages(messages);
498
- }
499
-
500
- /**
501
- * Stops the current streaming response by aborting the request.
502
- * Resolve any pending approval.
503
- */
504
- stopStreaming(reason?: string): void {
505
- this._controller?.abort();
506
-
507
- // Reject any pending approvals
508
- for (const [toolCallId, pending] of this._pendingApprovals) {
509
- pending.resolve(false, reason ?? 'Stream ended by user');
510
- this._agentEvent.emit({
511
- type: 'tool_approval_resolved',
512
- data: { toolCallId, approved: false }
513
- });
514
- }
515
- this._pendingApprovals.clear();
516
- }
517
-
518
- /**
519
- * Approves a pending tool call.
520
- * @param toolCallId The tool call ID to approve
521
- * @param reason Optional reason for approval
522
- */
523
- approveToolCall(toolCallId: string, reason?: string): void {
524
- const pending = this._pendingApprovals.get(toolCallId);
525
- if (pending) {
526
- pending.resolve(true, reason);
527
- this._pendingApprovals.delete(toolCallId);
528
- this._agentEvent.emit({
529
- type: 'tool_approval_resolved',
530
- data: { toolCallId, approved: true }
531
- });
532
- }
533
- }
534
-
535
- /**
536
- * Rejects a pending tool call.
537
- * @param toolCallId The tool call ID to reject
538
- * @param reason Optional reason for rejection
539
- */
540
- rejectToolCall(toolCallId: string, reason?: string): void {
541
- const pending = this._pendingApprovals.get(toolCallId);
542
- if (pending) {
543
- pending.resolve(false, reason);
544
- this._pendingApprovals.delete(toolCallId);
545
- this._agentEvent.emit({
546
- type: 'tool_approval_resolved',
547
- data: { toolCallId, approved: false }
548
- });
549
- }
550
- }
551
-
552
- /**
553
- * Generates AI response to user message using the agent.
554
- * Handles the complete execution cycle including tool calls.
555
- * @param message The user message to respond to (may include processed attachment content)
556
- */
557
- async generateResponse(message: UserContent): Promise<void> {
558
- this._streaming = new PromiseDelegate();
559
- this._controller = new AbortController();
560
- const responseHistory: ModelMessage[] = [];
561
-
562
- // Add user message to history
563
- responseHistory.push({
564
- role: 'user',
565
- content: message
566
- });
567
-
568
- try {
569
- // Ensure we have an agent
570
- if (!this._agent) {
571
- await this.initializeAgent();
572
- }
573
-
574
- if (!this._agent) {
575
- throw new Error('Failed to initialize agent');
576
- }
577
-
578
- let continueLoop = true;
579
- while (continueLoop) {
580
- const result = await this._agent.stream({
581
- messages: [...this._history, ...responseHistory],
582
- abortSignal: this._controller.signal
583
- });
584
-
585
- const streamResult = await this._processStreamResult(result);
586
-
587
- if (streamResult.aborted) {
588
- try {
589
- const responseMessages = await result.response;
590
- if (responseMessages.messages?.length) {
591
- this._history.push(
592
- ...Private.sanitizeModelMessages(responseMessages.messages)
593
- );
594
- }
595
- } catch {
596
- // Aborting before a step finishes leaves no completed response to persist.
597
- }
598
- break;
599
- }
600
-
601
- // Get response messages for completed steps.
602
- const responseMessages = await result.response;
603
-
604
- // Add response messages to history
605
- if (responseMessages.messages?.length) {
606
- responseHistory.push(...responseMessages.messages);
607
- }
608
-
609
- // Add approval response if processed
610
- if (streamResult.approvalResponse) {
611
- // Check if the last message is a tool message we can append to
612
- const lastMsg = responseHistory[responseHistory.length - 1];
613
- if (
614
- lastMsg &&
615
- lastMsg.role === 'tool' &&
616
- Array.isArray(lastMsg.content) &&
617
- Array.isArray(streamResult.approvalResponse.content)
618
- ) {
619
- const toolContent = lastMsg.content as unknown[];
620
- toolContent.push(...streamResult.approvalResponse.content);
621
- } else {
622
- // Add as separate message
623
- responseHistory.push(streamResult.approvalResponse);
624
- }
625
- }
626
-
627
- continueLoop = streamResult.approvalProcessed;
628
- }
629
-
630
- // Add the messages to the history only if the response ended without error.
631
- this._history.push(...Private.sanitizeModelMessages(responseHistory));
632
- } catch (error) {
633
- if ((error as Error).name !== 'AbortError') {
634
- let helpMessage = `${(error as Error).message}`;
635
-
636
- // Remove attachments from history on payload rejection errors
637
- if (
638
- APICallError.isInstance(error) &&
639
- (error.statusCode === 400 ||
640
- error.statusCode === 404 ||
641
- error.statusCode === 413 ||
642
- error.statusCode === 415 ||
643
- error.statusCode === 422)
644
- ) {
645
- this._stripAttachments(
646
- [...this._history, ...responseHistory],
647
- '_Attachment removed due to error_'
648
- );
649
- helpMessage +=
650
- '\n\nAttachments have been removed from history. Please send your prompt again.';
651
- }
652
- this._agentEvent.emit({
653
- type: 'error',
654
- data: { error: new Error(helpMessage) }
655
- });
656
- this._history.push(...Private.sanitizeModelMessages(responseHistory));
657
- this._history.push({
658
- role: 'assistant',
659
- content: helpMessage
660
- });
661
- }
662
- } finally {
663
- this._controller = null;
664
- this._streaming.resolve();
665
- }
666
- }
667
-
668
- /**
669
- * Create a transient language model to request a text response which won't be added to history.
670
- * @param messages - the messages sequence to send to the model.
671
- */
672
- async textResponse(messages: ModelMessage[]): Promise<string> {
673
- try {
674
- const model = await this._createModel();
675
- const result = await generateText({
676
- model,
677
- messages
678
- });
679
- this._updateTokenUsage(result.totalUsage, result.totalUsage.inputTokens);
680
- return result.text;
681
- } catch (e) {
682
- throw `Error while getting the topic of the chat\n${e}`;
683
- }
684
- }
685
-
686
- /**
687
- * Updates cumulative token usage statistics from a completed model step.
688
- */
689
- private _updateTokenUsage(
690
- usage: { inputTokens?: number; outputTokens?: number } | undefined,
691
- lastRequestInputTokens?: number
692
- ): void {
693
- const contextWindow = this._getActiveContextWindow();
694
- const estimatedRequestInputTokens =
695
- lastRequestInputTokens ?? usage?.inputTokens;
696
-
697
- if (usage) {
698
- this._tokenUsage.inputTokens += usage.inputTokens ?? 0;
699
- this._tokenUsage.outputTokens += usage.outputTokens ?? 0;
700
- }
701
-
702
- this._tokenUsage.lastRequestInputTokens = estimatedRequestInputTokens;
703
- this._tokenUsage.contextWindow = contextWindow;
704
-
705
- this._tokenUsageChanged.emit(this._tokenUsage);
706
- }
707
-
708
- /**
709
- * Removes image and file parts from all user messages in the given list.
710
- */
711
- private _stripAttachments(
712
- messages: ModelMessage[],
713
- placeholder: string
714
- ): void {
715
- for (const msg of messages) {
716
- if (msg.role === 'user' && Array.isArray(msg.content)) {
717
- const hasMedia = msg.content.some(p => p.type !== 'text');
718
- if (hasMedia) {
719
- const textContent = msg.content
720
- .filter(p => p.type === 'text')
721
- .map(p => (p as { text: string }).text)
722
- .join('\n');
723
- msg.content = textContent || placeholder;
724
- }
725
- }
726
- }
727
- }
728
-
729
- /**
730
- * Gets the configured context window for the active provider.
731
- */
732
- private _getActiveContextWindow(): number | undefined {
733
- const activeProviderConfig = this._settingsModel.getProvider(
734
- this._activeProvider
735
- );
736
- return getEffectiveContextWindow(
737
- activeProviderConfig,
738
- this._providerRegistry
739
- );
740
- }
741
-
742
- /**
743
- * Initializes the AI agent with current settings and tools.
744
- * Sets up the agent with model configuration, tools, and MCP tools.
745
- */
746
- initializeAgent = async (mcpTools?: ToolMap): Promise<void> => {
747
- this._initQueue = this._initQueue
748
- .catch(() => undefined)
749
- .then(async () => {
750
- try {
751
- this._refreshSkills();
752
- await this._prepareAgentConfig(mcpTools);
753
- this._rebuildAgent();
754
- } catch (error) {
755
- console.warn('Failed to initialize agent:', error);
756
- this._agent = null;
757
- }
758
- });
759
- return this._initQueue;
760
- };
761
-
762
- /**
763
- * Refresh the in-memory skills snapshot from the skill registry.
764
- */
765
- private _refreshSkills(): void {
766
- if (!this._skillRegistry) {
767
- this._skills = [];
768
- return;
769
- }
770
- this._skills = this._skillRegistry.listSkills();
771
- }
772
-
773
- /**
774
- * Prepare model, tools, and settings needed to (re)build the agent.
775
- */
776
- private async _prepareAgentConfig(mcpTools?: ToolMap): Promise<void> {
777
- const config = this._settingsModel.config;
778
- if (mcpTools !== undefined) {
779
- this._mcpTools = mcpTools;
780
- }
781
-
782
- const model = await this._createModel();
783
-
784
- const supportsToolCalling = this._supportsToolCalling();
785
- const canUseTools = config.toolsEnabled && supportsToolCalling;
786
- const hasFunctionToolRegistry = !!(
787
- this._toolRegistry && Object.keys(this._toolRegistry.tools).length > 0
788
- );
789
- const selectedFunctionTools =
790
- canUseTools && hasFunctionToolRegistry ? this.selectedAgentTools : {};
791
- const functionTools = canUseTools
792
- ? { ...selectedFunctionTools, ...this._mcpTools }
793
- : {};
794
-
795
- const activeProviderConfig = this._settingsModel.getProvider(
796
- this._activeProvider
797
- );
798
- const activeProviderInfo =
799
- activeProviderConfig && this._providerRegistry
800
- ? this._providerRegistry.getProviderInfo(activeProviderConfig.provider)
801
- : null;
802
- const contextWindow = getEffectiveContextWindow(
803
- activeProviderConfig,
804
- this._providerRegistry
805
- );
806
-
807
- this._tokenUsage.contextWindow = contextWindow;
808
- this._tokenUsageChanged.emit(this._tokenUsage);
809
-
810
- const temperature =
811
- activeProviderConfig?.parameters?.temperature ?? DEFAULT_TEMPERATURE;
812
- const maxTokens = activeProviderConfig?.parameters?.maxOutputTokens;
813
- const maxTurns =
814
- activeProviderConfig?.parameters?.maxTurns ?? DEFAULT_MAX_TURNS;
815
-
816
- const tools = this._buildRuntimeTools({
817
- providerInfo: activeProviderInfo,
818
- customSettings: activeProviderConfig?.customSettings,
819
- functionTools,
820
- includeProviderTools: canUseTools
821
- });
822
-
823
- const shouldUseTools = canUseTools && Object.keys(tools).length > 0;
824
-
825
- this._agentConfig = {
826
- model,
827
- tools,
828
- temperature,
829
- maxOutputTokens: maxTokens,
830
- maxTurns,
831
- baseSystemPrompt: config.systemPrompt || '',
832
- shouldUseTools
833
- };
834
- }
835
-
836
- /**
837
- * Build the runtime tool map used by the agent.
838
- */
839
- private _buildRuntimeTools(options: {
840
- providerInfo?: IProviderInfo | null;
841
- customSettings?: IProviderCustomSettings;
842
- functionTools: ToolMap;
843
- includeProviderTools: boolean;
844
- }): ToolMap {
845
- const providerTools = options.includeProviderTools
846
- ? createProviderTools({
847
- providerInfo: options.providerInfo,
848
- customSettings: options.customSettings,
849
- hasFunctionTools: Object.keys(options.functionTools).length > 0
850
- })
851
- : {};
852
-
853
- return {
854
- ...providerTools,
855
- ...options.functionTools
856
- };
857
- }
858
-
859
- /**
860
- * Rebuild the agent using cached resources and the current skills snapshot.
861
- */
862
- private _rebuildAgent(): void {
863
- if (!this._agentConfig) {
864
- this._agent = null;
865
- return;
866
- }
867
-
868
- const {
869
- model,
870
- tools,
871
- temperature,
872
- maxOutputTokens,
873
- maxTurns,
874
- baseSystemPrompt,
875
- shouldUseTools
876
- } = this._agentConfig;
877
-
878
- const baseInstructions = shouldUseTools
879
- ? this._getEnhancedSystemPrompt(baseSystemPrompt, tools)
880
- : baseSystemPrompt || 'You are a helpful assistant.';
881
- const richOutputWorkflowInstruction = shouldUseTools
882
- ? '- 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.'
883
- : '- When tools are unavailable, explain the limitation clearly and provide concrete steps the user can run to produce the desired rich outputs.';
884
- const supportedMimeTypesInstruction =
885
- this._getSupportedMimeTypesInstruction();
886
- const instructions = `${baseInstructions}
887
-
888
- RICH OUTPUT RENDERING:
889
- - The chat UI can render rich MIME outputs as separate assistant messages.
890
- - ${supportedMimeTypesInstruction}
891
- - Use only MIME types from the supported list when creating MIME bundles. Do not invent MIME keys.
892
- - Do not claim that you cannot display maps, images, or rich outputs in chat.
893
- ${richOutputWorkflowInstruction}`;
894
-
895
- this._agent = new ToolLoopAgent({
896
- model,
897
- instructions,
898
- tools,
899
- temperature,
900
- maxOutputTokens,
901
- stopWhen: stepCountIs(maxTurns)
902
- });
903
- }
904
-
905
- /**
906
- * Processes the stream result from agent execution.
907
- * Handles message streaming, tool calls, and emits appropriate events.
908
- * @param result The stream result from agent execution
909
- * @returns Processing result including approval info if applicable
910
- */
911
- private async _processStreamResult(
912
- result: StreamTextResult<ToolMap, never>
913
- ): Promise<IStreamProcessResult> {
914
- let fullResponse = '';
915
- let currentMessageId: string | null = null;
916
- const processResult: IStreamProcessResult = {
917
- approvalProcessed: false,
918
- aborted: false
919
- };
920
-
921
- for await (const part of result.fullStream) {
922
- switch (part.type) {
923
- case 'text-delta':
924
- if (!currentMessageId) {
925
- currentMessageId = `msg-${Date.now()}-${Math.random()}`;
926
- this._agentEvent.emit({
927
- type: 'message_start',
928
- data: { messageId: currentMessageId }
929
- });
930
- }
931
- fullResponse += part.text;
932
- this._agentEvent.emit({
933
- type: 'message_chunk',
934
- data: {
935
- messageId: currentMessageId,
936
- chunk: part.text,
937
- fullContent: fullResponse
938
- }
939
- });
940
- break;
941
-
942
- case 'tool-call':
943
- // Complete current message before tool call
944
- if (currentMessageId && fullResponse) {
945
- this._emitMessageComplete(currentMessageId, fullResponse);
946
- currentMessageId = null;
947
- fullResponse = '';
948
- }
949
- this._agentEvent.emit({
950
- type: 'tool_call_start',
951
- data: {
952
- callId: part.toolCallId,
953
- toolName: part.toolName,
954
- input: this._formatToolInput(JSON.stringify(part.input))
955
- }
956
- });
957
- break;
958
-
959
- case 'tool-result':
960
- this._handleToolResult(part);
961
- break;
962
-
963
- case 'tool-error':
964
- this._handleToolError(part);
965
- break;
966
-
967
- case 'tool-output-denied':
968
- this._handleToolOutputDenied(part);
969
- break;
970
-
971
- case 'tool-approval-request':
972
- // Complete current message before approval
973
- if (currentMessageId && fullResponse) {
974
- this._emitMessageComplete(currentMessageId, fullResponse);
975
- currentMessageId = null;
976
- fullResponse = '';
977
- }
978
- await this._handleApprovalRequest(part, processResult);
979
- break;
980
-
981
- case 'error':
982
- throw part.error;
983
-
984
- case 'finish-step':
985
- this._updateTokenUsage(part.usage, part.usage.inputTokens);
986
- break;
987
-
988
- case 'abort':
989
- processResult.aborted = true;
990
- break;
991
-
992
- // Ignore: text-start, text-end, finish, and others
993
- default:
994
- break;
995
- }
996
- }
997
-
998
- // Complete final message if content remains
999
- if (currentMessageId && fullResponse) {
1000
- this._emitMessageComplete(currentMessageId, fullResponse);
1001
- }
1002
-
1003
- return processResult;
1004
- }
1005
-
1006
- /**
1007
- * Emits a message_complete event.
1008
- */
1009
- private _emitMessageComplete(messageId: string, content: string): void {
1010
- this._agentEvent.emit({
1011
- type: 'message_complete',
1012
- data: { messageId, content }
1013
- });
1014
- }
1015
-
1016
- /**
1017
- * Handles tool-result stream parts.
1018
- */
1019
- private _handleToolResult(part: TypedToolResult<ToolMap>): void {
1020
- const isError =
1021
- typeof part.output === 'object' &&
1022
- part.output !== null &&
1023
- 'success' in part.output &&
1024
- part.output.success === false;
1025
-
1026
- this._agentEvent.emit({
1027
- type: 'tool_call_complete',
1028
- data: {
1029
- callId: part.toolCallId,
1030
- toolName: part.toolName,
1031
- outputData: part.output,
1032
- isError
1033
- }
1034
- });
1035
- }
1036
-
1037
- /**
1038
- * Handles tool-error stream parts.
1039
- */
1040
- private _handleToolError(part: TypedToolError<ToolMap>): void {
1041
- const output =
1042
- typeof part.error === 'string'
1043
- ? part.error
1044
- : part.error instanceof Error
1045
- ? part.error.message
1046
- : JSON.stringify(part.error, null, 2);
1047
-
1048
- this._agentEvent.emit({
1049
- type: 'tool_call_complete',
1050
- data: {
1051
- callId: part.toolCallId,
1052
- toolName: part.toolName,
1053
- outputData: output,
1054
- isError: true
1055
- }
1056
- });
1057
- }
1058
-
1059
- /**
1060
- * Handles tool-output-denied stream parts.
1061
- */
1062
- private _handleToolOutputDenied(part: TypedToolOutputDenied<ToolMap>): void {
1063
- this._agentEvent.emit({
1064
- type: 'tool_call_complete',
1065
- data: {
1066
- callId: part.toolCallId,
1067
- toolName: part.toolName,
1068
- outputData: 'Tool output was denied.',
1069
- isError: true
1070
- }
1071
- });
1072
- }
1073
-
1074
- /**
1075
- * Handles tool-approval-request stream parts.
1076
- */
1077
- private async _handleApprovalRequest(
1078
- part: ToolApprovalRequestOutput<ToolMap>,
1079
- result: IStreamProcessResult
1080
- ): Promise<void> {
1081
- const { approvalId, toolCall } = part;
1082
-
1083
- this._agentEvent.emit({
1084
- type: 'tool_approval_request',
1085
- data: {
1086
- toolCallId: toolCall.toolCallId,
1087
- toolName: toolCall.toolName,
1088
- args: toolCall.input
1089
- }
1090
- });
1091
-
1092
- const approved = await this._waitForApproval(toolCall.toolCallId);
1093
-
1094
- result.approvalProcessed = true;
1095
- result.approvalResponse = {
1096
- role: 'tool',
1097
- content: [
1098
- {
1099
- type: 'tool-approval-response',
1100
- approvalId,
1101
- approved
1102
- }
1103
- ]
1104
- };
1105
- }
1106
-
1107
- /**
1108
- * Waits for user approval of a tool call.
1109
- * @param toolCallId The tool call ID to wait for approval
1110
- * @returns Promise that resolves to true if approved, false if rejected
1111
- */
1112
- private _waitForApproval(toolCallId: string): Promise<boolean> {
1113
- return new Promise(resolve => {
1114
- this._pendingApprovals.set(toolCallId, {
1115
- resolve: (approved: boolean) => {
1116
- resolve(approved);
1117
- }
1118
- });
1119
- });
1120
- }
1121
-
1122
- /**
1123
- * Formats tool input for display by pretty-printing JSON strings.
1124
- * @param input The tool input string to format
1125
- * @returns Pretty-printed JSON string
1126
- */
1127
- private _formatToolInput(input: string): string {
1128
- try {
1129
- const parsed = JSON.parse(input);
1130
- return JSON.stringify(parsed, null, 2);
1131
- } catch {
1132
- return input;
1133
- }
1134
- }
1135
-
1136
- /**
1137
- * Checks if the current provider supports tool calling.
1138
- * @returns True if the provider supports tool calling, false otherwise
1139
- */
1140
- private _supportsToolCalling(): boolean {
1141
- const activeProviderConfig = this._settingsModel.getProvider(
1142
- this._activeProvider
1143
- );
1144
- if (!activeProviderConfig || !this._providerRegistry) {
1145
- return false;
1146
- }
1147
-
1148
- const providerInfo = this._providerRegistry.getProviderInfo(
1149
- activeProviderConfig.provider
1150
- );
1151
-
1152
- // Default to true if supportsToolCalling is not specified
1153
- return providerInfo?.supportsToolCalling !== false;
1154
- }
1155
-
1156
- /**
1157
- * Creates a model instance based on current settings.
1158
- * @returns The configured model instance for the agent
1159
- */
1160
- private async _createModel() {
1161
- if (!this._activeProvider) {
1162
- throw new Error('No active provider configured');
1163
- }
1164
- const activeProviderConfig = this._settingsModel.getProvider(
1165
- this._activeProvider
1166
- );
1167
- if (!activeProviderConfig) {
1168
- throw new Error('No active provider configured');
1169
- }
1170
- const provider = activeProviderConfig.provider;
1171
- const model = activeProviderConfig.model;
1172
- const baseURL = activeProviderConfig.baseURL;
1173
-
1174
- let apiKey: string;
1175
- if (this._secretsManager && this._settingsModel.config.useSecretsManager) {
1176
- const token = Private.getToken();
1177
- if (!token) {
1178
- // This should never happen, the secrets manager should be disabled.
1179
- console.error(
1180
- '@jupyterlite/ai::AgentManager error: the settings manager token is not set.\nYou should disable the the secrets manager from the AI settings.'
1181
- );
1182
- apiKey = '';
1183
- } else {
1184
- apiKey =
1185
- (
1186
- await this._secretsManager.get(
1187
- token,
1188
- SECRETS_NAMESPACE,
1189
- `${provider}:apiKey`
1190
- )
1191
- )?.value ?? '';
1192
- }
1193
- } else {
1194
- apiKey = this._settingsModel.getApiKey(activeProviderConfig.id);
1195
- }
1196
-
1197
- return createModel(
1198
- {
1199
- provider,
1200
- model,
1201
- apiKey,
1202
- baseURL
1203
- },
1204
- this._providerRegistry
1205
- );
1206
- }
1207
-
1208
- /**
1209
- * Enhances the base system prompt with dynamic context like skills.
1210
- * @param baseSystemPrompt The base system prompt from settings
1211
- * @returns The enhanced system prompt with dynamic additions
1212
- */
1213
- private _getEnhancedSystemPrompt(
1214
- baseSystemPrompt: string,
1215
- tools: ToolMap
1216
- ): string {
1217
- let prompt = baseSystemPrompt;
1218
-
1219
- if (this._skills.length > 0) {
1220
- const lines = this._skills.map(
1221
- skill => `- ${skill.name}: ${skill.description}`
1222
- );
1223
- const skillsPrompt = `
1224
-
1225
- AGENT SKILLS:
1226
- Skills are provided via the skills registry and accessed through tools (not commands).
1227
- When a skill is relevant to the user's task, activate it by calling load_skill with the skill name to load its full instructions, then follow those instructions.
1228
- If the user explicitly asks for the latest list of skills, call discover_skills (optionally with a query).
1229
- Do NOT call discover_skills just to list skills; use the preloaded snapshot below instead unless you need to verify a skill not present in the snapshot.
1230
- If the load_skill result includes a non-empty "resources" array, those are bundled files (scripts, references, templates) you MUST load before proceeding. Only load the listed resource paths; never invent resource names. For each resource path, execute load_skill again with the resource argument, e.g.: load_skill({ name: "<skill>", resource: "<path>" }). Load all listed resources before starting the task.
1231
-
1232
- AVAILABLE SKILLS (preloaded snapshot):
1233
- ${lines.join('\n')}
1234
- `;
1235
- prompt += skillsPrompt;
1236
- }
1237
-
1238
- const toolNames = new Set(Object.keys(tools));
1239
- const hasBrowserFetch = toolNames.has('browser_fetch');
1240
- const hasWebFetch = toolNames.has('web_fetch');
1241
- const hasWebSearch = toolNames.has('web_search');
1242
-
1243
- if (hasBrowserFetch || hasWebFetch || hasWebSearch) {
1244
- const webRetrievalPrompt = `
1245
-
1246
- WEB RETRIEVAL POLICY:
1247
- - If the user asks about a specific URL and browser_fetch is available, call browser_fetch first for that URL.
1248
- - If browser_fetch fails due to CORS/network/access, try web_fetch (if available) for that same URL.
1249
- - 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.
1250
- - If either fetch method fails with temporary access/network issues (for example: network_or_cors), try the other fetch method if available before searching.
1251
- - Only fall back to web_search after both fetch methods fail or are unavailable.
1252
- - If the user explicitly asks to inspect one exact URL, do not skip directly to search unless both fetch methods fail or are unavailable.
1253
- - In your final response, state which retrieval method succeeded (browser_fetch, web_fetch, or web_search) and mention relevant limitations.
1254
- `;
1255
- prompt += webRetrievalPrompt;
1256
- }
1257
-
1258
- return prompt;
1259
- }
1260
-
1261
- /**
1262
- * Build an instruction line describing MIME types supported by this session.
1263
- */
1264
- private _getSupportedMimeTypesInstruction(): string {
1265
- const mimeTypes = this._renderMimeRegistry?.mimeTypes ?? [];
1266
- const safeMimeTypes = mimeTypes.filter(mimeType => {
1267
- const factory = this._renderMimeRegistry?.getFactory(mimeType);
1268
- return !!factory?.safe;
1269
- });
1270
-
1271
- if (safeMimeTypes.length === 0) {
1272
- return 'Supported MIME types are determined by the active JupyterLab renderers in this session.';
1273
- }
1274
-
1275
- return `Supported MIME types in this session: ${safeMimeTypes.join(', ')}`;
1276
- }
1277
-
1278
- // Private attributes
1279
- private _settingsModel: IAISettingsModel;
1280
- private _toolRegistry?: IToolRegistry;
1281
- private _providerRegistry?: IProviderRegistry;
1282
- private _skillRegistry?: ISkillRegistry;
1283
- private _secretsManager?: ISecretsManager;
1284
- private _selectedToolNames: string[];
1285
- private _agent: ToolLoopAgent<never, ToolMap> | null;
1286
- private _history: ModelMessage[];
1287
- private _mcpTools: ToolMap;
1288
- private _controller: AbortController | null;
1289
- private _agentEvent: Signal<this, IAgentManager.IAgentEvent>;
1290
- private _tokenUsage: ITokenUsage;
1291
- private _tokenUsageChanged: Signal<this, ITokenUsage>;
1292
- private _activeProvider: string = '';
1293
- private _activeProviderChanged = new Signal<this, string | undefined>(this);
1294
- private _skills: ISkillSummary[];
1295
- private _renderMimeRegistry?: IRenderMimeRegistry;
1296
- private _initQueue: Promise<void> = Promise.resolve();
1297
- private _agentConfig: IAgentConfig | null;
1298
- private _pendingApprovals: Map<
1299
- string,
1300
- { resolve: (approved: boolean, reason?: string) => void }
1301
- > = new Map();
1302
- private _streaming: PromiseDelegate<void> = new PromiseDelegate();
1303
- }
1304
-
1305
- namespace Private {
1306
- /**
1307
- * Sanitize the messages before adding them to the history.
1308
- *
1309
- * 1- Make sure the message sequence is not altered:
1310
- * - tool-call messages should have a corresponding tool-result (and vice-versa)
1311
- * - tool-approval-request should have a tool-approval-response (and vice-versa)
1312
- *
1313
- * 2- Keep only serializable messages by doing a JSON round-trip.
1314
- * Messages that cannot be serialized are dropped.
1315
- */
1316
- export const sanitizeModelMessages = (
1317
- messages: ModelMessage[]
1318
- ): ModelMessage[] => {
1319
- const sanitized: ModelMessage[] = [];
1320
- for (const message of messages) {
1321
- if (message.role === 'assistant') {
1322
- let newMessage: AssistantModelMessage | undefined;
1323
- if (!Array.isArray(message.content)) {
1324
- newMessage = message;
1325
- } else {
1326
- // Remove assistant message content without a required response.
1327
- const newContent: typeof message.content = [];
1328
- for (const assistantContent of message.content) {
1329
- let isContentValid = true;
1330
- if (assistantContent.type === 'tool-call') {
1331
- const toolCallId = assistantContent.toolCallId;
1332
- isContentValid = !!messages.find(
1333
- msg =>
1334
- msg.role === 'tool' &&
1335
- Array.isArray(msg.content) &&
1336
- msg.content.find(
1337
- content =>
1338
- content.type === 'tool-result' &&
1339
- content.toolCallId === toolCallId
1340
- )
1341
- );
1342
- } else if (assistantContent.type === 'tool-approval-request') {
1343
- const approvalId = assistantContent.approvalId;
1344
- isContentValid = !!messages.find(
1345
- msg =>
1346
- msg.role === 'tool' &&
1347
- Array.isArray(msg.content) &&
1348
- msg.content.find(
1349
- content =>
1350
- content.type === 'tool-approval-response' &&
1351
- content.approvalId === approvalId
1352
- )
1353
- );
1354
- }
1355
- if (isContentValid) {
1356
- newContent.push(assistantContent);
1357
- }
1358
- }
1359
- if (newContent.length) {
1360
- newMessage = { ...message, content: newContent };
1361
- }
1362
- }
1363
- if (newMessage) {
1364
- try {
1365
- sanitized.push(JSON.parse(JSON.stringify(newMessage)));
1366
- } catch {
1367
- // Drop messages that cannot be serialized
1368
- }
1369
- }
1370
- } else if (message.role === 'tool') {
1371
- // Remove tool message content without request.
1372
- const newContent: typeof message.content = [];
1373
- for (const toolContent of message.content) {
1374
- let isContentValid = true;
1375
- if (toolContent.type === 'tool-result') {
1376
- const toolCallId = toolContent.toolCallId;
1377
- isContentValid = !!sanitized.find(
1378
- msg =>
1379
- msg.role === 'assistant' &&
1380
- Array.isArray(msg.content) &&
1381
- msg.content.find(
1382
- content =>
1383
- content.type === 'tool-call' &&
1384
- content.toolCallId === toolCallId
1385
- )
1386
- );
1387
- } else if (toolContent.type === 'tool-approval-response') {
1388
- const approvalId = toolContent.approvalId;
1389
- isContentValid = !!sanitized.find(
1390
- msg =>
1391
- msg.role === 'assistant' &&
1392
- Array.isArray(msg.content) &&
1393
- msg.content.find(
1394
- content =>
1395
- content.type === 'tool-approval-request' &&
1396
- content.approvalId === approvalId
1397
- )
1398
- );
1399
- }
1400
- if (isContentValid) {
1401
- newContent.push(toolContent);
1402
- }
1403
- }
1404
- if (newContent.length) {
1405
- try {
1406
- sanitized.push(
1407
- JSON.parse(JSON.stringify({ ...message, content: newContent }))
1408
- );
1409
- } catch {
1410
- // Drop messages that cannot be serialized
1411
- }
1412
- }
1413
- } else {
1414
- // Message is a system or user message.
1415
- sanitized.push(message);
1416
- }
1417
- }
1418
- return sanitized.length === messages.length ? sanitized : [];
1419
- };
1420
-
1421
- /**
1422
- * The token to use with the secrets manager, setter and getter.
1423
- */
1424
- let secretsToken: symbol | null;
1425
- export function setToken(value: symbol | null): void {
1426
- secretsToken = value;
1427
- }
1428
- export function getToken(): symbol | null {
1429
- return secretsToken;
1430
- }
1431
- }