@jupyterlite/ai 0.9.1 → 0.11.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 (71) hide show
  1. package/README.md +5 -214
  2. package/lib/agent.d.ts +58 -66
  3. package/lib/agent.js +291 -310
  4. package/lib/approval-buttons.d.ts +19 -82
  5. package/lib/approval-buttons.js +36 -289
  6. package/lib/chat-model-registry.d.ts +6 -0
  7. package/lib/chat-model-registry.js +4 -1
  8. package/lib/chat-model.d.ts +26 -54
  9. package/lib/chat-model.js +277 -303
  10. package/lib/components/clear-button.d.ts +6 -1
  11. package/lib/components/clear-button.js +10 -6
  12. package/lib/components/completion-status.d.ts +5 -0
  13. package/lib/components/completion-status.js +5 -4
  14. package/lib/components/model-select.d.ts +6 -1
  15. package/lib/components/model-select.js +13 -16
  16. package/lib/components/stop-button.d.ts +6 -1
  17. package/lib/components/stop-button.js +12 -8
  18. package/lib/components/token-usage-display.d.ts +5 -0
  19. package/lib/components/token-usage-display.js +2 -2
  20. package/lib/components/tool-select.d.ts +6 -1
  21. package/lib/components/tool-select.js +10 -9
  22. package/lib/index.d.ts +1 -0
  23. package/lib/index.js +61 -81
  24. package/lib/models/settings-model.d.ts +1 -1
  25. package/lib/models/settings-model.js +40 -26
  26. package/lib/providers/built-in-providers.js +38 -19
  27. package/lib/providers/models.d.ts +3 -3
  28. package/lib/providers/provider-registry.d.ts +3 -4
  29. package/lib/providers/provider-registry.js +1 -4
  30. package/lib/tokens.d.ts +5 -6
  31. package/lib/tools/commands.d.ts +2 -1
  32. package/lib/tools/commands.js +36 -49
  33. package/lib/widgets/ai-settings.d.ts +6 -0
  34. package/lib/widgets/ai-settings.js +72 -71
  35. package/lib/widgets/main-area-chat.d.ts +2 -0
  36. package/lib/widgets/main-area-chat.js +5 -2
  37. package/lib/widgets/provider-config-dialog.d.ts +2 -0
  38. package/lib/widgets/provider-config-dialog.js +34 -34
  39. package/package.json +13 -13
  40. package/schema/settings-model.json +3 -2
  41. package/src/agent.ts +360 -372
  42. package/src/approval-buttons.ts +43 -389
  43. package/src/chat-model-registry.ts +9 -1
  44. package/src/chat-model.ts +399 -370
  45. package/src/completion/completion-provider.ts +2 -3
  46. package/src/components/clear-button.tsx +18 -6
  47. package/src/components/completion-status.tsx +18 -4
  48. package/src/components/model-select.tsx +25 -16
  49. package/src/components/stop-button.tsx +22 -9
  50. package/src/components/token-usage-display.tsx +14 -2
  51. package/src/components/tool-select.tsx +27 -9
  52. package/src/index.ts +78 -134
  53. package/src/models/settings-model.ts +41 -27
  54. package/src/providers/built-in-providers.ts +38 -19
  55. package/src/providers/models.ts +3 -3
  56. package/src/providers/provider-registry.ts +4 -8
  57. package/src/tokens.ts +5 -6
  58. package/src/tools/commands.ts +40 -53
  59. package/src/widgets/ai-settings.tsx +153 -84
  60. package/src/widgets/main-area-chat.ts +8 -2
  61. package/src/widgets/provider-config-dialog.tsx +54 -41
  62. package/style/base.css +24 -73
  63. package/lib/mcp/browser.d.ts +0 -68
  64. package/lib/mcp/browser.js +0 -138
  65. package/lib/tools/file.d.ts +0 -36
  66. package/lib/tools/file.js +0 -351
  67. package/lib/tools/notebook.d.ts +0 -40
  68. package/lib/tools/notebook.js +0 -779
  69. package/src/mcp/browser.ts +0 -220
  70. package/src/tools/file.ts +0 -438
  71. package/src/tools/notebook.ts +0 -986
package/lib/agent.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Signal } from '@lumino/signaling';
2
- import { Agent, Runner, user } from '@openai/agents';
3
- import { BrowserMCPServerStreamableHttp } from './mcp/browser';
2
+ import { ToolLoopAgent, stepCountIs } from 'ai';
3
+ import { createMCPClient } from '@ai-sdk/mcp';
4
4
  import { createModel } from './providers/models';
5
5
  import { SECRETS_NAMESPACE } from './tokens';
