@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.
Files changed (64) hide show
  1. package/lib/agent.d.ts +33 -115
  2. package/lib/agent.js +192 -106
  3. package/lib/chat-model-handler.d.ts +9 -11
  4. package/lib/chat-model-handler.js +9 -4
  5. package/lib/chat-model.d.ts +84 -13
  6. package/lib/chat-model.js +214 -136
  7. package/lib/completion/completion-provider.d.ts +2 -3
  8. package/lib/components/completion-status.d.ts +2 -2
  9. package/lib/components/index.d.ts +1 -1
  10. package/lib/components/index.js +1 -1
  11. package/lib/components/model-select.d.ts +3 -3
  12. package/lib/components/save-button.d.ts +31 -0
  13. package/lib/components/save-button.js +41 -0
  14. package/lib/components/tool-select.d.ts +3 -4
  15. package/lib/components/{token-usage-display.d.ts → usage-display.d.ts} +13 -14
  16. package/lib/components/usage-display.js +109 -0
  17. package/lib/diff-manager.d.ts +2 -3
  18. package/lib/index.d.ts +2 -4
  19. package/lib/index.js +186 -28
  20. package/lib/models/settings-model.d.ts +11 -53
  21. package/lib/models/settings-model.js +38 -22
  22. package/lib/providers/built-in-providers.js +22 -36
  23. package/lib/providers/generated-context-windows.d.ts +8 -0
  24. package/lib/providers/generated-context-windows.js +96 -0
  25. package/lib/providers/model-info.d.ts +3 -0
  26. package/lib/providers/model-info.js +58 -0
  27. package/lib/tokens.d.ts +361 -36
  28. package/lib/tokens.js +18 -13
  29. package/lib/tools/commands.d.ts +2 -3
  30. package/lib/widgets/ai-settings.d.ts +3 -5
  31. package/lib/widgets/ai-settings.js +12 -0
  32. package/lib/widgets/main-area-chat.d.ts +2 -3
  33. package/lib/widgets/main-area-chat.js +12 -12
  34. package/lib/widgets/provider-config-dialog.d.ts +1 -2
  35. package/lib/widgets/provider-config-dialog.js +34 -34
  36. package/package.json +17 -10
  37. package/schema/settings-model.json +18 -1
  38. package/src/agent.ts +275 -248
  39. package/src/chat-model-handler.ts +25 -21
  40. package/src/chat-model.ts +307 -196
  41. package/src/completion/completion-provider.ts +7 -4
  42. package/src/components/completion-status.tsx +3 -3
  43. package/src/components/index.ts +1 -1
  44. package/src/components/model-select.tsx +4 -3
  45. package/src/components/save-button.tsx +84 -0
  46. package/src/components/tool-select.tsx +10 -4
  47. package/src/components/usage-display.tsx +208 -0
  48. package/src/diff-manager.ts +4 -4
  49. package/src/index.ts +250 -58
  50. package/src/models/settings-model.ts +46 -88
  51. package/src/providers/built-in-providers.ts +22 -36
  52. package/src/providers/generated-context-windows.ts +102 -0
  53. package/src/providers/model-info.ts +88 -0
  54. package/src/tokens.ts +438 -58
  55. package/src/tools/commands.ts +2 -3
  56. package/src/widgets/ai-settings.tsx +69 -15
  57. package/src/widgets/main-area-chat.ts +18 -15
  58. package/src/widgets/provider-config-dialog.tsx +96 -61
  59. package/style/base.css +17 -195
  60. package/lib/approval-buttons.d.ts +0 -49
  61. package/lib/approval-buttons.js +0 -79
  62. package/lib/components/token-usage-display.js +0 -72
  63. package/src/approval-buttons.ts +0 -115
  64. 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 { TranslationBundle } from '@jupyterlab/translation';
26
+ import { Contents } from '@jupyterlab/services';
27
27
 
28
28
  import { UUID } from '@lumino/coreutils';
29
29
 
30
- import { ISignal, Signal } from '@lumino/signaling';
30
+ import { Debouncer } from '@lumino/polling';
31
31
 
32
- import { AgentManager, IAgentEvent } from './agent';
32
+ import { ISignal, Signal } from '@lumino/signaling';
33
33
 
34
34
  import { AI_AVATAR } from './icons';
35
35
 
36
- import { AISettingsModel } from './models/settings-model';
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._trans = options.trans;
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
- this.setReady();
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<AgentManager, ITokenUsage> {
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(): 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(_sender: AgentManager, event: IAgentEvent): void {
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(event: IAgentEvent<'message_start'>): void {
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(event: IAgentEvent<'message_chunk'>): void {
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(event: IAgentEvent<'message_complete'>): void {
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: Private.buildToolCallHtml({
487
- toolName: context.toolName,
488
- input: context.input,
489
- status: context.status,
490
- summary: context.summary,
491
- trans: this._trans
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: bundle,
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
- body: Private.buildToolCallHtml({
629
- toolName: context.toolName,
630
- input: context.input,
631
- status: context.status,
632
- summary: context.summary,
633
- output,
634
- approvalId: context.approvalId,
635
- trans: this._trans
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: AISettingsModel;
1144
+ private _settingsModel: IAISettingsModel;
920
1145
  private _user: IUser;
921
1146
  private _toolContexts: Map<string, IToolExecutionContext> = new Map();
922
- private _agentManager: AgentManager;
1147
+ private _agentManager: IAgentManager;
923
1148
  private _currentStreamingMessage: IMessage | null = null;
924
1149
  private _nameChanged = new Signal<AIChatModel, string>(this);
925
- private _trans: TranslationBundle;
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, '&amp;')
1048
- .replace(/</g, '&lt;')
1049
- .replace(/>/g, '&gt;')
1050
- .replace(/"/g, '&quot;')
1051
- .replace(/'/g, '&#39;');
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: AISettingsModel;
1280
+ settingsModel: IAISettingsModel;
1205
1281
  /**
1206
1282
  * Optional agent manager for handling AI agent lifecycle
1207
1283
  */
1208
- agentManager: 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 application language translation bundle.
1294
+ * The contents manager.
1295
+ */
1296
+ contentsManager?: Contents.IManager;
1297
+ /**
1298
+ * Whether to restore or not the message (default to true)
1219
1299
  */
1220
- trans: TranslationBundle;
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: 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
  }