@jupyterlite/ai 0.9.1 → 0.10.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 +274 -300
- 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 +19 -54
- package/lib/chat-model.js +243 -303
- package/lib/components/clear-button.d.ts +6 -1
- package/lib/components/clear-button.js +8 -3
- 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 +9 -8
- package/lib/components/stop-button.d.ts +6 -1
- package/lib/components/stop-button.js +8 -3
- 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 +6 -5
- package/lib/index.js +58 -38
- package/lib/models/settings-model.d.ts +1 -1
- 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 +37 -46
- package/lib/tools/file.js +49 -73
- package/lib/tools/notebook.js +370 -445
- 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 +12 -12
- package/src/agent.ts +342 -361
- package/src/approval-buttons.ts +43 -389
- package/src/chat-model-registry.ts +9 -1
- package/src/chat-model.ts +355 -370
- package/src/completion/completion-provider.ts +2 -3
- package/src/components/clear-button.tsx +16 -3
- package/src/components/completion-status.tsx +18 -4
- package/src/components/model-select.tsx +21 -8
- package/src/components/stop-button.tsx +16 -3
- package/src/components/token-usage-display.tsx +14 -2
- package/src/components/tool-select.tsx +23 -5
- package/src/index.ts +75 -36
- package/src/models/settings-model.ts +1 -1
- 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 +39 -50
- package/src/tools/file.ts +49 -75
- package/src/tools/notebook.ts +451 -510
- 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 +13 -73
- package/lib/mcp/browser.d.ts +0 -68
- package/lib/mcp/browser.js +0 -138
- package/src/mcp/browser.ts +0 -220
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);
|
|
@@ -298,174 +247,101 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
298
247
|
* @param event Event containing the tool call start data
|
|
299
248
|
*/
|
|
300
249
|
_handleToolCallStartEvent(event) {
|
|
301
|
-
const
|
|
250
|
+
const messageId = UUID.uuid4();
|
|
251
|
+
const context = {
|
|
252
|
+
toolCallId: event.data.callId,
|
|
253
|
+
messageId,
|
|
254
|
+
toolName: event.data.toolName,
|
|
255
|
+
input: event.data.input,
|
|
256
|
+
status: 'pending'
|
|
257
|
+
};
|
|
258
|
+
this._toolContexts.set(event.data.callId, context);
|
|
302
259
|
const toolCallMessage = {
|
|
303
|
-
body:
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
<div class="jp-ai-tool-body">
|
|
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>`,
|
|
260
|
+
body: Private.buildToolCallHtml({
|
|
261
|
+
toolName: context.toolName,
|
|
262
|
+
input: context.input,
|
|
263
|
+
status: context.status,
|
|
264
|
+
trans: this._trans
|
|
265
|
+
}),
|
|
316
266
|
sender: this._getAIUser(),
|
|
317
|
-
id:
|
|
267
|
+
id: messageId,
|
|
318
268
|
time: Date.now() / 1000,
|
|
319
269
|
type: 'msg',
|
|
320
270
|
raw_time: false
|
|
321
271
|
};
|
|
322
|
-
if (event.data.callId) {
|
|
323
|
-
this._pendingToolCalls.set(event.data.callId, toolCallMessageId);
|
|
324
|
-
}
|
|
325
272
|
this.messageAdded(toolCallMessage);
|
|
326
273
|
}
|
|
327
274
|
/**
|
|
328
275
|
* Handles the completion of a tool call execution.
|
|
329
|
-
* @param event Event containing the tool call completion data
|
|
330
276
|
*/
|
|
331
277
|
_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
|
-
}
|
|
278
|
+
const status = event.data.isError ? 'error' : 'completed';
|
|
279
|
+
this._updateToolCallUI(event.data.callId, status, event.data.output);
|
|
280
|
+
this._toolContexts.delete(event.data.callId);
|
|
369
281
|
}
|
|
370
282
|
/**
|
|
371
|
-
* Handles
|
|
372
|
-
* @param event Event containing the tool approval request data
|
|
283
|
+
* Handles error events from the AI agent.
|
|
373
284
|
*/
|
|
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}]`,
|
|
285
|
+
_handleErrorEvent(event) {
|
|
286
|
+
this.messageAdded({
|
|
287
|
+
body: `Error generating response: ${event.data.error.message}`,
|
|
419
288
|
sender: this._getAIUser(),
|
|
420
|
-
id:
|
|
289
|
+
id: UUID.uuid4(),
|
|
421
290
|
time: Date.now() / 1000,
|
|
422
291
|
type: 'msg',
|
|
423
292
|
raw_time: false
|
|
424
|
-
};
|
|
425
|
-
this.messageAdded(approvalMessage);
|
|
426
|
-
this.updateWriters([]); // Stop showing "AI is writing"
|
|
293
|
+
});
|
|
427
294
|
}
|
|
428
295
|
/**
|
|
429
|
-
* Handles
|
|
430
|
-
* @param event Event containing the grouped tool approval request data
|
|
296
|
+
* Handles tool approval request events from the AI agent.
|
|
431
297
|
*/
|
|
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"
|
|
298
|
+
_handleToolApprovalRequest(event) {
|
|
299
|
+
const context = this._toolContexts.get(event.data.toolCallId);
|
|
300
|
+
if (!context) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
context.approvalId = event.data.approvalId;
|
|
304
|
+
context.input = JSON.stringify(event.data.args, null, 2);
|
|
305
|
+
this._updateToolCallUI(event.data.toolCallId, 'awaiting_approval');
|
|
454
306
|
}
|
|
455
307
|
/**
|
|
456
|
-
* Handles
|
|
457
|
-
* @param event Event containing the error information
|
|
308
|
+
* Handles tool approval resolved events from the AI agent.
|
|
458
309
|
*/
|
|
459
|
-
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
310
|
+
_handleToolApprovalResolved(event) {
|
|
311
|
+
const context = Array.from(this._toolContexts.values()).find(ctx => ctx.approvalId === event.data.approvalId);
|
|
312
|
+
if (!context) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const status = event.data.approved ? 'approved' : 'rejected';
|
|
316
|
+
this._updateToolCallUI(context.toolCallId, status);
|
|
317
|
+
if (!event.data.approved) {
|
|
318
|
+
this._toolContexts.delete(context.toolCallId);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Updates a tool call's UI with new status and optional output.
|
|
323
|
+
*/
|
|
324
|
+
_updateToolCallUI(toolCallId, status, output) {
|
|
325
|
+
const context = this._toolContexts.get(toolCallId);
|
|
326
|
+
if (!context) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
const existingMessage = this.messages.find(msg => msg.id === context.messageId);
|
|
330
|
+
if (!existingMessage) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
context.status = status;
|
|
334
|
+
this.messageAdded({
|
|
335
|
+
...existingMessage,
|
|
336
|
+
body: Private.buildToolCallHtml({
|
|
337
|
+
toolName: context.toolName,
|
|
338
|
+
input: context.input,
|
|
339
|
+
status: context.status,
|
|
340
|
+
output,
|
|
341
|
+
approvalId: context.approvalId,
|
|
342
|
+
trans: this._trans
|
|
343
|
+
})
|
|
344
|
+
});
|
|
469
345
|
}
|
|
470
346
|
/**
|
|
471
347
|
* Processes file attachments and returns their content as formatted strings.
|
|
@@ -508,16 +384,34 @@ ${toolsList}
|
|
|
508
384
|
return null;
|
|
509
385
|
}
|
|
510
386
|
try {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
387
|
+
// Try reading from live notebook if open
|
|
388
|
+
const widget = this.input.documentManager?.findWidget(attachment.value);
|
|
389
|
+
let cellData;
|
|
390
|
+
let kernelLang = 'text';
|
|
391
|
+
const ymodel = widget?.context.model.sharedModel;
|
|
392
|
+
if (ymodel) {
|
|
393
|
+
const nb = ymodel.toJSON();
|
|
394
|
+
cellData = nb.cells;
|
|
395
|
+
const lang = nb.metadata.language_info?.name ||
|
|
396
|
+
nb.metadata.kernelspec?.language ||
|
|
397
|
+
'text';
|
|
398
|
+
kernelLang = String(lang);
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
// Fallback: reading from disk
|
|
402
|
+
const model = await this.input.documentManager?.services.contents.get(attachment.value);
|
|
403
|
+
if (!model || model.type !== 'notebook') {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
cellData = model.content.cells ?? [];
|
|
407
|
+
kernelLang =
|
|
408
|
+
model.content.metadata.language_info?.name ||
|
|
409
|
+
model.content.metadata.kernelspec?.language ||
|
|
410
|
+
'text';
|
|
514
411
|
}
|
|
515
|
-
const kernelLang = model.content?.metadata?.language_info?.name ||
|
|
516
|
-
model.content?.metadata?.kernelspec?.language ||
|
|
517
|
-
'text';
|
|
518
412
|
const selectedCells = attachment.cells
|
|
519
413
|
.map(cellInfo => {
|
|
520
|
-
const cell =
|
|
414
|
+
const cell = cellData.find(c => c.id === cellInfo.id);
|
|
521
415
|
if (!cell) {
|
|
522
416
|
return null;
|
|
523
417
|
}
|
|
@@ -548,9 +442,9 @@ ${toolsList}
|
|
|
548
442
|
case 'application/vnd.jupyter.widget-view+json':
|
|
549
443
|
return `Widget: ${value.model_id ?? 'unknown model'}`;
|
|
550
444
|
case 'image/png':
|
|
551
|
-
return `}...)`;
|
|
445
|
+
return `.slice(0, 100)}...)`;
|
|
552
446
|
case 'image/jpeg':
|
|
553
|
-
return `}...)`;
|
|
447
|
+
return `.slice(0, 100)}...)`;
|
|
554
448
|
case 'image/svg+xml':
|
|
555
449
|
return String(value).slice(0, 500) + '...\n[svg truncated]';
|
|
556
450
|
case 'text/html':
|
|
@@ -575,8 +469,9 @@ ${toolsList}
|
|
|
575
469
|
}
|
|
576
470
|
let outputs = '';
|
|
577
471
|
if (cellType === 'code' && Array.isArray(cell.outputs)) {
|
|
578
|
-
|
|
579
|
-
|
|
472
|
+
const outputsArray = cell.outputs;
|
|
473
|
+
outputs = outputsArray
|
|
474
|
+
.map(output => {
|
|
580
475
|
if (output.output_type === 'stream') {
|
|
581
476
|
return output.text;
|
|
582
477
|
}
|
|
@@ -630,34 +525,37 @@ ${toolsList}
|
|
|
630
525
|
return null;
|
|
631
526
|
}
|
|
632
527
|
try {
|
|
633
|
-
|
|
634
|
-
|
|
528
|
+
// Try reading from an open widget first
|
|
529
|
+
const widget = this.input.documentManager?.findWidget(attachment.value);
|
|
530
|
+
if (widget && widget.context && widget.context.model) {
|
|
531
|
+
const model = widget.context.model;
|
|
532
|
+
const ymodel = model.sharedModel;
|
|
533
|
+
if (typeof ymodel.getSource === 'function') {
|
|
534
|
+
const source = ymodel.getSource();
|
|
535
|
+
return typeof source === 'string'
|
|
536
|
+
? source
|
|
537
|
+
: JSON.stringify(source, null, 2);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
// If not open, load from disk
|
|
541
|
+
const diskModel = await this.input.documentManager?.services.contents.get(attachment.value);
|
|
542
|
+
if (!diskModel?.content) {
|
|
635
543
|
return null;
|
|
636
544
|
}
|
|
637
|
-
if (
|
|
545
|
+
if (diskModel.type === 'file') {
|
|
638
546
|
// Regular file content
|
|
639
|
-
return
|
|
547
|
+
return diskModel.content;
|
|
640
548
|
}
|
|
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
|
|
549
|
+
if (diskModel.type === 'notebook') {
|
|
550
|
+
const cleaned = {
|
|
551
|
+
...diskModel,
|
|
552
|
+
cells: diskModel.content.cells.map((cell) => ({
|
|
553
|
+
...cell,
|
|
554
|
+
outputs: [],
|
|
555
|
+
execution_count: null
|
|
556
|
+
}))
|
|
659
557
|
};
|
|
660
|
-
return JSON.stringify(
|
|
558
|
+
return JSON.stringify(cleaned);
|
|
661
559
|
}
|
|
662
560
|
return null;
|
|
663
561
|
}
|
|
@@ -666,80 +564,122 @@ ${toolsList}
|
|
|
666
564
|
return null;
|
|
667
565
|
}
|
|
668
566
|
}
|
|
567
|
+
// Private fields
|
|
568
|
+
_settingsModel;
|
|
569
|
+
_user;
|
|
570
|
+
_toolContexts = new Map();
|
|
571
|
+
_agentManager;
|
|
572
|
+
_currentStreamingMessage = null;
|
|
573
|
+
_nameChanged = new Signal(this);
|
|
574
|
+
_trans;
|
|
575
|
+
}
|
|
576
|
+
var Private;
|
|
577
|
+
(function (Private) {
|
|
578
|
+
function escapeHtml(value) {
|
|
579
|
+
// Prefer the same native escaping approach used in JupyterLab itself
|
|
580
|
+
// (e.g. `@jupyterlab/completer`).
|
|
581
|
+
if (typeof document !== 'undefined') {
|
|
582
|
+
const node = document.createElement('span');
|
|
583
|
+
node.textContent = value;
|
|
584
|
+
return node.innerHTML;
|
|
585
|
+
}
|
|
586
|
+
// Fallback
|
|
587
|
+
return value
|
|
588
|
+
.replace(/&/g, '&')
|
|
589
|
+
.replace(/</g, '<')
|
|
590
|
+
.replace(/>/g, '>')
|
|
591
|
+
.replace(/"/g, '"')
|
|
592
|
+
.replace(/'/g, ''');
|
|
593
|
+
}
|
|
594
|
+
Private.escapeHtml = escapeHtml;
|
|
595
|
+
const STATUS_CONFIG = {
|
|
596
|
+
pending: {
|
|
597
|
+
cssClass: 'jp-ai-tool-pending',
|
|
598
|
+
statusClass: 'jp-ai-tool-status-pending'
|
|
599
|
+
},
|
|
600
|
+
awaiting_approval: {
|
|
601
|
+
cssClass: 'jp-ai-tool-pending',
|
|
602
|
+
statusClass: 'jp-ai-tool-status-approval',
|
|
603
|
+
open: true
|
|
604
|
+
},
|
|
605
|
+
approved: {
|
|
606
|
+
cssClass: 'jp-ai-tool-pending',
|
|
607
|
+
statusClass: 'jp-ai-tool-status-completed'
|
|
608
|
+
},
|
|
609
|
+
rejected: {
|
|
610
|
+
cssClass: 'jp-ai-tool-error',
|
|
611
|
+
statusClass: 'jp-ai-tool-status-error'
|
|
612
|
+
},
|
|
613
|
+
completed: {
|
|
614
|
+
cssClass: 'jp-ai-tool-completed',
|
|
615
|
+
statusClass: 'jp-ai-tool-status-completed'
|
|
616
|
+
},
|
|
617
|
+
error: {
|
|
618
|
+
cssClass: 'jp-ai-tool-error',
|
|
619
|
+
statusClass: 'jp-ai-tool-status-error'
|
|
620
|
+
}
|
|
621
|
+
};
|
|
669
622
|
/**
|
|
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
|
|
623
|
+
* Returns the translated status text for a given tool status.
|
|
674
624
|
*/
|
|
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);
|
|
625
|
+
const getStatusText = (status, trans) => {
|
|
626
|
+
switch (status) {
|
|
627
|
+
case 'pending':
|
|
628
|
+
return trans.__('Running...');
|
|
629
|
+
case 'awaiting_approval':
|
|
630
|
+
return trans.__('Awaiting Approval');
|
|
631
|
+
case 'approved':
|
|
632
|
+
return trans.__('Approved - Executing...');
|
|
633
|
+
case 'rejected':
|
|
634
|
+
return trans.__('Rejected');
|
|
635
|
+
case 'completed':
|
|
636
|
+
return trans.__('Completed');
|
|
637
|
+
case 'error':
|
|
638
|
+
return trans.__('Error');
|
|
695
639
|
}
|
|
696
|
-
}
|
|
640
|
+
};
|
|
697
641
|
/**
|
|
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
|
|
642
|
+
* Builds HTML for a tool call display.
|
|
702
643
|
*/
|
|
703
|
-
|
|
704
|
-
const
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
644
|
+
function buildToolCallHtml(options) {
|
|
645
|
+
const { toolName, input, status, output, approvalId, trans } = options;
|
|
646
|
+
const config = STATUS_CONFIG[status];
|
|
647
|
+
const statusText = getStatusText(status, trans);
|
|
648
|
+
const escapedToolName = escapeHtml(toolName);
|
|
649
|
+
const escapedInput = escapeHtml(input);
|
|
650
|
+
const openAttr = config.open ? ' open' : '';
|
|
651
|
+
let bodyContent = `
|
|
652
|
+
<div class="jp-ai-tool-section">
|
|
653
|
+
<div class="jp-ai-tool-label">${trans.__('Input')}</div>
|
|
654
|
+
<pre class="jp-ai-tool-code"><code>${escapedInput}</code></pre>
|
|
655
|
+
</div>`;
|
|
656
|
+
// Add approval buttons if awaiting approval
|
|
657
|
+
if (status === 'awaiting_approval' && approvalId) {
|
|
658
|
+
bodyContent += `
|
|
659
|
+
<div class="jp-ai-tool-approval-buttons jp-ai-approval-id--${approvalId}">
|
|
660
|
+
<button class="jp-ai-approval-btn jp-ai-approval-approve">${trans.__('Approve')}</button>
|
|
661
|
+
<button class="jp-ai-approval-btn jp-ai-approval-reject">${trans.__('Reject')}</button>
|
|
662
|
+
</div>`;
|
|
663
|
+
}
|
|
664
|
+
// Add output/result section if provided
|
|
665
|
+
if (output !== undefined) {
|
|
666
|
+
const escapedOutput = escapeHtml(output);
|
|
667
|
+
const label = status === 'error' ? trans.__('Error') : trans.__('Result');
|
|
668
|
+
bodyContent += `
|
|
669
|
+
<div class="jp-ai-tool-section">
|
|
670
|
+
<div class="jp-ai-tool-label">${label}</div>
|
|
671
|
+
<pre class="jp-ai-tool-code"><code>${escapedOutput}</code></pre>
|
|
672
|
+
</div>`;
|
|
673
|
+
}
|
|
674
|
+
return `<details class="jp-ai-tool-call ${config.cssClass}"${openAttr}>
|
|
722
675
|
<summary class="jp-ai-tool-header">
|
|
723
676
|
<div class="jp-ai-tool-icon">⚡</div>
|
|
724
|
-
<div class="jp-ai-tool-title">${
|
|
725
|
-
<div class="jp-ai-tool-status ${
|
|
677
|
+
<div class="jp-ai-tool-title">${escapedToolName}</div>
|
|
678
|
+
<div class="jp-ai-tool-status ${config.statusClass}">${statusText}</div>
|
|
726
679
|
</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>
|
|
680
|
+
<div class="jp-ai-tool-body">${bodyContent}
|
|
731
681
|
</div>
|
|
732
|
-
</
|
|
733
|
-
</details>`
|
|
734
|
-
};
|
|
735
|
-
this.messageAdded(updatedMessage);
|
|
736
|
-
}
|
|
682
|
+
</details>`;
|
|
737
683
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
_user;
|
|
741
|
-
_pendingToolCalls = new Map();
|
|
742
|
-
_agentManager;
|
|
743
|
-
_currentStreamingMessage = null;
|
|
744
|
-
_nameChanged = new Signal(this);
|
|
745
|
-
}
|
|
684
|
+
Private.buildToolCallHtml = buildToolCallHtml;
|
|
685
|
+
})(Private || (Private = {}));
|