6
6
  export class AgentManagerFactory {
@@ -8,7 +8,7 @@ export class AgentManagerFactory {
8
8
  Private.setToken(options.token);
9
9
  this._settingsModel = options.settingsModel;
10
10
  this._secretsManager = options.secretsManager;
11
- this._mcpServers = [];
11
+ this._mcpClients = [];
12
12
  this._mcpConnectionChanged = new Signal(this);
13
13
  // Initialize agent on construction
14
14
  this._initializeAgents().catch(error => console.warn('Failed to initialize agent in constructor:', error));
@@ -35,7 +35,23 @@ export class AgentManagerFactory {
35
35
  * @returns True if the server is connected, false otherwise
36
36
  */
37
37
  isMCPServerConnected(serverName) {
38
- return this._mcpServers.some(server => server.name === serverName);
38
+ return this._mcpClients.some(wrapper => wrapper.name === serverName);
39
+ }
40
+ /**
41
+ * Gets the MCP tools from connected servers
42
+ */
43
+ async getMCPTools() {
44
+ const mcpTools = {};
45
+ for (const wrapper of this._mcpClients) {
46
+ try {
47
+ const tools = await wrapper.client.tools();
48
+ Object.assign(mcpTools, tools);
49
+ }
50
+ catch (error) {
51
+ console.warn(`Failed to get tools from MCP server ${wrapper.name}:`, error);
52
+ }
53
+ }
54
+ return mcpTools;
39
55
  }
40
56
  /**
41
57
  * Handles settings changes and reinitializes the agent.
@@ -44,33 +60,36 @@ export class AgentManagerFactory {
44
60
  this._initializeAgents().catch(error => console.warn('Failed to initialize agent on settings change:', error));
45
61
  }
46
62
  /**
47
- * Initializes MCP (Model Context Protocol) servers based on current settings.
48
- * Closes existing servers and connects to enabled servers from configuration.
63
+ * Initializes MCP (Model Context Protocol) clients based on current settings.
64
+ * Closes existing clients and connects to enabled servers from configuration.
49
65
  */
50
- async _initializeMCPServers() {
66
+ async _initializeMCPClients() {
51
67
  const config = this._settingsModel.config;
52
68
  const enabledServers = config.mcpServers.filter(server => server.enabled);
53
69
  let connectionChanged = false;
54
- // Close existing servers
55
- for (const server of this._mcpServers) {
70
+ // Close existing clients
71
+ for (const wrapper of this._mcpClients) {
56
72
  try {
57
- await server.close();
73
+ await wrapper.client.close();
58
74
  connectionChanged = true;
59
75
  }
60
76
  catch (error) {
61
- console.warn('Error closing MCP server:', error);
77
+ console.warn('Error closing MCP client:', error);
62
78
  }
63
79
  }
64
- this._mcpServers = [];
65
- // Initialize new servers
80
+ this._mcpClients = [];
66
81
  for (const serverConfig of enabledServers) {
67
82
  try {
68
- const mcpServer = new BrowserMCPServerStreamableHttp({
69
- url: serverConfig.url,
70
- name: serverConfig.name
83
+ const client = await createMCPClient({
84
+ transport: {
85
+ type: 'http',
86
+ url: serverConfig.url
87
+ }
88
+ });
89
+ this._mcpClients.push({
90
+ name: serverConfig.name,
91
+ client
71
92
  });
72
- await mcpServer.connect();
73
- this._mcpServers.push(mcpServer);
74
93
  connectionChanged = true;
75
94
  }
76
95
  catch (error) {
@@ -79,7 +98,7 @@ export class AgentManagerFactory {
79
98
  }
80
99
  // Emit connection change signal if there were any changes
81
100
  if (connectionChanged) {
82
- this._mcpConnectionChanged.emit(this._mcpServers.length > 0);
101
+ this._mcpConnectionChanged.emit(this._mcpClients.length > 0);
83
102
  }
84
103
  }
85
104
  /**
@@ -92,10 +111,10 @@ export class AgentManagerFactory {
92
111
  }
93
112
  this._isInitializing = true;
94
113
  try {
95
- await this._initializeMCPServers();
96
- const mcpServers = this._mcpServers.filter(server => server !== null);
114
+ await this._initializeMCPClients();
115
+ const mcpTools = await this.getMCPTools();
97
116
  this._agentManagers.forEach(manager => {
98
- manager.initializeAgent(mcpServers);
117
+ manager.initializeAgent(mcpTools);
99
118
  });
100
119
  }
101
120
  catch (error) {
@@ -108,7 +127,7 @@ export class AgentManagerFactory {
108
127
  _agentManagers = [];
109
128
  _settingsModel;
110
129
  _secretsManager;
111
- _mcpServers;
130
+ _mcpClients;
112
131
  _mcpConnectionChanged;
113
132
  _isInitializing = false;
114
133
  }
@@ -120,7 +139,7 @@ const DEFAULT_MAX_TURNS = 25;
120
139
  /**
121
140
  * Manages the AI agent lifecycle and execution loop.
122
141
  * Provides agent initialization, tool management, MCP server integration,
123
- * and handles the complete agent execution cycle including tool approvals.
142
+ * and handles the complete agent execution cycle.
124
143
  * Emits events for UI updates instead of directly manipulating the chat interface.
125
144
  */
126
145
  export class AgentManager {
@@ -135,13 +154,10 @@ export class AgentManager {
135
154
  this._secretsManager = options.secretsManager;
136
155
  this._selectedToolNames = [];
137
156
  this._agent = null;
138
- this._runner = new Runner({ tracingDisabled: true });
139
157
  this._history = [];
140
- this._mcpServers = [];
158
+ this._mcpTools = {};
141
159
  this._isInitializing = false;
142
160
  this._controller = null;
143
- this._pendingApprovals = new Map();
144
- this._interruptedState = null;
145
161
  this._agentEvent = new Signal(this);
146
162
  this._tokenUsage = options.tokenUsage ?? {
147
163
  inputTokens: 0,
@@ -199,18 +215,18 @@ export class AgentManager {
199
215
  this.initializeAgent().catch(error => console.warn('Failed to initialize agent on tools change:', error));
200
216
  }
201
217
  /**
202
- * Gets the currently selected tools as OpenAI agents tools.
203
- * @returns Array of selected tools formatted for OpenAI agents
218
+ * Gets the currently selected tools as a record.
219
+ * @returns Record of selected tools
204
220
  */
205
221
  get selectedAgentTools() {
206
222
  if (!this._toolRegistry) {
207
- return [];
223
+ return {};
208
224
  }
209
- const result = [];
225
+ const result = {};
210
226
  for (const name of this._selectedToolNames) {
211
227
  const tool = this._toolRegistry.get(name);
212
228
  if (tool) {
213
- result.push(tool);
229
+ result[name] = tool;
214
230
  }
215
231
  }
216
232
  return result;
@@ -238,14 +254,21 @@ export class AgentManager {
238
254
  }
239
255
  /**
240
256
  * Clears conversation history and resets agent state.
241
- * Removes all conversation history, pending approvals, and interrupted state.
242
257
  */
243
258
  clearHistory() {
244
- this._history = [];
245
- this._runner = new Runner({ tracingDisabled: true });
259
+ // Stop any ongoing streaming
260
+ this.stopStreaming();
261
+ // Reject any pending approvals
262
+ for (const [approvalId, pending] of this._pendingApprovals) {
263
+ pending.resolve(false, 'Chat cleared');
264
+ this._agentEvent.emit({
265
+ type: 'tool_approval_resolved',
266
+ data: { approvalId, approved: false }
267
+ });
268
+ }
246
269
  this._pendingApprovals.clear();
247
- this._interruptedState = null;
248
- // Reset token usage
270
+ // Clear history and token usage
271
+ this._history = [];
249
272
  this._tokenUsage = { inputTokens: 0, outputTokens: 0 };
250
273
  this._tokenUsageChanged.emit(this._tokenUsage);
251
274
  }
@@ -255,13 +278,44 @@ export class AgentManager {
255
278
  stopStreaming() {
256
279
  this._controller?.abort();
257
280
  }
281
+ /**
282
+ * Approves a pending tool call.
283
+ * @param approvalId The approval ID to approve
284
+ * @param reason Optional reason for approval
285
+ */
286
+ approveToolCall(approvalId, reason) {
287
+ const pending = this._pendingApprovals.get(approvalId);
288
+ if (pending) {
289
+ pending.resolve(true, reason);
290
+ this._pendingApprovals.delete(approvalId);
291
+ this._agentEvent.emit({
292
+ type: 'tool_approval_resolved',
293
+ data: { approvalId, approved: true }
294
+ });
295
+ }
296
+ }
297
+ /**
298
+ * Rejects a pending tool call.
299
+ * @param approvalId The approval ID to reject
300
+ * @param reason Optional reason for rejection
301
+ */
302
+ rejectToolCall(approvalId, reason) {
303
+ const pending = this._pendingApprovals.get(approvalId);
304
+ if (pending) {
305
+ pending.resolve(false, reason);
306
+ this._pendingApprovals.delete(approvalId);
307
+ this._agentEvent.emit({
308
+ type: 'tool_approval_resolved',
309
+ data: { approvalId, approved: false }
310
+ });
311
+ }
312
+ }
258
313
  /**
259
314
  * Generates AI response to user message using the agent.
260
- * Handles the complete execution cycle including tool calls and approvals.
315
+ * Handles the complete execution cycle including tool calls.
261
316
  * @param message The user message to respond to (may include processed attachment content)
262
317
  */
263
318
  async generateResponse(message) {
264
- const config = this._settingsModel.config;
265
319
  this._controller = new AbortController();
266
320
  try {
267
321
  // Ensure we have an agent
@@ -271,140 +325,79 @@ export class AgentManager {
271
325
  if (!this._agent) {
272
326
  throw new Error('Failed to initialize agent');
273
327
  }
274
- const shouldUseTools = config.toolsEnabled &&
275
- this._selectedToolNames.length > 0 &&
276
- this._toolRegistry &&
277
- Object.keys(this._toolRegistry.tools).length > 0 &&
278
- this._supportsToolCalling();
279
328
  // Add user message to history
280
- this._history.push(user(message));
281
- // Get provider-specific maxTurns or use default
282
- const activeProviderConfig = this._settingsModel.getProvider(this._activeProvider);
283
- const maxTurns = activeProviderConfig?.parameters?.maxTurns ?? DEFAULT_MAX_TURNS;
284
- // Main agentic loop
285
- let result = await this._runner.run(this._agent, this._history, {
286
- stream: true,
287
- signal: this._controller.signal,
288
- ...(shouldUseTools && { maxTurns })
329
+ this._history.push({
330
+ role: 'user',
331
+ content: message
289
332
  });
290
- await this._processRunResult(result);
291
- let hasInterruptions = result.interruptions && result.interruptions.length > 0;
292
- while (hasInterruptions) {
293
- this._interruptedState = result;
294
- const interruptions = result.interruptions;
295
- if (interruptions.length > 1) {
296
- await this._handleGroupedToolApprovals(interruptions);
297
- }
298
- else {
299
- await this._handleSingleToolApproval(interruptions[0]);
333
+ let continueLoop = true;
334
+ while (continueLoop) {
335
+ const result = await this._agent.stream({
336
+ messages: this._history,
337
+ abortSignal: this._controller.signal
338
+ });
339
+ const streamResult = await this._processStreamResult(result);
340
+ // Get response messages and update token usage
341
+ const responseMessages = await result.response;
342
+ this._updateTokenUsage(await result.usage);
343
+ // Add response messages to history
344
+ if (responseMessages.messages?.length) {
345
+ this._history.push(...responseMessages.messages);
300
346
  }
301
- // Wait for all approvals to be resolved
302
- while (this._pendingApprovals.size > 0) {
303
- await new Promise(resolve => setTimeout(resolve, 100));
347
+ // Add approval response if processed
348
+ if (streamResult.approvalResponse) {
349
+ // Check if the last message is a tool message we can append to
350
+ const lastMsg = this._history[this._history.length - 1];
351
+ if (lastMsg &&
352
+ lastMsg.role === 'tool' &&
353
+ Array.isArray(lastMsg.content) &&
354
+ Array.isArray(streamResult.approvalResponse.content)) {
355
+ const toolContent = lastMsg.content;
356
+ toolContent.push(...streamResult.approvalResponse.content);
357
+ }
358
+ else {
359
+ // Add as separate message
360
+ this._history.push(streamResult.approvalResponse);
361
+ }
304
362
  }
305
- // Continue execution
306
- result = await this._runner.run(this._agent, result.state, {
307
- stream: true,
308
- signal: this._controller.signal,
309
- maxTurns
310
- });
311
- await this._processRunResult(result);
312
- hasInterruptions =
313
- result.interruptions && result.interruptions.length > 0;
363
+ continueLoop = streamResult.approvalProcessed;
314
364
  }
315
- // Clear interrupted state
316
- this._interruptedState = null;
317
- this._history = result.history;
318
365
  }
319
366
  catch (error) {
320
- this._agentEvent.emit({
321
- type: 'error',
322
- data: { error: error }
323
- });
367
+ if (error.name !== 'AbortError') {
368
+ this._agentEvent.emit({
369
+ type: 'error',
370
+ data: { error: error }
371
+ });
372
+ }
324
373
  }
325
374
  finally {
326
375
  this._controller = null;
327
376
  }
328
377
  }
329
378
  /**
330
- * Approves a tool call by interruption ID.
331
- * @param interruptionId The interruption ID to approve
332
- */
333
- async approveToolCall(interruptionId) {
334
- const pending = this._pendingApprovals.get(interruptionId);
335
- if (!pending) {
336
- console.warn(`No pending approval found for interruption ${interruptionId}`);
337
- return;
338
- }
339
- if (this._interruptedState) {
340
- this._interruptedState.state.approve(pending.interruption);
341
- }
342
- pending.approved = true;
343
- this._pendingApprovals.delete(interruptionId);
344
- }
345
- /**
346
- * Rejects a tool call by interruption ID.
347
- * @param interruptionId The interruption ID to reject
348
- */
349
- async rejectToolCall(interruptionId) {
350
- const pending = this._pendingApprovals.get(interruptionId);
351
- if (!pending) {
352
- console.warn(`No pending approval found for interruption ${interruptionId}`);
353
- return;
354
- }
355
- if (this._interruptedState) {
356
- this._interruptedState.state.reject(pending.interruption);
357
- }
358
- pending.approved = false;
359
- this._pendingApprovals.delete(interruptionId);
360
- }
361
- /**
362
- * Approves all tools in a group by group ID.
363
- * @param groupId The group ID containing the tool calls
364
- * @param interruptionIds Array of interruption IDs to approve
365
- */
366
- async approveGroupedToolCalls(groupId, interruptionIds) {
367
- for (const interruptionId of interruptionIds) {
368
- const pending = this._pendingApprovals.get(interruptionId);
369
- if (pending && pending.groupId === groupId) {
370
- if (this._interruptedState) {
371
- this._interruptedState.state.approve(pending.interruption);
372
- }
373
- pending.approved = true;
374
- this._pendingApprovals.delete(interruptionId);
375
- }
376
- }
377
- }
378
- /**
379
- * Rejects all tools in a group by group ID.
380
- * @param groupId The group ID containing the tool calls
381
- * @param interruptionIds Array of interruption IDs to reject
379
+ * Updates token usage statistics.
382
380
  */
383
- async rejectGroupedToolCalls(groupId, interruptionIds) {
384
- for (const interruptionId of interruptionIds) {
385
- const pending = this._pendingApprovals.get(interruptionId);
386
- if (pending && pending.groupId === groupId) {
387
- if (this._interruptedState) {
388
- this._interruptedState.state.reject(pending.interruption);
389
- }
390
- pending.approved = false;
391
- this._pendingApprovals.delete(interruptionId);
392
- }
381
+ _updateTokenUsage(usage) {
382
+ if (usage) {
383
+ this._tokenUsage.inputTokens += usage.inputTokens ?? 0;
384
+ this._tokenUsage.outputTokens += usage.outputTokens ?? 0;
385
+ this._tokenUsageChanged.emit(this._tokenUsage);
393
386
  }
394
387
  }
395
388
  /**
396
389
  * Initializes the AI agent with current settings and tools.
397
- * Sets up the agent with model configuration, tools, and MCP servers.
390
+ * Sets up the agent with model configuration, tools, and MCP tools.
398
391
  */
399
- initializeAgent = async (mcpServers) => {
392
+ initializeAgent = async (mcpTools) => {
400
393
  if (this._isInitializing) {
401
394
  return;
402
395
  }
403
396
  this._isInitializing = true;
404
397
  try {
405
398
  const config = this._settingsModel.config;
406
- if (mcpServers !== undefined) {
407
- this._mcpServers = mcpServers;
399
+ if (mcpTools !== undefined) {
400
+ this._mcpTools = mcpTools;
408
401
  }
409
402
  const model = await this._createModel();
410
403
  const shouldUseTools = config.toolsEnabled &&
@@ -412,24 +405,23 @@ export class AgentManager {
412
405
  this._toolRegistry &&
413
406
  Object.keys(this._toolRegistry.tools).length > 0 &&
414
407
  this._supportsToolCalling();
415
- const tools = shouldUseTools ? this.selectedAgentTools : [];
408
+ const tools = shouldUseTools
409
+ ? { ...this.selectedAgentTools, ...this._mcpTools }
410
+ : this._mcpTools;
416
411
  const activeProviderConfig = this._settingsModel.getProvider(this._activeProvider);
417
412
  const temperature = activeProviderConfig?.parameters?.temperature ?? DEFAULT_TEMPERATURE;
418
- const maxTokens = activeProviderConfig?.parameters?.maxTokens;
419
- this._agent = new Agent({
420
- name: 'Assistant',
421
- instructions: shouldUseTools
422
- ? this._getEnhancedSystemPrompt(config.systemPrompt || '')
423
- : config.systemPrompt || 'You are a helpful assistant.',
424
- model: model,
425
- mcpServers: this._mcpServers,
413
+ const maxTokens = activeProviderConfig?.parameters?.maxOutputTokens;
414
+ const maxTurns = activeProviderConfig?.parameters?.maxTurns ?? DEFAULT_MAX_TURNS;
415
+ const instructions = shouldUseTools
416
+ ? this._getEnhancedSystemPrompt(config.systemPrompt || '')
417
+ : config.systemPrompt || 'You are a helpful assistant.';
418
+ this._agent = new ToolLoopAgent({
419
+ model,
420
+ instructions,
426
421
  tools,
427
- ...(temperature && {
428
- modelSettings: {
429
- temperature,
430
- maxTokens
431
- }
432
- })
422
+ temperature,
423
+ maxOutputTokens: maxTokens,
424
+ stopWhen: stepCountIs(maxTurns)
433
425
  });
434
426
  }
435
427
  catch (error) {
@@ -441,175 +433,159 @@ export class AgentManager {
441
433
  }
442
434
  };
443
435
  /**
444
- * Processes the result stream from agent execution.
436
+ * Processes the stream result from agent execution.
445
437
  * Handles message streaming, tool calls, and emits appropriate events.
446
- * @param result The async iterable result from agent execution
438
+ * @param result The stream result from agent execution
439
+ * @returns Processing result including approval info if applicable
447
440
  */
448
- async _processRunResult(result) {
441
+ async _processStreamResult(result) {
449
442
  let fullResponse = '';
450
443
  let currentMessageId = null;
451
- for await (const event of result) {
452
- if (event.type === 'raw_model_stream_event') {
453
- const data = event.data;
454
- if (data.type === 'response_started') {
455
- currentMessageId = `msg-${Date.now()}-${Math.random()}`;
456
- fullResponse = '';
457
- this._agentEvent.emit({
458
- type: 'message_start',
459
- data: { messageId: currentMessageId }
460
- });
461
- }
462
- else if (data.type === 'output_text_delta') {
463
- if (currentMessageId) {
464
- const chunk = data.delta || '';
465
- fullResponse += chunk;
444
+ const processResult = { approvalProcessed: false };
445
+ for await (const part of result.fullStream) {
446
+ switch (part.type) {
447
+ case 'text-delta':
448
+ if (!currentMessageId) {
449
+ currentMessageId = `msg-${Date.now()}-${Math.random()}`;
466
450
  this._agentEvent.emit({
467
- type: 'message_chunk',
468
- data: {
469
- messageId: currentMessageId,
470
- chunk,
471
- fullContent: fullResponse
472
- }
451
+ type: 'message_start',
452
+ data: { messageId: currentMessageId }
473
453
  });
474
454
  }
475
- }
476
- else if (data.type === 'response_done') {
477
- if (currentMessageId) {
478
- this._agentEvent.emit({
479
- type: 'message_complete',
480
- data: {
481
- messageId: currentMessageId,
482
- content: fullResponse
483
- }
484
- });
455
+ fullResponse += part.text;
456
+ this._agentEvent.emit({
457
+ type: 'message_chunk',
458
+ data: {
459
+ messageId: currentMessageId,
460
+ chunk: part.text,
461
+ fullContent: fullResponse
462
+ }
463
+ });
464
+ break;
465
+ case 'tool-call':
466
+ // Complete current message before tool call
467
+ if (currentMessageId && fullResponse) {
468
+ this._emitMessageComplete(currentMessageId, fullResponse);
485
469
  currentMessageId = null;
470
+ fullResponse = '';
486
471
  }
487
- const usage = data.response.usage;
488
- const { inputTokens, outputTokens } = usage;
489
- this._tokenUsage.inputTokens += inputTokens;
490
- this._tokenUsage.outputTokens += outputTokens;
491
- this._tokenUsageChanged.emit(this._tokenUsage);
492
- }
493
- else if (data.type === 'model') {
494
- const modelEvent = data.event;
495
- if (modelEvent.type === 'tool-call') {
496
- this._handleToolCallStart(modelEvent);
472
+ this._agentEvent.emit({
473
+ type: 'tool_call_start',
474
+ data: {
475
+ callId: part.toolCallId,
476
+ toolName: part.toolName,
477
+ input: this._formatToolInput(JSON.stringify(part.input))
478
+ }
479
+ });
480
+ break;
481
+ case 'tool-result':
482
+ this._handleToolResult(part);
483
+ break;
484
+ case 'tool-approval-request':
485
+ // Complete current message before approval
486
+ if (currentMessageId && fullResponse) {
487
+ this._emitMessageComplete(currentMessageId, fullResponse);
488
+ currentMessageId = null;
489
+ fullResponse = '';
497
490
  }
498
- }
499
- }
500
- else if (event.type === 'run_item_stream_event') {
501
- if (event.name === 'tool_output') {
502
- this._handleToolOutput(event);
503
- }
491
+ await this._handleApprovalRequest(part, processResult);
492
+ break;
493
+ // Ignore: text-start, text-end, finish, error, and others
494
+ default:
495
+ break;
504
496
  }
505
497
  }
506
- }
507
- /**
508
- * Formats tool input for display by pretty-printing JSON strings.
509
- * @param input The tool input string to format
510
- * @returns Pretty-printed JSON string
511
- */
512
- _formatToolInput(input) {
513
- try {
514
- // Parse and re-stringify with formatting
515
- const parsed = JSON.parse(input);
516
- return JSON.stringify(parsed, null, 2);
517
- }
518
- catch {
519
- // If parsing fails, return the string as-is
520
- return input;
498
+ // Complete final message if content remains
499
+ if (currentMessageId && fullResponse) {
500
+ this._emitMessageComplete(currentMessageId, fullResponse);
521
501
  }
502
+ return processResult;
522
503
  }
523
504
  /**
524
- * Handles the start of a tool call from the model event.
525
- * @param modelEvent The model event containing tool call information
505
+ * Emits a message_complete event.
526
506
  */
527
- _handleToolCallStart(modelEvent) {
528
- const toolCallId = modelEvent.toolCallId;
529
- const toolName = modelEvent.toolName;
530
- const toolInput = modelEvent.input;
507
+ _emitMessageComplete(messageId, content) {
531
508
  this._agentEvent.emit({
532
- type: 'tool_call_start',
533
- data: {
534
- callId: toolCallId,
535
- toolName,
536
- input: this._formatToolInput(toolInput)
537
- }
509
+ type: 'message_complete',
510
+ data: { messageId, content }
538
511
  });
539
512
  }
540
513
  /**
541
- * Handles tool execution output and completion.
542
- * @param event The tool output event containing result information
543
- */
544
- _handleToolOutput(event) {
545
- const toolEvent = event;
546
- const toolCallOutput = toolEvent.item;
547
- const callId = toolCallOutput.rawItem.callId;
548
- const resultText = typeof toolCallOutput.output === 'string'
549
- ? toolCallOutput.output
550
- : JSON.stringify(toolCallOutput.output, null, 2);
551
- const isError = toolCallOutput.rawItem.type === 'function_call_result' &&
552
- toolCallOutput.rawItem.status === 'incomplete';
553
- const toolName = toolCallOutput.rawItem.type === 'function_call_result'
554
- ? toolCallOutput.rawItem.name
555
- : 'Unknown Tool';
514
+ * Handles tool-result stream parts.
515
+ */
516
+ _handleToolResult(part) {
517
+ const output = typeof part.output === 'string'
518
+ ? part.output
519
+ : JSON.stringify(part.output, null, 2);
520
+ const isError = typeof part.output === 'object' &&
521
+ part.output !== null &&
522
+ 'success' in part.output &&
523
+ part.output.success === false;
556
524
  this._agentEvent.emit({
557
525
  type: 'tool_call_complete',
558
526
  data: {
559
- callId,
560
- toolName,
561
- output: resultText,
527
+ callId: part.toolCallId,
528
+ toolName: part.toolName,
529
+ output,
562
530
  isError
563
531
  }
564
532
  });
565
533
  }
566
534
  /**
567
- * Handles approval request for a single tool call.
568
- * @param interruption The tool approval interruption item
535
+ * Handles tool-approval-request stream parts.
569
536
  */
570
- async _handleSingleToolApproval(interruption) {
571
- const toolName = interruption.rawItem.name || 'Unknown Tool';
572
- const toolInput = interruption.rawItem.arguments || '{}';
573
- const interruptionId = `int-${Date.now()}-${Math.random()}`;
574
- const callId = interruption.rawItem.type === 'function_call'
575
- ? interruption.rawItem.callId
576
- : undefined;
577
- this._pendingApprovals.set(interruptionId, { interruption });
537
+ async _handleApprovalRequest(part, result) {
538
+ const { approvalId, toolCall } = part;
578
539
  this._agentEvent.emit({
579
- type: 'tool_approval_required',
540
+ type: 'tool_approval_request',
580
541
  data: {
581
- interruptionId,
582
- toolName,
583
- toolInput: this._formatToolInput(toolInput),
584
- callId
542
+ approvalId,
543
+ toolCallId: toolCall.toolCallId,
544
+ toolName: toolCall.toolName,
545
+ args: toolCall.input
585
546
  }
586
547
  });
548
+ const approved = await this._waitForApproval(approvalId);
549
+ result.approvalProcessed = true;
550
+ result.approvalResponse = {
551
+ role: 'tool',
552
+ content: [
553
+ {
554
+ type: 'tool-approval-response',
555
+ approvalId,
556
+ approved
557
+ }
558
+ ]
559
+ };
587
560
  }
588
561
  /**
589
- * Handles approval requests for multiple grouped tool calls.
590
- * @param interruptions Array of tool approval interruption items
591
- */
592
- async _handleGroupedToolApprovals(interruptions) {
593
- const groupId = `group-${Date.now()}-${Math.random()}`;
594
- const approvals = interruptions.map(interruption => {
595
- const toolName = interruption.rawItem.name || 'Unknown Tool';
596
- const toolInput = interruption.rawItem.arguments || '{}';
597
- const interruptionId = `int-${Date.now()}-${Math.random()}`;
598
- this._pendingApprovals.set(interruptionId, { interruption, groupId });
599
- return {
600
- interruptionId,
601
- toolName,
602
- toolInput: this._formatToolInput(toolInput)
603
- };
604
- });
605
- this._agentEvent.emit({
606
- type: 'grouped_approval_required',
607
- data: {
608
- groupId,
609
- approvals
610
- }
562
+ * Waits for user approval of a tool call.
563
+ * @param approvalId The approval ID to wait for
564
+ * @returns Promise that resolves to true if approved, false if rejected
565
+ */
566
+ _waitForApproval(approvalId) {
567
+ return new Promise(resolve => {
568
+ this._pendingApprovals.set(approvalId, {
569
+ resolve: (approved) => {
570
+ resolve(approved);
571
+ }
572
+ });
611
573
  });
612
574
  }
575
+ /**
576
+ * Formats tool input for display by pretty-printing JSON strings.
577
+ * @param input The tool input string to format
578
+ * @returns Pretty-printed JSON string
579
+ */
580
+ _formatToolInput(input) {
581
+ try {
582
+ const parsed = JSON.parse(input);
583
+ return JSON.stringify(parsed, null, 2);
584
+ }
585
+ catch {
586
+ return input;
587
+ }
588
+ }
613
589
  /**
614
590
  * Checks if the current provider supports tool calling.
615
591
  * @returns True if the provider supports tool calling, false otherwise
@@ -678,17 +654,24 @@ Guidelines:
678
654
  - End with a brief summary of accomplishments
679
655
  - Use natural, conversational tone throughout
680
656
 
681
- COMMAND DISCOVERY:
682
- - When you want to execute JupyterLab commands, ALWAYS use the 'discover_commands' tool first to find available commands and their metadata, with the optional query parameter.
683
- - The query should typically be a single word, e.g., 'terminal', 'notebook', 'cell', 'file', 'edit', 'view', 'run', etc, to find relevant commands.
684
- - If searching with a query does not yield the desired command, try again with a different query or use an empty query to list all commands.
685
- - This ensures you have complete information about command IDs, descriptions, and required arguments before attempting to execute them. Only after discovering the available commands should you use the 'execute_command' tool with the correct command ID and arguments.
657
+ PRIMARY TOOL USAGE - COMMAND-BASED OPERATIONS:
658
+ Most operations in JupyterLab should be performed using the command system:
659
+ 1. Use 'discover_commands' to find available commands and their metadata
660
+ 2. Use 'execute_command' to perform the actual operation
661
+
662
+ COMMAND DISCOVERY WORKFLOW:
663
+ - For file and notebook operations, use query 'jupyterlab-ai-commands' to discover the curated set of AI commands (~17 commands for file/notebook/directory operations)
664
+ - For other JupyterLab operations (terminal, launcher, UI), use specific keywords like 'terminal', 'launcher', etc.
665
+ - IMPORTANT: Always use 'jupyterlab-ai-commands' as the query for file/notebook tasks - this returns a focused set of commands instead of 100+ generic JupyterLab commands
686
666
 
687
- TOOL SELECTION GUIDELINES:
688
- - For file operations (create, read, write, modify files and directories): Use dedicated file manipulation tools
689
- - For general JupyterLab UI interactions (opening panels, running commands, navigating interface): Use the general command tool (execute_command)
690
- - Examples of file operations: Creating notebooks, editing code files, managing project structure
691
- - Examples of UI interactions: Opening terminal, switching tabs, running notebook cells, accessing menus
667
+ KERNEL PREFERENCE FOR NOTEBOOKS AND CONSOLES:
668
+ When creating notebooks or consoles for a specific programming language, use the 'kernelPreference' argument to specify the kernel:
669
+ - To specify by language: { "kernelPreference": { "language": "python" } } or { "kernelPreference": { "language": "julia" } }
670
+ - To specify by kernel name: { "kernelPreference": { "name": "python3" } } or { "kernelPreference": { "name": "julia-1.10" } }
671
+ - Example: execute_command with commandId="notebook:create-new" and args={ "kernelPreference": { "language": "python" } }
672
+ - Example: execute_command with commandId="console:create" and args={ "kernelPreference": { "name": "python3" } }
673
+ - Common kernel names: "python3" (Python), "julia-1.10" (Julia), "ir" (R), "xpython" (xeus-python)
674
+ - If unsure of exact kernel name, prefer using "language" which will match any kernel supporting that language
692
675
  `;
693
676
  return baseSystemPrompt + progressReportingPrompt;
694
677
  }
@@ -699,18 +682,16 @@ TOOL SELECTION GUIDELINES:
699
682
  _secretsManager;
700
683
  _selectedToolNames;
701
684
  _agent;
702
- _runner;
703
685
  _history;
704
- _mcpServers;
686
+ _mcpTools;
705
687
  _isInitializing;
706
688
  _controller;
707
- _pendingApprovals;
708
- _interruptedState;
709
689
  _agentEvent;
710
690
  _tokenUsage;
711
691
  _tokenUsageChanged;
712
692
  _activeProvider = '';
713
693
  _activeProviderChanged = new Signal(this);
694
+ _pendingApprovals = new Map();
714
695
  }
715
696
  var Private;
716
697
  (function (Private) {