@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.
- package/README.md +5 -214
- package/lib/agent.d.ts +58 -66
- package/lib/agent.js +291 -310
- package/lib/approval-buttons.d.ts +19 -82
- package/lib/approval-buttons.js +36 -289
- package/lib/chat-model-registry.d.ts +6 -0
- package/lib/chat-model-registry.js +4 -1
- package/lib/chat-model.d.ts +26 -54
- package/lib/chat-model.js +277 -303
- package/lib/components/clear-button.d.ts +6 -1
- package/lib/components/clear-button.js +10 -6
- package/lib/components/completion-status.d.ts +5 -0
- package/lib/components/completion-status.js +5 -4
- package/lib/components/model-select.d.ts +6 -1
- package/lib/components/model-select.js +13 -16
- package/lib/components/stop-button.d.ts +6 -1
- package/lib/components/stop-button.js +12 -8
- package/lib/components/token-usage-display.d.ts +5 -0
- package/lib/components/token-usage-display.js +2 -2
- package/lib/components/tool-select.d.ts +6 -1
- package/lib/components/tool-select.js +10 -9
- package/lib/index.d.ts +1 -0
- package/lib/index.js +61 -81
- package/lib/models/settings-model.d.ts +1 -1
- package/lib/models/settings-model.js +40 -26
- package/lib/providers/built-in-providers.js +38 -19
- package/lib/providers/models.d.ts +3 -3
- package/lib/providers/provider-registry.d.ts +3 -4
- package/lib/providers/provider-registry.js +1 -4
- package/lib/tokens.d.ts +5 -6
- package/lib/tools/commands.d.ts +2 -1
- package/lib/tools/commands.js +36 -49
- package/lib/widgets/ai-settings.d.ts +6 -0
- package/lib/widgets/ai-settings.js +72 -71
- package/lib/widgets/main-area-chat.d.ts +2 -0
- package/lib/widgets/main-area-chat.js +5 -2
- package/lib/widgets/provider-config-dialog.d.ts +2 -0
- package/lib/widgets/provider-config-dialog.js +34 -34
- package/package.json +13 -13
- package/schema/settings-model.json +3 -2
- package/src/agent.ts +360 -372
- package/src/approval-buttons.ts +43 -389
- package/src/chat-model-registry.ts +9 -1
- package/src/chat-model.ts +399 -370
- package/src/completion/completion-provider.ts +2 -3
- package/src/components/clear-button.tsx +18 -6
- package/src/components/completion-status.tsx +18 -4
- package/src/components/model-select.tsx +25 -16
- package/src/components/stop-button.tsx +22 -9
- package/src/components/token-usage-display.tsx +14 -2
- package/src/components/tool-select.tsx +27 -9
- package/src/index.ts +78 -134
- package/src/models/settings-model.ts +41 -27
- package/src/providers/built-in-providers.ts +38 -19
- package/src/providers/models.ts +3 -3
- package/src/providers/provider-registry.ts +4 -8
- package/src/tokens.ts +5 -6
- package/src/tools/commands.ts +40 -53
- package/src/widgets/ai-settings.tsx +153 -84
- package/src/widgets/main-area-chat.ts +8 -2
- package/src/widgets/provider-config-dialog.tsx +54 -41
- package/style/base.css +24 -73
- package/lib/mcp/browser.d.ts +0 -68
- package/lib/mcp/browser.js +0 -138
- package/lib/tools/file.d.ts +0 -36
- package/lib/tools/file.js +0 -351
- package/lib/tools/notebook.d.ts +0 -40
- package/lib/tools/notebook.js +0 -779
- package/src/mcp/browser.ts +0 -220
- package/src/tools/file.ts +0 -438
- package/src/tools/notebook.ts +0 -986
package/lib/chat-model.js
CHANGED
|
@@ -4,9 +4,8 @@ import { UUID } from '@lumino/coreutils';
|
|
|
4
4
|
import { Signal } from '@lumino/signaling';
|
|
5
5
|
import { AI_AVATAR } from './icons';
|
|
6
6
|
/**
|
|
7
|
-
* AI Chat Model implementation that provides chat functionality
|
|
8
|
-
*
|
|
9
|
-
* Extends the base AbstractChatModel to provide AI-powered conversations.
|
|
7
|
+
* AI Chat Model implementation that provides chat functionality tool integration,
|
|
8
|
+
* and MCP server support.
|
|
10
9
|
*/
|
|
11
10
|
export class AIChatModel extends AbstractChatModel {
|
|
12
11
|
/**
|
|
@@ -25,6 +24,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
25
24
|
this._settingsModel = options.settingsModel;
|
|
26
25
|
this._user = options.user;
|
|
27
26
|
this._agentManager = options.agentManager;
|
|
27
|
+
this._trans = options.trans;
|
|
28
28
|
// Listen for agent events
|
|
29
29
|
this._agentManager.agentEvent.connect(this._onAgentEvent, this);
|
|
30
30
|
// Listen for settings changes to update chat behavior
|
|
@@ -90,7 +90,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
90
90
|
*/
|
|
91
91
|
clearMessages = () => {
|
|
92
92
|
this.messagesDeleted(0, this.messages.length);
|
|
93
|
-
this.
|
|
93
|
+
this._toolContexts.clear();
|
|
94
94
|
this._agentManager.clearHistory();
|
|
95
95
|
};
|
|
96
96
|
/**
|
|
@@ -127,7 +127,6 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
127
127
|
let enhancedMessage = message.body;
|
|
128
128
|
if (this.input.attachments.length > 0) {
|
|
129
129
|
const attachmentContents = await this._processAttachments(this.input.attachments);
|
|
130
|
-
// Clear attachments right after processing
|
|
131
130
|
this.input.clearAttachments();
|
|
132
131
|
if (attachmentContents.length > 0) {
|
|
133
132
|
enhancedMessage +=
|
|
@@ -152,56 +151,6 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
152
151
|
this.updateWriters([]);
|
|
153
152
|
}
|
|
154
153
|
}
|
|
155
|
-
/**
|
|
156
|
-
* Approves a tool call and updates the UI accordingly.
|
|
157
|
-
* @param interruptionId The interruption ID to approve
|
|
158
|
-
* @param messageId Optional message ID for UI updates
|
|
159
|
-
*/
|
|
160
|
-
async approveToolCall(interruptionId, messageId) {
|
|
161
|
-
await this._agentManager.approveToolCall(interruptionId);
|
|
162
|
-
// Update the tool call box to show "Approved" status
|
|
163
|
-
if (messageId) {
|
|
164
|
-
this._updateToolCallBoxStatus(messageId, 'Approved', true);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Rejects a tool call and updates the UI accordingly.
|
|
169
|
-
* @param interruptionId The interruption ID to reject
|
|
170
|
-
* @param messageId Optional message ID for UI updates
|
|
171
|
-
*/
|
|
172
|
-
async rejectToolCall(interruptionId, messageId) {
|
|
173
|
-
await this._agentManager.rejectToolCall(interruptionId);
|
|
174
|
-
// Update the tool call box to show "Rejected" status
|
|
175
|
-
if (messageId) {
|
|
176
|
-
this._updateToolCallBoxStatus(messageId, 'Rejected', false);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Approves all tools in a group.
|
|
181
|
-
* @param groupId The group ID containing the tool calls
|
|
182
|
-
* @param interruptionIds Array of interruption IDs to approve
|
|
183
|
-
* @param messageId Optional message ID for UI updates
|
|
184
|
-
*/
|
|
185
|
-
async approveGroupedToolCalls(groupId, interruptionIds, messageId) {
|
|
186
|
-
await this._agentManager.approveGroupedToolCalls(groupId, interruptionIds);
|
|
187
|
-
// Update the grouped approval message to show approved status
|
|
188
|
-
if (messageId) {
|
|
189
|
-
this._updateGroupedApprovalStatus(messageId, 'Tools approved', true);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Rejects all tools in a group.
|
|
194
|
-
* @param groupId The group ID containing the tool calls
|
|
195
|
-
* @param interruptionIds Array of interruption IDs to reject
|
|
196
|
-
* @param messageId Optional message ID for UI updates
|
|
197
|
-
*/
|
|
198
|
-
async rejectGroupedToolCalls(groupId, interruptionIds, messageId) {
|
|
199
|
-
await this._agentManager.rejectGroupedToolCalls(groupId, interruptionIds);
|
|
200
|
-
// Update the grouped approval message to show rejected status
|
|
201
|
-
if (messageId) {
|
|
202
|
-
this._updateGroupedApprovalStatus(messageId, 'Tools rejected', false);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
154
|
/**
|
|
206
155
|
* Gets the AI user information for system messages.
|
|
207
156
|
*/
|
|
@@ -243,11 +192,11 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
243
192
|
case 'tool_call_complete':
|
|
244
193
|
this._handleToolCallCompleteEvent(event);
|
|
245
194
|
break;
|
|
246
|
-
case '
|
|
247
|
-
this.
|
|
195
|
+
case 'tool_approval_request':
|
|
196
|
+
this._handleToolApprovalRequest(event);
|
|
248
197
|
break;
|
|
249
|
-
case '
|
|
250
|
-
this.
|
|
198
|
+
case 'tool_approval_resolved':
|
|
199
|
+
this._handleToolApprovalResolved(event);
|
|
251
200
|
break;
|
|
252
201
|
case 'error':
|
|
253
202
|
this._handleErrorEvent(event);
|
|
@@ -293,179 +242,137 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
293
242
|
this._currentStreamingMessage = null;
|
|
294
243
|
}
|
|
295
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Extracts a human-readable summary from tool input for display in the header.
|
|
247
|
+
* @param toolName The name of the tool being called
|
|
248
|
+
* @param input The formatted JSON input string
|
|
249
|
+
* @returns A short summary string or empty string if none available
|
|
250
|
+
*/
|
|
251
|
+
_extractToolSummary(toolName, input) {
|
|
252
|
+
try {
|
|
253
|
+
const parsedInput = JSON.parse(input);
|
|
254
|
+
switch (toolName) {
|
|
255
|
+
case 'execute_command':
|
|
256
|
+
if (parsedInput.commandId) {
|
|
257
|
+
return parsedInput.commandId;
|
|
258
|
+
}
|
|
259
|
+
break;
|
|
260
|
+
case 'discover_commands':
|
|
261
|
+
if (parsedInput.query) {
|
|
262
|
+
return `query: "${parsedInput.query}"`;
|
|
263
|
+
}
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
// If parsing fails, return empty string
|
|
269
|
+
}
|
|
270
|
+
return '';
|
|
271
|
+
}
|
|
296
272
|
/**
|
|
297
273
|
* Handles the start of a tool call execution.
|
|
298
274
|
* @param event Event containing the tool call start data
|
|
299
275
|
*/
|
|
300
276
|
_handleToolCallStartEvent(event) {
|
|
301
|
-
const
|
|
277
|
+
const messageId = UUID.uuid4();
|
|
278
|
+
const summary = this._extractToolSummary(event.data.toolName, event.data.input);
|
|
279
|
+
const context = {
|
|
280
|
+
toolCallId: event.data.callId,
|
|
281
|
+
messageId,
|
|
282
|
+
toolName: event.data.toolName,
|
|
283
|
+
input: event.data.input,
|
|
284
|
+
status: 'pending',
|
|
285
|
+
summary
|
|
286
|
+
};
|
|
287
|
+
this._toolContexts.set(event.data.callId, context);
|
|
302
288
|
const toolCallMessage = {
|
|
303
|
-
body:
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
<div class="jp-ai-tool-section">
|
|
311
|
-
<div class="jp-ai-tool-label">Input</div>
|
|
312
|
-
<pre class="jp-ai-tool-code"><code>${event.data.input}</code></pre>
|
|
313
|
-
</div>
|
|
314
|
-
</div>
|
|
315
|
-
</details>`,
|
|
289
|
+
body: Private.buildToolCallHtml({
|
|
290
|
+
toolName: context.toolName,
|
|
291
|
+
input: context.input,
|
|
292
|
+
status: context.status,
|
|
293
|
+
summary: context.summary,
|
|
294
|
+
trans: this._trans
|
|
295
|
+
}),
|
|
316
296
|
sender: this._getAIUser(),
|
|
317
|
-
id:
|
|
297
|
+
id: messageId,
|
|
318
298
|
time: Date.now() / 1000,
|
|
319
299
|
type: 'msg',
|
|
320
300
|
raw_time: false
|
|
321
301
|
};
|
|
322
|
-
if (event.data.callId) {
|
|
323
|
-
this._pendingToolCalls.set(event.data.callId, toolCallMessageId);
|
|
324
|
-
}
|
|
325
302
|
this.messageAdded(toolCallMessage);
|
|
326
303
|
}
|
|
327
304
|
/**
|
|
328
305
|
* Handles the completion of a tool call execution.
|
|
329
|
-
* @param event Event containing the tool call completion data
|
|
330
306
|
*/
|
|
331
307
|
_handleToolCallCompleteEvent(event) {
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
if (existingMessageIndex !== -1) {
|
|
336
|
-
const existingMessage = this.messages[existingMessageIndex];
|
|
337
|
-
const inputJson = existingMessage.body.match(/<code>([\s\S]*?)<\/code>/)?.[1] || '';
|
|
338
|
-
const statusClass = event.data.isError
|
|
339
|
-
? 'jp-ai-tool-error'
|
|
340
|
-
: 'jp-ai-tool-completed';
|
|
341
|
-
const statusText = event.data.isError ? 'Error' : 'Completed';
|
|
342
|
-
const statusColor = event.data.isError
|
|
343
|
-
? 'jp-ai-tool-status-error'
|
|
344
|
-
: 'jp-ai-tool-status-completed';
|
|
345
|
-
const updatedMessage = {
|
|
346
|
-
...existingMessage,
|
|
347
|
-
body: `<details class="jp-ai-tool-call ${statusClass}">
|
|
348
|
-
<summary class="jp-ai-tool-header">
|
|
349
|
-
<div class="jp-ai-tool-icon">⚡</div>
|
|
350
|
-
<div class="jp-ai-tool-title">${event.data.toolName}</div>
|
|
351
|
-
<div class="jp-ai-tool-status ${statusColor}">${statusText}</div>
|
|
352
|
-
</summary>
|
|
353
|
-
<div class="jp-ai-tool-body">
|
|
354
|
-
<div class="jp-ai-tool-section">
|
|
355
|
-
<div class="jp-ai-tool-label">Input</div>
|
|
356
|
-
<pre class="jp-ai-tool-code"><code>${inputJson}</code></pre>
|
|
357
|
-
</div>
|
|
358
|
-
<div class="jp-ai-tool-section">
|
|
359
|
-
<div class="jp-ai-tool-label">${event.data.isError ? 'Error' : 'Result'}</div>
|
|
360
|
-
<pre class="jp-ai-tool-code"><code>${event.data.output}</code></pre>
|
|
361
|
-
</div>
|
|
362
|
-
</div>
|
|
363
|
-
</details>`
|
|
364
|
-
};
|
|
365
|
-
this.messageAdded(updatedMessage);
|
|
366
|
-
this._pendingToolCalls.delete(event.data.callId);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
308
|
+
const status = event.data.isError ? 'error' : 'completed';
|
|
309
|
+
this._updateToolCallUI(event.data.callId, status, event.data.output);
|
|
310
|
+
this._toolContexts.delete(event.data.callId);
|
|
369
311
|
}
|
|
370
312
|
/**
|
|
371
|
-
* Handles
|
|
372
|
-
* @param event Event containing the tool approval request data
|
|
313
|
+
* Handles error events from the AI agent.
|
|
373
314
|
*/
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
const messageId = this._pendingToolCalls.get(event.data.callId);
|
|
378
|
-
if (messageId) {
|
|
379
|
-
const existingMessageIndex = this.messages.findIndex(msg => msg.id === messageId);
|
|
380
|
-
if (existingMessageIndex !== -1) {
|
|
381
|
-
const existingMessage = this.messages[existingMessageIndex];
|
|
382
|
-
const assistantName = this._getAIUser().display_name;
|
|
383
|
-
const updatedMessage = {
|
|
384
|
-
...existingMessage,
|
|
385
|
-
body: `<details class="jp-ai-tool-call jp-ai-tool-pending" open>
|
|
386
|
-
<summary class="jp-ai-tool-header">
|
|
387
|
-
<div class="jp-ai-tool-icon">⚡</div>
|
|
388
|
-
<div class="jp-ai-tool-title">${event.data.toolName}</div>
|
|
389
|
-
<div class="jp-ai-tool-status jp-ai-tool-status-pending">Needs Approval</div>
|
|
390
|
-
</summary>
|
|
391
|
-
<div class="jp-ai-tool-body">
|
|
392
|
-
<div class="jp-ai-tool-section">
|
|
393
|
-
<div class="jp-ai-tool-label">${assistantName} wants to execute this tool. Do you approve?</div>
|
|
394
|
-
<pre class="jp-ai-tool-code"><code>${event.data.toolInput}</code></pre>
|
|
395
|
-
</div>
|
|
396
|
-
[APPROVAL_BUTTONS:${event.data.interruptionId}]
|
|
397
|
-
</div>
|
|
398
|
-
</details>`
|
|
399
|
-
};
|
|
400
|
-
this.messageAdded(updatedMessage);
|
|
401
|
-
this.updateWriters([]);
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
// Fallback: create separate approval message
|
|
407
|
-
const approvalMessageId = UUID.uuid4();
|
|
408
|
-
const assistantName = this._getAIUser().display_name;
|
|
409
|
-
const approvalMessage = {
|
|
410
|
-
body: `**🤖 Tool Approval Required: ${event.data.toolName}**
|
|
411
|
-
|
|
412
|
-
${assistantName} wants to execute this tool. Do you approve?
|
|
413
|
-
|
|
414
|
-
\`\`\`json
|
|
415
|
-
${event.data.toolInput}
|
|
416
|
-
\`\`\`
|
|
417
|
-
|
|
418
|
-
[APPROVAL_BUTTONS:${event.data.interruptionId}]`,
|
|
315
|
+
_handleErrorEvent(event) {
|
|
316
|
+
this.messageAdded({
|
|
317
|
+
body: `Error generating response: ${event.data.error.message}`,
|
|
419
318
|
sender: this._getAIUser(),
|
|
420
|
-
id:
|
|
319
|
+
id: UUID.uuid4(),
|
|
421
320
|
time: Date.now() / 1000,
|
|
422
321
|
type: 'msg',
|
|
423
322
|
raw_time: false
|
|
424
|
-
};
|
|
425
|
-
this.messageAdded(approvalMessage);
|
|
426
|
-
this.updateWriters([]); // Stop showing "AI is writing"
|
|
323
|
+
});
|
|
427
324
|
}
|
|
428
325
|
/**
|
|
429
|
-
* Handles
|
|
430
|
-
* @param event Event containing the grouped tool approval request data
|
|
326
|
+
* Handles tool approval request events from the AI agent.
|
|
431
327
|
*/
|
|
432
|
-
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
${assistantName} wants to execute ${event.data.approvals.length} tools. Do you approve?
|
|
442
|
-
|
|
443
|
-
${toolsList}
|
|
444
|
-
|
|
445
|
-
[GROUP_APPROVAL_BUTTONS:${event.data.groupId}:${event.data.approvals.map(info => info.interruptionId).join(',')}]`,
|
|
446
|
-
sender: this._getAIUser(),
|
|
447
|
-
id: approvalMessageId,
|
|
448
|
-
time: Date.now() / 1000,
|
|
449
|
-
type: 'msg',
|
|
450
|
-
raw_time: false
|
|
451
|
-
};
|
|
452
|
-
this.messageAdded(approvalMessage);
|
|
453
|
-
this.updateWriters([]); // Stop showing "AI is writing"
|
|
328
|
+
_handleToolApprovalRequest(event) {
|
|
329
|
+
const context = this._toolContexts.get(event.data.toolCallId);
|
|
330
|
+
if (!context) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
context.approvalId = event.data.approvalId;
|
|
334
|
+
context.input = JSON.stringify(event.data.args, null, 2);
|
|
335
|
+
this._updateToolCallUI(event.data.toolCallId, 'awaiting_approval');
|
|
454
336
|
}
|
|
455
337
|
/**
|
|
456
|
-
* Handles
|
|
457
|
-
* @param event Event containing the error information
|
|
338
|
+
* Handles tool approval resolved events from the AI agent.
|
|
458
339
|
*/
|
|
459
|
-
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
340
|
+
_handleToolApprovalResolved(event) {
|
|
341
|
+
const context = Array.from(this._toolContexts.values()).find(ctx => ctx.approvalId === event.data.approvalId);
|
|
342
|
+
if (!context) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const status = event.data.approved ? 'approved' : 'rejected';
|
|
346
|
+
this._updateToolCallUI(context.toolCallId, status);
|
|
347
|
+
if (!event.data.approved) {
|
|
348
|
+
this._toolContexts.delete(context.toolCallId);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Updates a tool call's UI with new status and optional output.
|
|
353
|
+
*/
|
|
354
|
+
_updateToolCallUI(toolCallId, status, output) {
|
|
355
|
+
const context = this._toolContexts.get(toolCallId);
|
|
356
|
+
if (!context) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
const existingMessage = this.messages.find(msg => msg.id === context.messageId);
|
|
360
|
+
if (!existingMessage) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
context.status = status;
|
|
364
|
+
this.messageAdded({
|
|
365
|
+
...existingMessage,
|
|
366
|
+
body: Private.buildToolCallHtml({
|
|
367
|
+
toolName: context.toolName,
|
|
368
|
+
input: context.input,
|
|
369
|
+
status: context.status,
|
|
370
|
+
summary: context.summary,
|
|
371
|
+
output,
|
|
372
|
+
approvalId: context.approvalId,
|
|
373
|
+
trans: this._trans
|
|
374
|
+
})
|
|
375
|
+
});
|
|
469
376
|
}
|
|
470
377
|
/**
|
|
471
378
|
* Processes file attachments and returns their content as formatted strings.
|
|
@@ -508,16 +415,34 @@ ${toolsList}
|
|
|
508
415
|
return null;
|
|
509
416
|
}
|
|
510
417
|
try {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
418
|
+
// Try reading from live notebook if open
|
|
419
|
+
const widget = this.input.documentManager?.findWidget(attachment.value);
|
|
420
|
+
let cellData;
|
|
421
|
+
let kernelLang = 'text';
|
|
422
|
+
const ymodel = widget?.context.model.sharedModel;
|
|
423
|
+
if (ymodel) {
|
|
424
|
+
const nb = ymodel.toJSON();
|
|
425
|
+
cellData = nb.cells;
|
|
426
|
+
const lang = nb.metadata.language_info?.name ||
|
|
427
|
+
nb.metadata.kernelspec?.language ||
|
|
428
|
+
'text';
|
|
429
|
+
kernelLang = String(lang);
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
// Fallback: reading from disk
|
|
433
|
+
const model = await this.input.documentManager?.services.contents.get(attachment.value);
|
|
434
|
+
if (!model || model.type !== 'notebook') {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
cellData = model.content.cells ?? [];
|
|
438
|
+
kernelLang =
|
|
439
|
+
model.content.metadata.language_info?.name ||
|
|
440
|
+
model.content.metadata.kernelspec?.language ||
|
|
441
|
+
'text';
|
|
514
442
|
}
|
|
515
|
-
const kernelLang = model.content?.metadata?.language_info?.name ||
|
|
516
|
-
model.content?.metadata?.kernelspec?.language ||
|
|
517
|
-
'text';
|
|
518
443
|
const selectedCells = attachment.cells
|
|
519
444
|
.map(cellInfo => {
|
|
520
|
-
const cell =
|
|
445
|
+
const cell = cellData.find(c => c.id === cellInfo.id);
|
|
521
446
|
if (!cell) {
|
|
522
447
|
return null;
|
|
523
448
|
}
|
|
@@ -548,9 +473,9 @@ ${toolsList}
|
|
|
548
473
|
case 'application/vnd.jupyter.widget-view+json':
|
|
549
474
|
return `Widget: ${value.model_id ?? 'unknown model'}`;
|
|
550
475
|
case 'image/png':
|
|
551
|
-
return `}...)`;
|
|
476
|
+
return `.slice(0, 100)}...)`;
|
|
552
477
|
case 'image/jpeg':
|
|
553
|
-
return `}...)`;
|
|
478
|
+
return `.slice(0, 100)}...)`;
|
|
554
479
|
case 'image/svg+xml':
|
|
555
480
|
return String(value).slice(0, 500) + '...\n[svg truncated]';
|
|
556
481
|
case 'text/html':
|
|
@@ -575,8 +500,9 @@ ${toolsList}
|
|
|
575
500
|
}
|
|
576
501
|
let outputs = '';
|
|
577
502
|
if (cellType === 'code' && Array.isArray(cell.outputs)) {
|
|
578
|
-
|
|
579
|
-
|
|
503
|
+
const outputsArray = cell.outputs;
|
|
504
|
+
outputs = outputsArray
|
|
505
|
+
.map(output => {
|
|
580
506
|
if (output.output_type === 'stream') {
|
|
581
507
|
return output.text;
|
|
582
508
|
}
|
|
@@ -630,34 +556,37 @@ ${toolsList}
|
|
|
630
556
|
return null;
|
|
631
557
|
}
|
|
632
558
|
try {
|
|
633
|
-
|
|
634
|
-
|
|
559
|
+
// Try reading from an open widget first
|
|
560
|
+
const widget = this.input.documentManager?.findWidget(attachment.value);
|
|
561
|
+
if (widget && widget.context && widget.context.model) {
|
|
562
|
+
const model = widget.context.model;
|
|
563
|
+
const ymodel = model.sharedModel;
|
|
564
|
+
if (typeof ymodel.getSource === 'function') {
|
|
565
|
+
const source = ymodel.getSource();
|
|
566
|
+
return typeof source === 'string'
|
|
567
|
+
? source
|
|
568
|
+
: JSON.stringify(source, null, 2);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
// If not open, load from disk
|
|
572
|
+
const diskModel = await this.input.documentManager?.services.contents.get(attachment.value);
|
|
573
|
+
if (!diskModel?.content) {
|
|
635
574
|
return null;
|
|
636
575
|
}
|
|
637
|
-
if (
|
|
576
|
+
if (diskModel.type === 'file') {
|
|
638
577
|
// Regular file content
|
|
639
|
-
return
|
|
578
|
+
return diskModel.content;
|
|
640
579
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
}
|
|
649
|
-
if (cleanCell.execution_count) {
|
|
650
|
-
cleanCell.execution_count = null;
|
|
651
|
-
}
|
|
652
|
-
return cleanCell;
|
|
653
|
-
});
|
|
654
|
-
const notebookModel = {
|
|
655
|
-
cells,
|
|
656
|
-
metadata: model.metadata || {},
|
|
657
|
-
nbformat: model.nbformat || 4,
|
|
658
|
-
nbformat_minor: model.nbformat_minor || 4
|
|
580
|
+
if (diskModel.type === 'notebook') {
|
|
581
|
+
const cleaned = {
|
|
582
|
+
...diskModel,
|
|
583
|
+
cells: diskModel.content.cells.map((cell) => ({
|
|
584
|
+
...cell,
|
|
585
|
+
outputs: [],
|
|
586
|
+
execution_count: null
|
|
587
|
+
}))
|
|
659
588
|
};
|
|
660
|
-
return JSON.stringify(
|
|
589
|
+
return JSON.stringify(cleaned);
|
|
661
590
|
}
|
|
662
591
|
return null;
|
|
663
592
|
}
|
|
@@ -666,80 +595,125 @@ ${toolsList}
|
|
|
666
595
|
return null;
|
|
667
596
|
}
|
|
668
597
|
}
|
|
598
|
+
// Private fields
|
|
599
|
+
_settingsModel;
|
|
600
|
+
_user;
|
|
601
|
+
_toolContexts = new Map();
|
|
602
|
+
_agentManager;
|
|
603
|
+
_currentStreamingMessage = null;
|
|
604
|
+
_nameChanged = new Signal(this);
|
|
605
|
+
_trans;
|
|
606
|
+
}
|
|
607
|
+
var Private;
|
|
608
|
+
(function (Private) {
|
|
609
|
+
function escapeHtml(value) {
|
|
610
|
+
// Prefer the same native escaping approach used in JupyterLab itself
|
|
611
|
+
// (e.g. `@jupyterlab/completer`).
|
|
612
|
+
if (typeof document !== 'undefined') {
|
|
613
|
+
const node = document.createElement('span');
|
|
614
|
+
node.textContent = value;
|
|
615
|
+
return node.innerHTML;
|
|
616
|
+
}
|
|
617
|
+
// Fallback
|
|
618
|
+
return value
|
|
619
|
+
.replace(/&/g, '&')
|
|
620
|
+
.replace(/</g, '<')
|
|
621
|
+
.replace(/>/g, '>')
|
|
622
|
+
.replace(/"/g, '"')
|
|
623
|
+
.replace(/'/g, ''');
|
|
624
|
+
}
|
|
625
|
+
Private.escapeHtml = escapeHtml;
|
|
626
|
+
const STATUS_CONFIG = {
|
|
627
|
+
pending: {
|
|
628
|
+
cssClass: 'jp-ai-tool-pending',
|
|
629
|
+
statusClass: 'jp-ai-tool-status-pending'
|
|
630
|
+
},
|
|
631
|
+
awaiting_approval: {
|
|
632
|
+
cssClass: 'jp-ai-tool-pending',
|
|
633
|
+
statusClass: 'jp-ai-tool-status-approval',
|
|
634
|
+
open: true
|
|
635
|
+
},
|
|
636
|
+
approved: {
|
|
637
|
+
cssClass: 'jp-ai-tool-pending',
|
|
638
|
+
statusClass: 'jp-ai-tool-status-completed'
|
|
639
|
+
},
|
|
640
|
+
rejected: {
|
|
641
|
+
cssClass: 'jp-ai-tool-error',
|
|
642
|
+
statusClass: 'jp-ai-tool-status-error'
|
|
643
|
+
},
|
|
644
|
+
completed: {
|
|
645
|
+
cssClass: 'jp-ai-tool-completed',
|
|
646
|
+
statusClass: 'jp-ai-tool-status-completed'
|
|
647
|
+
},
|
|
648
|
+
error: {
|
|
649
|
+
cssClass: 'jp-ai-tool-error',
|
|
650
|
+
statusClass: 'jp-ai-tool-status-error'
|
|
651
|
+
}
|
|
652
|
+
};
|
|
669
653
|
/**
|
|
670
|
-
*
|
|
671
|
-
* @param messageId The message ID to update
|
|
672
|
-
* @param status The status text to display
|
|
673
|
-
* @param isSuccess Whether the action was successful
|
|
654
|
+
* Returns the translated status text for a given tool status.
|
|
674
655
|
*/
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
<div class="jp-ai-group-approval-${statusClass}">
|
|
691
|
-
Status: ${status}
|
|
692
|
-
</div>`
|
|
693
|
-
};
|
|
694
|
-
this.messageAdded(updatedMessage);
|
|
656
|
+
const getStatusText = (status, trans) => {
|
|
657
|
+
switch (status) {
|
|
658
|
+
case 'pending':
|
|
659
|
+
return trans.__('Running...');
|
|
660
|
+
case 'awaiting_approval':
|
|
661
|
+
return trans.__('Awaiting Approval');
|
|
662
|
+
case 'approved':
|
|
663
|
+
return trans.__('Approved - Executing...');
|
|
664
|
+
case 'rejected':
|
|
665
|
+
return trans.__('Rejected');
|
|
666
|
+
case 'completed':
|
|
667
|
+
return trans.__('Completed');
|
|
668
|
+
case 'error':
|
|
669
|
+
return trans.__('Error');
|
|
695
670
|
}
|
|
696
|
-
}
|
|
671
|
+
};
|
|
697
672
|
/**
|
|
698
|
-
*
|
|
699
|
-
* @param messageId The message ID to update
|
|
700
|
-
* @param status The status text to display
|
|
701
|
-
* @param isSuccess Whether the action was successful
|
|
673
|
+
* Builds HTML for a tool call display.
|
|
702
674
|
*/
|
|
703
|
-
|
|
704
|
-
const
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
675
|
+
function buildToolCallHtml(options) {
|
|
676
|
+
const { toolName, input, status, summary, output, approvalId, trans } = options;
|
|
677
|
+
const config = STATUS_CONFIG[status];
|
|
678
|
+
const statusText = getStatusText(status, trans);
|
|
679
|
+
const escapedToolName = escapeHtml(toolName);
|
|
680
|
+
const escapedInput = escapeHtml(input);
|
|
681
|
+
const openAttr = config.open ? ' open' : '';
|
|
682
|
+
const summaryHtml = summary
|
|
683
|
+
? `<span class="jp-ai-tool-summary">${escapeHtml(summary)}</span>`
|
|
684
|
+
: '';
|
|
685
|
+
let bodyContent = `
|
|
686
|
+
<div class="jp-ai-tool-section">
|
|
687
|
+
<div class="jp-ai-tool-label">${trans.__('Input')}</div>
|
|
688
|
+
<pre class="jp-ai-tool-code"><code>${escapedInput}</code></pre>
|
|
689
|
+
</div>`;
|
|
690
|
+
// Add approval buttons if awaiting approval
|
|
691
|
+
if (status === 'awaiting_approval' && approvalId) {
|
|
692
|
+
bodyContent += `
|
|
693
|
+
<div class="jp-ai-tool-approval-buttons jp-ai-approval-id--${approvalId}">
|
|
694
|
+
<button class="jp-ai-approval-btn jp-ai-approval-approve">${trans.__('Approve')}</button>
|
|
695
|
+
<button class="jp-ai-approval-btn jp-ai-approval-reject">${trans.__('Reject')}</button>
|
|
696
|
+
</div>`;
|
|
697
|
+
}
|
|
698
|
+
// Add output/result section if provided
|
|
699
|
+
if (output !== undefined) {
|
|
700
|
+
const escapedOutput = escapeHtml(output);
|
|
701
|
+
const label = status === 'error' ? trans.__('Error') : trans.__('Result');
|
|
702
|
+
bodyContent += `
|
|
703
|
+
<div class="jp-ai-tool-section">
|
|
704
|
+
<div class="jp-ai-tool-label">${label}</div>
|
|
705
|
+
<pre class="jp-ai-tool-code"><code>${escapedOutput}</code></pre>
|
|
706
|
+
</div>`;
|
|
707
|
+
}
|
|
708
|
+
return `<details class="jp-ai-tool-call ${config.cssClass}"${openAttr}>
|
|
722
709
|
<summary class="jp-ai-tool-header">
|
|
723
710
|
<div class="jp-ai-tool-icon">⚡</div>
|
|
724
|
-
<div class="jp-ai-tool-title">${
|
|
725
|
-
<div class="jp-ai-tool-status ${
|
|
711
|
+
<div class="jp-ai-tool-title">${escapedToolName}${summaryHtml}</div>
|
|
712
|
+
<div class="jp-ai-tool-status ${config.statusClass}">${statusText}</div>
|
|
726
713
|
</summary>
|
|
727
|
-
<div class="jp-ai-tool-body"
|
|
728
|
-
<div class="jp-ai-tool-section">
|
|
729
|
-
<div class="jp-ai-tool-label">Input</div>
|
|
730
|
-
<pre class="jp-ai-tool-code"><code>${toolInput}</code></pre>
|
|
731
|
-
</div>
|
|
714
|
+
<div class="jp-ai-tool-body">${bodyContent}
|
|
732
715
|
</div>
|
|
733
|
-
</details
|
|
734
|
-
};
|
|
735
|
-
this.messageAdded(updatedMessage);
|
|
736
|
-
}
|
|
716
|
+
</details>`;
|
|
737
717
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
_user;
|
|
741
|
-
_pendingToolCalls = new Map();
|
|
742
|
-
_agentManager;
|
|
743
|
-
_currentStreamingMessage = null;
|
|
744
|
-
_nameChanged = new Signal(this);
|
|
745
|
-
}
|
|
718
|
+
Private.buildToolCallHtml = buildToolCallHtml;
|
|
719
|
+
})(Private || (Private = {}));
|