@jupyterlite/ai 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/agent.d.ts +33 -115
- package/lib/agent.js +192 -106
- package/lib/chat-model-handler.d.ts +9 -11
- package/lib/chat-model-handler.js +9 -4
- package/lib/chat-model.d.ts +84 -13
- package/lib/chat-model.js +214 -136
- package/lib/completion/completion-provider.d.ts +2 -3
- package/lib/components/completion-status.d.ts +2 -2
- package/lib/components/index.d.ts +1 -1
- package/lib/components/index.js +1 -1
- package/lib/components/model-select.d.ts +3 -3
- package/lib/components/save-button.d.ts +31 -0
- package/lib/components/save-button.js +41 -0
- package/lib/components/tool-select.d.ts +3 -4
- package/lib/components/{token-usage-display.d.ts → usage-display.d.ts} +13 -14
- package/lib/components/usage-display.js +109 -0
- package/lib/diff-manager.d.ts +2 -3
- package/lib/index.d.ts +2 -4
- package/lib/index.js +186 -28
- package/lib/models/settings-model.d.ts +11 -53
- package/lib/models/settings-model.js +38 -22
- package/lib/providers/built-in-providers.js +22 -36
- package/lib/providers/generated-context-windows.d.ts +8 -0
- package/lib/providers/generated-context-windows.js +96 -0
- package/lib/providers/model-info.d.ts +3 -0
- package/lib/providers/model-info.js +58 -0
- package/lib/tokens.d.ts +361 -36
- package/lib/tokens.js +18 -13
- package/lib/tools/commands.d.ts +2 -3
- package/lib/widgets/ai-settings.d.ts +3 -5
- package/lib/widgets/ai-settings.js +12 -0
- package/lib/widgets/main-area-chat.d.ts +2 -3
- package/lib/widgets/main-area-chat.js +12 -12
- package/lib/widgets/provider-config-dialog.d.ts +1 -2
- package/lib/widgets/provider-config-dialog.js +34 -34
- package/package.json +17 -10
- package/schema/settings-model.json +18 -1
- package/src/agent.ts +275 -248
- package/src/chat-model-handler.ts +25 -21
- package/src/chat-model.ts +307 -196
- package/src/completion/completion-provider.ts +7 -4
- package/src/components/completion-status.tsx +3 -3
- package/src/components/index.ts +1 -1
- package/src/components/model-select.tsx +4 -3
- package/src/components/save-button.tsx +84 -0
- package/src/components/tool-select.tsx +10 -4
- package/src/components/usage-display.tsx +208 -0
- package/src/diff-manager.ts +4 -4
- package/src/index.ts +250 -58
- package/src/models/settings-model.ts +46 -88
- package/src/providers/built-in-providers.ts +22 -36
- package/src/providers/generated-context-windows.ts +102 -0
- package/src/providers/model-info.ts +88 -0
- package/src/tokens.ts +438 -58
- package/src/tools/commands.ts +2 -3
- package/src/widgets/ai-settings.tsx +69 -15
- package/src/widgets/main-area-chat.ts +18 -15
- package/src/widgets/provider-config-dialog.tsx +96 -61
- package/style/base.css +17 -195
- package/lib/approval-buttons.d.ts +0 -49
- package/lib/approval-buttons.js +0 -79
- package/lib/components/token-usage-display.js +0 -72
- package/src/approval-buttons.ts +0 -115
- package/src/components/token-usage-display.tsx +0 -138
package/src/chat-model.ts
CHANGED
|
@@ -23,19 +23,17 @@ import { INotebookModel, Notebook } from '@jupyterlab/notebook';
|
|
|
23
23
|
|
|
24
24
|
import { IRenderMime } from '@jupyterlab/rendermime';
|
|
25
25
|
|
|
26
|
-
import {
|
|
26
|
+
import { Contents } from '@jupyterlab/services';
|
|
27
27
|
|
|
28
28
|
import { UUID } from '@lumino/coreutils';
|
|
29
29
|
|
|
30
|
-
import {
|
|
30
|
+
import { Debouncer } from '@lumino/polling';
|
|
31
31
|
|
|
32
|
-
import {
|
|
32
|
+
import { ISignal, Signal } from '@lumino/signaling';
|
|
33
33
|
|
|
34
34
|
import { AI_AVATAR } from './icons';
|
|
35
35
|
|
|
36
|
-
import {
|
|
37
|
-
|
|
38
|
-
import { ITokenUsage } from './tokens';
|
|
36
|
+
import type { IAgentManager, IAISettingsModel, ITokenUsage } from './tokens';
|
|
39
37
|
|
|
40
38
|
/**
|
|
41
39
|
* Tool call status types.
|
|
@@ -107,14 +105,15 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
107
105
|
this._settingsModel = options.settingsModel;
|
|
108
106
|
this._user = options.user;
|
|
109
107
|
this._agentManager = options.agentManager;
|
|
110
|
-
this.
|
|
108
|
+
this._contentsManager = options.contentsManager;
|
|
111
109
|
|
|
112
110
|
// Listen for agent events
|
|
113
111
|
this._agentManager.agentEvent.connect(this._onAgentEvent, this);
|
|
114
112
|
|
|
115
113
|
// Listen for settings changes to update chat behavior
|
|
116
114
|
this._settingsModel.stateChanged.connect(this._onSettingsChanged, this);
|
|
117
|
-
|
|
115
|
+
|
|
116
|
+
this._autosaveDebouncer = new Debouncer(this.save, 3000);
|
|
118
117
|
}
|
|
119
118
|
|
|
120
119
|
/**
|
|
@@ -126,6 +125,50 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
126
125
|
set name(value: string) {
|
|
127
126
|
super.name = value;
|
|
128
127
|
this._nameChanged.emit(value);
|
|
128
|
+
if (!this.messages.length) {
|
|
129
|
+
const directory = this._settingsModel.config.chatBackupDirectory;
|
|
130
|
+
const filepath = PathExt.join(directory, `${this.name}.chat`);
|
|
131
|
+
this.restore(filepath, true);
|
|
132
|
+
}
|
|
133
|
+
this.setReady();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Whether to save the chat automatically.
|
|
138
|
+
*/
|
|
139
|
+
get autosave(): boolean {
|
|
140
|
+
return this._autosave;
|
|
141
|
+
}
|
|
142
|
+
set autosave(value: boolean) {
|
|
143
|
+
this._autosave = value;
|
|
144
|
+
this._autosaveChanged.emit(value);
|
|
145
|
+
if (value) {
|
|
146
|
+
this.messagesUpdated.connect(
|
|
147
|
+
this._autosaveDebouncer.invoke,
|
|
148
|
+
this._autosaveDebouncer
|
|
149
|
+
);
|
|
150
|
+
this.messageChanged.connect(
|
|
151
|
+
this._autosaveDebouncer.invoke,
|
|
152
|
+
this._autosaveDebouncer
|
|
153
|
+
);
|
|
154
|
+
this._autosaveDebouncer.invoke();
|
|
155
|
+
} else {
|
|
156
|
+
this.messagesUpdated.disconnect(
|
|
157
|
+
this._autosaveDebouncer.invoke,
|
|
158
|
+
this._autosaveDebouncer
|
|
159
|
+
);
|
|
160
|
+
this.messageChanged.disconnect(
|
|
161
|
+
this._autosaveDebouncer.invoke,
|
|
162
|
+
this._autosaveDebouncer
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* A signal emitting when the autosave flag changed.
|
|
169
|
+
*/
|
|
170
|
+
get autosaveChanged(): ISignal<AIChatModel, boolean> {
|
|
171
|
+
return this._autosaveChanged;
|
|
129
172
|
}
|
|
130
173
|
|
|
131
174
|
/**
|
|
@@ -145,17 +188,35 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
145
188
|
/**
|
|
146
189
|
* A signal emitting when the token usage changed.
|
|
147
190
|
*/
|
|
148
|
-
get tokenUsageChanged(): ISignal<
|
|
191
|
+
get tokenUsageChanged(): ISignal<IAgentManager, ITokenUsage> {
|
|
149
192
|
return this._agentManager.tokenUsageChanged;
|
|
150
193
|
}
|
|
151
194
|
|
|
152
195
|
/**
|
|
153
196
|
* Get the agent manager associated to the model.
|
|
154
197
|
*/
|
|
155
|
-
get agentManager():
|
|
198
|
+
get agentManager(): IAgentManager {
|
|
156
199
|
return this._agentManager;
|
|
157
200
|
}
|
|
158
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Whether save/restore is available.
|
|
204
|
+
*/
|
|
205
|
+
get saveAvailable(): boolean {
|
|
206
|
+
return !!this._contentsManager;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Dispose of the model.
|
|
211
|
+
*/
|
|
212
|
+
dispose(): void {
|
|
213
|
+
this.messagesUpdated.disconnect(
|
|
214
|
+
this._autosaveDebouncer.invoke,
|
|
215
|
+
this._autosaveDebouncer
|
|
216
|
+
);
|
|
217
|
+
super.dispose();
|
|
218
|
+
}
|
|
219
|
+
|
|
159
220
|
/**
|
|
160
221
|
* Creates a chat context for the current conversation.
|
|
161
222
|
*/
|
|
@@ -273,6 +334,150 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
273
334
|
}
|
|
274
335
|
}
|
|
275
336
|
|
|
337
|
+
/**
|
|
338
|
+
* Save the chat as json file.
|
|
339
|
+
*/
|
|
340
|
+
save = async (): Promise<void> => {
|
|
341
|
+
if (!this._contentsManager) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const directory = this._settingsModel.config.chatBackupDirectory;
|
|
345
|
+
const filepath = PathExt.join(directory, `${this.name}.chat`);
|
|
346
|
+
const content = JSON.stringify(this._serializeModel());
|
|
347
|
+
await this._contentsManager
|
|
348
|
+
.get(filepath, { content: false })
|
|
349
|
+
.catch(async () => {
|
|
350
|
+
await this._contentsManager
|
|
351
|
+
?.get(directory, { content: false })
|
|
352
|
+
.catch(async () => {
|
|
353
|
+
const dir = await this._contentsManager!.newUntitled({
|
|
354
|
+
type: 'directory'
|
|
355
|
+
});
|
|
356
|
+
await this._contentsManager!.rename(dir.path, directory);
|
|
357
|
+
});
|
|
358
|
+
const file = await this._contentsManager!.newUntitled({ ext: '.chat' });
|
|
359
|
+
await this._contentsManager?.rename(file.path, filepath);
|
|
360
|
+
});
|
|
361
|
+
await this._contentsManager.save(filepath, {
|
|
362
|
+
content,
|
|
363
|
+
type: 'file',
|
|
364
|
+
format: 'text'
|
|
365
|
+
});
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Restore the chat from a json file.
|
|
370
|
+
*
|
|
371
|
+
* @param silent - Whether a log should be displayed in the console if the
|
|
372
|
+
* restoration is not possible.
|
|
373
|
+
*/
|
|
374
|
+
restore = async (filepath: string, silent = false): Promise<boolean> => {
|
|
375
|
+
if (!this._contentsManager) {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
const contentModel = await this._contentsManager
|
|
379
|
+
.get(filepath, { content: true, type: 'file', format: 'text' })
|
|
380
|
+
.catch(() => {
|
|
381
|
+
if (!silent) {
|
|
382
|
+
console.log(`There is no backup for chat '${this.name}'`);
|
|
383
|
+
}
|
|
384
|
+
return;
|
|
385
|
+
});
|
|
386
|
+
if (!contentModel) {
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
let content: AIChatModel.ExportedChat;
|
|
390
|
+
try {
|
|
391
|
+
content = JSON.parse(contentModel.content);
|
|
392
|
+
} catch (e) {
|
|
393
|
+
throw `Error when parsing the chat ${filepath}\n${e}`;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (content.metadata?.provider) {
|
|
397
|
+
if (this._settingsModel.getProvider(content.metadata.provider)) {
|
|
398
|
+
this._agentManager.activeProvider = content.metadata.provider;
|
|
399
|
+
} else if (!silent) {
|
|
400
|
+
console.log(
|
|
401
|
+
`Provider '${content.metadata.provider}' doesn't exist, it can't be restored.`
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
} else if (!silent) {
|
|
405
|
+
console.log(`Provider not providing when restoring ${filepath}.`);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const messages: IMessageContent[] = content.messages.map(message => {
|
|
409
|
+
let attachments: IAttachment[] = [];
|
|
410
|
+
if (content.attachments && message.attachments) {
|
|
411
|
+
attachments =
|
|
412
|
+
message.attachments.map(index => content.attachments![index]) ?? [];
|
|
413
|
+
}
|
|
414
|
+
return {
|
|
415
|
+
...message,
|
|
416
|
+
sender: content.users[message.sender] ?? { username: 'unknown' },
|
|
417
|
+
mentions: message.mentions?.map(mention => content.users[mention]),
|
|
418
|
+
attachments
|
|
419
|
+
};
|
|
420
|
+
});
|
|
421
|
+
this.clearMessages();
|
|
422
|
+
this.messagesInserted(0, messages);
|
|
423
|
+
this._agentManager.setHistory(messages);
|
|
424
|
+
this.autosave = content.metadata?.autosave ?? false;
|
|
425
|
+
return true;
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Serialize the model for backup
|
|
430
|
+
*/
|
|
431
|
+
private _serializeModel(): AIChatModel.ExportedChat {
|
|
432
|
+
const provider = this._agentManager.activeProvider;
|
|
433
|
+
const messages: IMessageContent<string, string>[] = [];
|
|
434
|
+
const users: { [id: string]: IUser } = {};
|
|
435
|
+
const attachmentMap = new Map<string, number>(); // JSON → index
|
|
436
|
+
const attachmentsList: IAttachment[] = []; // Actual attachments
|
|
437
|
+
|
|
438
|
+
this.messages.forEach(message => {
|
|
439
|
+
let attachmentIndexes: string[] = [];
|
|
440
|
+
if (message.attachments) {
|
|
441
|
+
attachmentIndexes = message.attachments.map(attachment => {
|
|
442
|
+
const attachmentJson = JSON.stringify(attachment);
|
|
443
|
+
let index: number;
|
|
444
|
+
if (attachmentMap.has(attachmentJson)) {
|
|
445
|
+
index = attachmentMap.get(attachmentJson)!;
|
|
446
|
+
} else {
|
|
447
|
+
index = attachmentsList.length;
|
|
448
|
+
attachmentMap.set(attachmentJson, index);
|
|
449
|
+
attachmentsList.push(attachment);
|
|
450
|
+
}
|
|
451
|
+
return index.toString();
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
messages.push({
|
|
456
|
+
...message.content,
|
|
457
|
+
sender: message.sender.username,
|
|
458
|
+
mentions: message.mentions?.map(user => user.username),
|
|
459
|
+
attachments: attachmentIndexes
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
if (!users[message.sender.username]) {
|
|
463
|
+
users[message.sender.username] = message.sender;
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
const attachments = Object.fromEntries(
|
|
468
|
+
attachmentsList.map((item, index) => [index, item])
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
return {
|
|
472
|
+
messages,
|
|
473
|
+
users,
|
|
474
|
+
attachments,
|
|
475
|
+
metadata: {
|
|
476
|
+
provider,
|
|
477
|
+
autosave: this.autosave
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
}
|
|
276
481
|
/**
|
|
277
482
|
* Gets the AI user information for system messages.
|
|
278
483
|
*/
|
|
@@ -299,7 +504,10 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
299
504
|
* Handles events emitted by the agent manager.
|
|
300
505
|
* @param event The event data containing type and payload
|
|
301
506
|
*/
|
|
302
|
-
private _onAgentEvent(
|
|
507
|
+
private _onAgentEvent(
|
|
508
|
+
_sender: IAgentManager,
|
|
509
|
+
event: IAgentManager.IAgentEvent
|
|
510
|
+
): void {
|
|
303
511
|
switch (event.type) {
|
|
304
512
|
case 'message_start':
|
|
305
513
|
this._handleMessageStart(event);
|
|
@@ -332,7 +540,9 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
332
540
|
* Handles the start of a new message from the AI agent.
|
|
333
541
|
* @param event Event containing the message start data
|
|
334
542
|
*/
|
|
335
|
-
private _handleMessageStart(
|
|
543
|
+
private _handleMessageStart(
|
|
544
|
+
event: IAgentManager.IAgentEvent<'message_start'>
|
|
545
|
+
): void {
|
|
336
546
|
const aiMessage: IMessageContent = {
|
|
337
547
|
body: '',
|
|
338
548
|
sender: this._getAIUser(),
|
|
@@ -350,7 +560,9 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
350
560
|
* Handles streaming message chunks from the AI agent.
|
|
351
561
|
* @param event Event containing the message chunk data
|
|
352
562
|
*/
|
|
353
|
-
private _handleMessageChunk(
|
|
563
|
+
private _handleMessageChunk(
|
|
564
|
+
event: IAgentManager.IAgentEvent<'message_chunk'>
|
|
565
|
+
): void {
|
|
354
566
|
if (
|
|
355
567
|
this._currentStreamingMessage &&
|
|
356
568
|
this._currentStreamingMessage.id === event.data.messageId
|
|
@@ -363,7 +575,9 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
363
575
|
* Handles the completion of a message from the AI agent.
|
|
364
576
|
* @param event Event containing the message completion data
|
|
365
577
|
*/
|
|
366
|
-
private _handleMessageComplete(
|
|
578
|
+
private _handleMessageComplete(
|
|
579
|
+
event: IAgentManager.IAgentEvent<'message_complete'>
|
|
580
|
+
): void {
|
|
367
581
|
if (
|
|
368
582
|
this._currentStreamingMessage &&
|
|
369
583
|
this._currentStreamingMessage.id === event.data.messageId
|
|
@@ -458,7 +672,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
458
672
|
* @param event Event containing the tool call start data
|
|
459
673
|
*/
|
|
460
674
|
private _handleToolCallStartEvent(
|
|
461
|
-
event: IAgentEvent<'tool_call_start'>
|
|
675
|
+
event: IAgentManager.IAgentEvent<'tool_call_start'>
|
|
462
676
|
): void {
|
|
463
677
|
const messageId = UUID.uuid4();
|
|
464
678
|
const summary = this._extractToolSummary(
|
|
@@ -483,13 +697,18 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
483
697
|
this._toolContexts.set(event.data.callId, context);
|
|
484
698
|
|
|
485
699
|
const toolCallMessage: IMessageContent = {
|
|
486
|
-
body:
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
700
|
+
body: '',
|
|
701
|
+
mime_model: {
|
|
702
|
+
data: {
|
|
703
|
+
'application/vnd.jupyter.chat.components': 'tool-call'
|
|
704
|
+
},
|
|
705
|
+
metadata: {
|
|
706
|
+
toolName: context.toolName,
|
|
707
|
+
input: context.input,
|
|
708
|
+
status: context.status,
|
|
709
|
+
summary: context.summary
|
|
710
|
+
}
|
|
711
|
+
},
|
|
493
712
|
sender: this._getAIUser(),
|
|
494
713
|
id: messageId,
|
|
495
714
|
time: Date.now() / 1000,
|
|
@@ -504,7 +723,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
504
723
|
* Handles the completion of a tool call execution.
|
|
505
724
|
*/
|
|
506
725
|
private _handleToolCallCompleteEvent(
|
|
507
|
-
event: IAgentEvent<'tool_call_complete'>
|
|
726
|
+
event: IAgentManager.IAgentEvent<'tool_call_complete'>
|
|
508
727
|
): void {
|
|
509
728
|
const context = this._toolContexts.get(event.data.callId);
|
|
510
729
|
const status = event.data.isError ? 'error' : 'completed';
|
|
@@ -527,7 +746,8 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
527
746
|
);
|
|
528
747
|
for (const bundle of mimeBundles) {
|
|
529
748
|
this.messageAdded({
|
|
530
|
-
body:
|
|
749
|
+
body: '',
|
|
750
|
+
mime_model: bundle,
|
|
531
751
|
sender: this._getAIUser(),
|
|
532
752
|
id: UUID.uuid4(),
|
|
533
753
|
time: Date.now() / 1000,
|
|
@@ -556,7 +776,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
556
776
|
/**
|
|
557
777
|
* Handles error events from the AI agent.
|
|
558
778
|
*/
|
|
559
|
-
private _handleErrorEvent(event: IAgentEvent<'error'>): void {
|
|
779
|
+
private _handleErrorEvent(event: IAgentManager.IAgentEvent<'error'>): void {
|
|
560
780
|
this.messageAdded({
|
|
561
781
|
body: `Error generating response: ${event.data.error.message}`,
|
|
562
782
|
sender: this._getAIUser(),
|
|
@@ -571,7 +791,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
571
791
|
* Handles tool approval request events from the AI agent.
|
|
572
792
|
*/
|
|
573
793
|
private _handleToolApprovalRequest(
|
|
574
|
-
event: IAgentEvent<'tool_approval_request'>
|
|
794
|
+
event: IAgentManager.IAgentEvent<'tool_approval_request'>
|
|
575
795
|
): void {
|
|
576
796
|
const context = this._toolContexts.get(event.data.toolCallId);
|
|
577
797
|
if (!context) {
|
|
@@ -586,7 +806,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
586
806
|
* Handles tool approval resolved events from the AI agent.
|
|
587
807
|
*/
|
|
588
808
|
private _handleToolApprovalResolved(
|
|
589
|
-
event: IAgentEvent<'tool_approval_resolved'>
|
|
809
|
+
event: IAgentManager.IAgentEvent<'tool_approval_resolved'>
|
|
590
810
|
): void {
|
|
591
811
|
const context = Array.from(this._toolContexts.values()).find(
|
|
592
812
|
ctx => ctx.approvalId === event.data.approvalId
|
|
@@ -625,15 +845,20 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
625
845
|
|
|
626
846
|
context.status = status;
|
|
627
847
|
existingMessage.update({
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
848
|
+
mime_model: {
|
|
849
|
+
data: {
|
|
850
|
+
'application/vnd.jupyter.chat.components': 'tool-call'
|
|
851
|
+
},
|
|
852
|
+
metadata: {
|
|
853
|
+
toolName: context.toolName,
|
|
854
|
+
input: context.input,
|
|
855
|
+
status: context.status,
|
|
856
|
+
summary: context.summary,
|
|
857
|
+
output,
|
|
858
|
+
targetId: this.name,
|
|
859
|
+
approvalId: context.approvalId
|
|
860
|
+
}
|
|
861
|
+
}
|
|
637
862
|
});
|
|
638
863
|
}
|
|
639
864
|
|
|
@@ -916,13 +1141,16 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
916
1141
|
}
|
|
917
1142
|
|
|
918
1143
|
// Private fields
|
|
919
|
-
private _settingsModel:
|
|
1144
|
+
private _settingsModel: IAISettingsModel;
|
|
920
1145
|
private _user: IUser;
|
|
921
1146
|
private _toolContexts: Map<string, IToolExecutionContext> = new Map();
|
|
922
|
-
private _agentManager:
|
|
1147
|
+
private _agentManager: IAgentManager;
|
|
923
1148
|
private _currentStreamingMessage: IMessage | null = null;
|
|
924
1149
|
private _nameChanged = new Signal<AIChatModel, string>(this);
|
|
925
|
-
private
|
|
1150
|
+
private _contentsManager?: Contents.IManager;
|
|
1151
|
+
private _autosave: boolean = false;
|
|
1152
|
+
private _autosaveChanged = new Signal<AIChatModel, boolean>(this);
|
|
1153
|
+
private _autosaveDebouncer: Debouncer;
|
|
926
1154
|
}
|
|
927
1155
|
|
|
928
1156
|
namespace Private {
|
|
@@ -1032,158 +1260,6 @@ namespace Private {
|
|
|
1032
1260
|
return '[Complex object - cannot serialize]';
|
|
1033
1261
|
}
|
|
1034
1262
|
}
|
|
1035
|
-
|
|
1036
|
-
export function escapeHtml(value: string): string {
|
|
1037
|
-
// Prefer the same native escaping approach used in JupyterLab itself
|
|
1038
|
-
// (e.g. `@jupyterlab/completer`).
|
|
1039
|
-
if (typeof document !== 'undefined') {
|
|
1040
|
-
const node = document.createElement('span');
|
|
1041
|
-
node.textContent = value;
|
|
1042
|
-
return node.innerHTML;
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
// Fallback
|
|
1046
|
-
return value
|
|
1047
|
-
.replace(/&/g, '&')
|
|
1048
|
-
.replace(/</g, '<')
|
|
1049
|
-
.replace(/>/g, '>')
|
|
1050
|
-
.replace(/"/g, '"')
|
|
1051
|
-
.replace(/'/g, ''');
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
/**
|
|
1055
|
-
* Configuration for rendering tool call status.
|
|
1056
|
-
*/
|
|
1057
|
-
interface IStatusConfig {
|
|
1058
|
-
cssClass: string;
|
|
1059
|
-
statusClass: string;
|
|
1060
|
-
open?: boolean;
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
const STATUS_CONFIG: Record<ToolStatus, IStatusConfig> = {
|
|
1064
|
-
pending: {
|
|
1065
|
-
cssClass: 'jp-ai-tool-pending',
|
|
1066
|
-
statusClass: 'jp-ai-tool-status-pending'
|
|
1067
|
-
},
|
|
1068
|
-
awaiting_approval: {
|
|
1069
|
-
cssClass: 'jp-ai-tool-pending',
|
|
1070
|
-
statusClass: 'jp-ai-tool-status-approval',
|
|
1071
|
-
open: true
|
|
1072
|
-
},
|
|
1073
|
-
approved: {
|
|
1074
|
-
cssClass: 'jp-ai-tool-pending',
|
|
1075
|
-
statusClass: 'jp-ai-tool-status-completed'
|
|
1076
|
-
},
|
|
1077
|
-
rejected: {
|
|
1078
|
-
cssClass: 'jp-ai-tool-error',
|
|
1079
|
-
statusClass: 'jp-ai-tool-status-error'
|
|
1080
|
-
},
|
|
1081
|
-
completed: {
|
|
1082
|
-
cssClass: 'jp-ai-tool-completed',
|
|
1083
|
-
statusClass: 'jp-ai-tool-status-completed'
|
|
1084
|
-
},
|
|
1085
|
-
error: {
|
|
1086
|
-
cssClass: 'jp-ai-tool-error',
|
|
1087
|
-
statusClass: 'jp-ai-tool-status-error'
|
|
1088
|
-
}
|
|
1089
|
-
};
|
|
1090
|
-
|
|
1091
|
-
/**
|
|
1092
|
-
* Returns the translated status text for a given tool status.
|
|
1093
|
-
*/
|
|
1094
|
-
const getStatusText = (
|
|
1095
|
-
status: ToolStatus,
|
|
1096
|
-
trans: TranslationBundle
|
|
1097
|
-
): string => {
|
|
1098
|
-
switch (status) {
|
|
1099
|
-
case 'pending':
|
|
1100
|
-
return trans.__('Running...');
|
|
1101
|
-
case 'awaiting_approval':
|
|
1102
|
-
return trans.__('Awaiting Approval');
|
|
1103
|
-
case 'approved':
|
|
1104
|
-
return trans.__('Approved - Executing...');
|
|
1105
|
-
case 'rejected':
|
|
1106
|
-
return trans.__('Rejected');
|
|
1107
|
-
case 'completed':
|
|
1108
|
-
return trans.__('Completed');
|
|
1109
|
-
case 'error':
|
|
1110
|
-
return trans.__('Error');
|
|
1111
|
-
}
|
|
1112
|
-
};
|
|
1113
|
-
|
|
1114
|
-
/**
|
|
1115
|
-
* Options for building tool call HTML.
|
|
1116
|
-
*/
|
|
1117
|
-
interface IToolCallHtmlOptions {
|
|
1118
|
-
toolName: string;
|
|
1119
|
-
input: string;
|
|
1120
|
-
status: ToolStatus;
|
|
1121
|
-
summary?: string;
|
|
1122
|
-
output?: string;
|
|
1123
|
-
approvalId?: string;
|
|
1124
|
-
trans: TranslationBundle;
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
/**
|
|
1128
|
-
* Builds HTML for a tool call display.
|
|
1129
|
-
*/
|
|
1130
|
-
export function buildToolCallHtml(
|
|
1131
|
-
options: IToolCallHtmlOptions
|
|
1132
|
-
): Partial<IRenderMime.IMimeModel> & Pick<IRenderMime.IMimeModel, 'data'> {
|
|
1133
|
-
const { toolName, input, status, summary, output, approvalId, trans } =
|
|
1134
|
-
options;
|
|
1135
|
-
const config = STATUS_CONFIG[status];
|
|
1136
|
-
const statusText = getStatusText(status, trans);
|
|
1137
|
-
const escapedToolName = escapeHtml(toolName);
|
|
1138
|
-
const escapedInput = escapeHtml(input);
|
|
1139
|
-
const openAttr = config.open ? ' open' : '';
|
|
1140
|
-
const summaryHtml = summary
|
|
1141
|
-
? `<span class="jp-ai-tool-summary">${escapeHtml(summary)}</span>`
|
|
1142
|
-
: '';
|
|
1143
|
-
|
|
1144
|
-
let bodyContent = `
|
|
1145
|
-
<div class="jp-ai-tool-section">
|
|
1146
|
-
<div class="jp-ai-tool-label">${trans.__('Input')}</div>
|
|
1147
|
-
<pre class="jp-ai-tool-code"><code>${escapedInput}</code></pre>
|
|
1148
|
-
</div>`;
|
|
1149
|
-
|
|
1150
|
-
// Add approval buttons if awaiting approval
|
|
1151
|
-
if (status === 'awaiting_approval' && approvalId) {
|
|
1152
|
-
bodyContent += `
|
|
1153
|
-
<div class="jp-ai-tool-approval-buttons jp-ai-approval-id--${approvalId}">
|
|
1154
|
-
<button class="jp-ai-approval-btn jp-ai-approval-approve">${trans.__('Approve')}</button>
|
|
1155
|
-
<button class="jp-ai-approval-btn jp-ai-approval-reject">${trans.__('Reject')}</button>
|
|
1156
|
-
</div>`;
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
// Add output/result section if provided
|
|
1160
|
-
if (output !== undefined) {
|
|
1161
|
-
const escapedOutput = escapeHtml(output);
|
|
1162
|
-
const label = status === 'error' ? trans.__('Error') : trans.__('Result');
|
|
1163
|
-
bodyContent += `
|
|
1164
|
-
<div class="jp-ai-tool-section">
|
|
1165
|
-
<div class="jp-ai-tool-label">${label}</div>
|
|
1166
|
-
<pre class="jp-ai-tool-code"><code>${escapedOutput}</code></pre>
|
|
1167
|
-
</div>`;
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
const HTMLContent = `<details class="jp-ai-tool-call ${config.cssClass}"${openAttr}>
|
|
1171
|
-
<summary class="jp-ai-tool-header">
|
|
1172
|
-
<div class="jp-ai-tool-icon">⚡</div>
|
|
1173
|
-
<div class="jp-ai-tool-title">${escapedToolName}${summaryHtml}</div>
|
|
1174
|
-
<div class="jp-ai-tool-status ${config.statusClass}">${statusText}</div>
|
|
1175
|
-
</summary>
|
|
1176
|
-
<div class="jp-ai-tool-body">${bodyContent}
|
|
1177
|
-
</div>
|
|
1178
|
-
</details>`;
|
|
1179
|
-
|
|
1180
|
-
return {
|
|
1181
|
-
trusted: true,
|
|
1182
|
-
data: {
|
|
1183
|
-
'text/html': HTMLContent
|
|
1184
|
-
}
|
|
1185
|
-
};
|
|
1186
|
-
}
|
|
1187
1263
|
}
|
|
1188
1264
|
|
|
1189
1265
|
/**
|
|
@@ -1201,11 +1277,11 @@ export namespace AIChatModel {
|
|
|
1201
1277
|
/**
|
|
1202
1278
|
* Settings model for AI configuration
|
|
1203
1279
|
*/
|
|
1204
|
-
settingsModel:
|
|
1280
|
+
settingsModel: IAISettingsModel;
|
|
1205
1281
|
/**
|
|
1206
1282
|
* Optional agent manager for handling AI agent lifecycle
|
|
1207
1283
|
*/
|
|
1208
|
-
agentManager:
|
|
1284
|
+
agentManager: IAgentManager;
|
|
1209
1285
|
/**
|
|
1210
1286
|
* Optional active cell manager for Jupyter integration
|
|
1211
1287
|
*/
|
|
@@ -1215,9 +1291,13 @@ export namespace AIChatModel {
|
|
|
1215
1291
|
*/
|
|
1216
1292
|
documentManager?: IDocumentManager;
|
|
1217
1293
|
/**
|
|
1218
|
-
* The
|
|
1294
|
+
* The contents manager.
|
|
1295
|
+
*/
|
|
1296
|
+
contentsManager?: Contents.IManager;
|
|
1297
|
+
/**
|
|
1298
|
+
* Whether to restore or not the message (default to true)
|
|
1219
1299
|
*/
|
|
1220
|
-
|
|
1300
|
+
restore?: boolean;
|
|
1221
1301
|
}
|
|
1222
1302
|
|
|
1223
1303
|
/**
|
|
@@ -1239,6 +1319,37 @@ export namespace AIChatModel {
|
|
|
1239
1319
|
/**
|
|
1240
1320
|
* The agent manager of the chat.
|
|
1241
1321
|
*/
|
|
1242
|
-
agentManager:
|
|
1322
|
+
agentManager: IAgentManager;
|
|
1243
1323
|
}
|
|
1324
|
+
|
|
1325
|
+
/**
|
|
1326
|
+
* The exported chat format.
|
|
1327
|
+
*/
|
|
1328
|
+
export type ExportedChat = {
|
|
1329
|
+
/**
|
|
1330
|
+
* Message list (user are only string to avoid duplication).
|
|
1331
|
+
*/
|
|
1332
|
+
messages: IMessageContent<string, string>[];
|
|
1333
|
+
/**
|
|
1334
|
+
* The user list.
|
|
1335
|
+
*/
|
|
1336
|
+
users: { [id: string]: IUser };
|
|
1337
|
+
/**
|
|
1338
|
+
* The attachments of the chat.
|
|
1339
|
+
*/
|
|
1340
|
+
attachments?: { [id: string]: IAttachment };
|
|
1341
|
+
/**
|
|
1342
|
+
* The metadata associated to the chat.
|
|
1343
|
+
*/
|
|
1344
|
+
metadata?: {
|
|
1345
|
+
/**
|
|
1346
|
+
* Provider of the chat.
|
|
1347
|
+
*/
|
|
1348
|
+
provider?: string;
|
|
1349
|
+
/**
|
|
1350
|
+
* Whether the chat is automatically saved.
|
|
1351
|
+
*/
|
|
1352
|
+
autosave?: boolean;
|
|
1353
|
+
};
|
|
1354
|
+
};
|
|
1244
1355
|
}
|