@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
@@ -1,17 +1,15 @@
1
1
  import { ActiveCellManager } from '@jupyter/chat';
2
- import { TranslationBundle } from '@jupyterlab/translation';
3
- import { AgentManagerFactory } from './agent';
4
- import { AIChatModel } from './chat-model';
5
- import { AISettingsModel } from './models/settings-model';
6
- import { IChatModelHandler, IProviderRegistry, ITokenUsage, IToolRegistry } from './tokens';
7
2
  import { IDocumentManager } from '@jupyterlab/docmanager';
8
3
  import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
4
+ import { Contents } from '@jupyterlab/services';
5
+ import { AIChatModel } from './chat-model';
6
+ import type { IAgentManagerFactory, IAISettingsModel, IChatModelHandler, ICreateChatOptions, IProviderRegistry, IToolRegistry } from './tokens';
9
7
  /**
10
8
  * The chat model handler.
11
9
  */
12
10
  export declare class ChatModelHandler implements IChatModelHandler {
13
11
  constructor(options: ChatModelHandler.IOptions);
14
- createModel(name: string, activeProvider: string, tokenUsage?: ITokenUsage): AIChatModel;
12
+ createModel(options: ICreateChatOptions): AIChatModel;
15
13
  /**
16
14
  * Getter/setter for the active cell manager.
17
15
  */
@@ -24,7 +22,7 @@ export declare class ChatModelHandler implements IChatModelHandler {
24
22
  private _providerRegistry?;
25
23
  private _rmRegistry;
26
24
  private _activeCellManager?;
27
- private _trans;
25
+ private _contentsManager?;
28
26
  }
29
27
  export declare namespace ChatModelHandler {
30
28
  interface IOptions {
@@ -35,11 +33,11 @@ export declare namespace ChatModelHandler {
35
33
  /**
36
34
  * The agent manager factory.
37
35
  */
38
- agentManagerFactory: AgentManagerFactory;
36
+ agentManagerFactory: IAgentManagerFactory;
39
37
  /**
40
38
  * AI settings model for configuration
41
39
  */
42
- settingsModel: AISettingsModel;
40
+ settingsModel: IAISettingsModel;
43
41
  /**
44
42
  * Optional tool registry for managing available tools
45
43
  */
@@ -57,8 +55,8 @@ export declare namespace ChatModelHandler {
57
55
  */
58
56
  activeCellManager?: ActiveCellManager | undefined;
59
57
  /**
60
- * The application language translation bundle.
58
+ * The contents manager.
61
59
  */
62
- trans: TranslationBundle;
60
+ contentsManager?: Contents.IManager;
63
61
  }
64
62
  }
@@ -11,9 +11,10 @@ export class ChatModelHandler {
11
11
  this._providerRegistry = options.providerRegistry;
12
12
  this._rmRegistry = options.rmRegistry;
13
13
  this._activeCellManager = options.activeCellManager;
14
- this._trans = options.trans;
14
+ this._contentsManager = options.contentsManager;
15
15
  }
16
- createModel(name, activeProvider, tokenUsage) {
16
+ createModel(options) {
17
+ const { name, activeProvider, tokenUsage, messages, autosave } = options;
17
18
  // Create Agent Manager first so it can be shared
18
19
  const agentManager = this._agentManagerFactory.createAgent({
19
20
  settingsModel: this._settingsModel,
@@ -30,8 +31,12 @@ export class ChatModelHandler {
30
31
  agentManager,
31
32
  activeCellManager: this._activeCellManager,
32
33
  documentManager: this._docManager,
33
- trans: this._trans
34
+ contentsManager: this._contentsManager
34
35
  });
36
+ messages?.forEach(message => {
37
+ model.messageAdded({ ...message.content });
38
+ });
39
+ model.autosave = autosave ?? false;
35
40
  model.name = name;
36
41
  return model;
37
42
  }
@@ -51,5 +56,5 @@ export class ChatModelHandler {
51
56
  _providerRegistry;
52
57
  _rmRegistry;
53
58
  _activeCellManager;
54
- _trans;
59
+ _contentsManager;
55
60
  }
@@ -1,10 +1,8 @@
1
- import { AbstractChatModel, IActiveCellManager, IChatContext, INewMessage, IUser } from '@jupyter/chat';
1
+ import { AbstractChatModel, IActiveCellManager, IAttachment, IChatContext, IMessageContent, INewMessage, IUser } from '@jupyter/chat';
2
2
  import { IDocumentManager } from '@jupyterlab/docmanager';
3
- import { TranslationBundle } from '@jupyterlab/translation';
3
+ import { Contents } from '@jupyterlab/services';
4
4
  import { ISignal } from '@lumino/signaling';
5
- import { AgentManager } from './agent';
6
- import { AISettingsModel } from './models/settings-model';
7
- import { ITokenUsage } from './tokens';
5
+ import type { IAgentManager, IAISettingsModel, ITokenUsage } from './tokens';
8
6
  /**
9
7
  * AI Chat Model implementation that provides chat functionality tool integration,
10
8
  * and MCP server support.
@@ -20,6 +18,15 @@ export declare class AIChatModel extends AbstractChatModel {
20
18
  */
21
19
  get name(): string;
22
20
  set name(value: string);
21
+ /**
22
+ * Whether to save the chat automatically.
23
+ */
24
+ get autosave(): boolean;
25
+ set autosave(value: boolean);
26
+ /**
27
+ * A signal emitting when the autosave flag changed.
28
+ */
29
+ get autosaveChanged(): ISignal<AIChatModel, boolean>;
23
30
  /**
24
31
  * A signal emitting when the chat name has changed.
25
32
  */
@@ -31,11 +38,19 @@ export declare class AIChatModel extends AbstractChatModel {
31
38
  /**
32
39
  * A signal emitting when the token usage changed.
33
40
  */
34
- get tokenUsageChanged(): ISignal<AgentManager, ITokenUsage>;
41
+ get tokenUsageChanged(): ISignal<IAgentManager, ITokenUsage>;
35
42
  /**
36
43
  * Get the agent manager associated to the model.
37
44
  */
38
- get agentManager(): AgentManager;
45
+ get agentManager(): IAgentManager;
46
+ /**
47
+ * Whether save/restore is available.
48
+ */
49
+ get saveAvailable(): boolean;
50
+ /**
51
+ * Dispose of the model.
52
+ */
53
+ dispose(): void;
39
54
  /**
40
55
  * Creates a chat context for the current conversation.
41
56
  */
@@ -57,6 +72,21 @@ export declare class AIChatModel extends AbstractChatModel {
57
72
  * @param message The user message to send
58
73
  */
59
74
  sendMessage(message: INewMessage): Promise<void>;
75
+ /**
76
+ * Save the chat as json file.
77
+ */
78
+ save: () => Promise<void>;
79
+ /**
80
+ * Restore the chat from a json file.
81
+ *
82
+ * @param silent - Whether a log should be displayed in the console if the
83
+ * restoration is not possible.
84
+ */
85
+ restore: (filepath: string, silent?: boolean) => Promise<boolean>;
86
+ /**
87
+ * Serialize the model for backup
88
+ */
89
+ private _serializeModel;
60
90
  /**
61
91
  * Gets the AI user information for system messages.
62
92
  */
@@ -149,7 +179,10 @@ export declare class AIChatModel extends AbstractChatModel {
149
179
  private _agentManager;
150
180
  private _currentStreamingMessage;
151
181
  private _nameChanged;
152
- private _trans;
182
+ private _contentsManager?;
183
+ private _autosave;
184
+ private _autosaveChanged;
185
+ private _autosaveDebouncer;
153
186
  }
154
187
  /**
155
188
  * Namespace containing types and interfaces for AIChatModel.
@@ -166,11 +199,11 @@ export declare namespace AIChatModel {
166
199
  /**
167
200
  * Settings model for AI configuration
168
201
  */
169
- settingsModel: AISettingsModel;
202
+ settingsModel: IAISettingsModel;
170
203
  /**
171
204
  * Optional agent manager for handling AI agent lifecycle
172
205
  */
173
- agentManager: AgentManager;
206
+ agentManager: IAgentManager;
174
207
  /**
175
208
  * Optional active cell manager for Jupyter integration
176
209
  */
@@ -180,9 +213,13 @@ export declare namespace AIChatModel {
180
213
  */
181
214
  documentManager?: IDocumentManager;
182
215
  /**
183
- * The application language translation bundle.
216
+ * The contents manager.
217
+ */
218
+ contentsManager?: Contents.IManager;
219
+ /**
220
+ * Whether to restore or not the message (default to true)
184
221
  */
185
- trans: TranslationBundle;
222
+ restore?: boolean;
186
223
  }
187
224
  /**
188
225
  * The chat context for toolbar buttons.
@@ -203,6 +240,40 @@ export declare namespace AIChatModel {
203
240
  /**
204
241
  * The agent manager of the chat.
205
242
  */
206
- agentManager: AgentManager;
243
+ agentManager: IAgentManager;
207
244
  }
245
+ /**
246
+ * The exported chat format.
247
+ */
248
+ type ExportedChat = {
249
+ /**
250
+ * Message list (user are only string to avoid duplication).
251
+ */
252
+ messages: IMessageContent<string, string>[];
253
+ /**
254
+ * The user list.
255
+ */
256
+ users: {
257
+ [id: string]: IUser;
258
+ };
259
+ /**
260
+ * The attachments of the chat.
261
+ */
262
+ attachments?: {
263
+ [id: string]: IAttachment;
264
+ };
265
+ /**
266
+ * The metadata associated to the chat.
267
+ */
268
+ metadata?: {
269
+ /**
270
+ * Provider of the chat.
271
+ */
272
+ provider?: string;
273
+ /**
274
+ * Whether the chat is automatically saved.
275
+ */
276
+ autosave?: boolean;
277
+ };
278
+ };
208
279
  }
package/lib/chat-model.js CHANGED
@@ -2,6 +2,7 @@ import { AbstractChatModel } from '@jupyter/chat';
2
2
  import { PathExt } from '@jupyterlab/coreutils';
3
3
  import * as nbformat from '@jupyterlab/nbformat';
4
4
  import { UUID } from '@lumino/coreutils';
5
+ import { Debouncer } from '@lumino/polling';
5
6
  import { Signal } from '@lumino/signaling';
6
7
  import { AI_AVATAR } from './icons';
7
8
  /**
@@ -25,12 +26,12 @@ export class AIChatModel extends AbstractChatModel {
25
26
  this._settingsModel = options.settingsModel;
26
27
  this._user = options.user;
27
28
  this._agentManager = options.agentManager;
28
- this._trans = options.trans;
29
+ this._contentsManager = options.contentsManager;
29
30
  // Listen for agent events
30
31
  this._agentManager.agentEvent.connect(this._onAgentEvent, this);
31
32
  // Listen for settings changes to update chat behavior
32
33
  this._settingsModel.stateChanged.connect(this._onSettingsChanged, this);
33
- this.setReady();
34
+ this._autosaveDebouncer = new Debouncer(this.save, 3000);
34
35
  }
35
36
  /**
36
37
  * Override the getter/setter of the name to add a signal when renaming a chat.
@@ -41,6 +42,37 @@ export class AIChatModel extends AbstractChatModel {
41
42
  set name(value) {
42
43
  super.name = value;
43
44
  this._nameChanged.emit(value);
45
+ if (!this.messages.length) {
46
+ const directory = this._settingsModel.config.chatBackupDirectory;
47
+ const filepath = PathExt.join(directory, `${this.name}.chat`);
48
+ this.restore(filepath, true);
49
+ }
50
+ this.setReady();
51
+ }
52
+ /**
53
+ * Whether to save the chat automatically.
54
+ */
55
+ get autosave() {
56
+ return this._autosave;
57
+ }
58
+ set autosave(value) {
59
+ this._autosave = value;
60
+ this._autosaveChanged.emit(value);
61
+ if (value) {
62
+ this.messagesUpdated.connect(this._autosaveDebouncer.invoke, this._autosaveDebouncer);
63
+ this.messageChanged.connect(this._autosaveDebouncer.invoke, this._autosaveDebouncer);
64
+ this._autosaveDebouncer.invoke();
65
+ }
66
+ else {
67
+ this.messagesUpdated.disconnect(this._autosaveDebouncer.invoke, this._autosaveDebouncer);
68
+ this.messageChanged.disconnect(this._autosaveDebouncer.invoke, this._autosaveDebouncer);
69
+ }
70
+ }
71
+ /**
72
+ * A signal emitting when the autosave flag changed.
73
+ */
74
+ get autosaveChanged() {
75
+ return this._autosaveChanged;
44
76
  }
45
77
  /**
46
78
  * A signal emitting when the chat name has changed.
@@ -66,6 +98,19 @@ export class AIChatModel extends AbstractChatModel {
66
98
  get agentManager() {
67
99
  return this._agentManager;
68
100
  }
101
+ /**
102
+ * Whether save/restore is available.
103
+ */
104
+ get saveAvailable() {
105
+ return !!this._contentsManager;
106
+ }
107
+ /**
108
+ * Dispose of the model.
109
+ */
110
+ dispose() {
111
+ this.messagesUpdated.disconnect(this._autosaveDebouncer.invoke, this._autosaveDebouncer);
112
+ super.dispose();
113
+ }
69
114
  /**
70
115
  * Creates a chat context for the current conversation.
71
116
  */
@@ -172,6 +217,141 @@ export class AIChatModel extends AbstractChatModel {
172
217
  this.updateWriters([]);
173
218
  }
174
219
  }
220
+ /**
221
+ * Save the chat as json file.
222
+ */
223
+ save = async () => {
224
+ if (!this._contentsManager) {
225
+ return;
226
+ }
227
+ const directory = this._settingsModel.config.chatBackupDirectory;
228
+ const filepath = PathExt.join(directory, `${this.name}.chat`);
229
+ const content = JSON.stringify(this._serializeModel());
230
+ await this._contentsManager
231
+ .get(filepath, { content: false })
232
+ .catch(async () => {
233
+ await this._contentsManager
234
+ ?.get(directory, { content: false })
235
+ .catch(async () => {
236
+ const dir = await this._contentsManager.newUntitled({
237
+ type: 'directory'
238
+ });
239
+ await this._contentsManager.rename(dir.path, directory);
240
+ });
241
+ const file = await this._contentsManager.newUntitled({ ext: '.chat' });
242
+ await this._contentsManager?.rename(file.path, filepath);
243
+ });
244
+ await this._contentsManager.save(filepath, {
245
+ content,
246
+ type: 'file',
247
+ format: 'text'
248
+ });
249
+ };
250
+ /**
251
+ * Restore the chat from a json file.
252
+ *
253
+ * @param silent - Whether a log should be displayed in the console if the
254
+ * restoration is not possible.
255
+ */
256
+ restore = async (filepath, silent = false) => {
257
+ if (!this._contentsManager) {
258
+ return false;
259
+ }
260
+ const contentModel = await this._contentsManager
261
+ .get(filepath, { content: true, type: 'file', format: 'text' })
262
+ .catch(() => {
263
+ if (!silent) {
264
+ console.log(`There is no backup for chat '${this.name}'`);
265
+ }
266
+ return;
267
+ });
268
+ if (!contentModel) {
269
+ return false;
270
+ }
271
+ let content;
272
+ try {
273
+ content = JSON.parse(contentModel.content);
274
+ }
275
+ catch (e) {
276
+ throw `Error when parsing the chat ${filepath}\n${e}`;
277
+ }
278
+ if (content.metadata?.provider) {
279
+ if (this._settingsModel.getProvider(content.metadata.provider)) {
280
+ this._agentManager.activeProvider = content.metadata.provider;
281
+ }
282
+ else if (!silent) {
283
+ console.log(`Provider '${content.metadata.provider}' doesn't exist, it can't be restored.`);
284
+ }
285
+ }
286
+ else if (!silent) {
287
+ console.log(`Provider not providing when restoring ${filepath}.`);
288
+ }
289
+ const messages = content.messages.map(message => {
290
+ let attachments = [];
291
+ if (content.attachments && message.attachments) {
292
+ attachments =
293
+ message.attachments.map(index => content.attachments[index]) ?? [];
294
+ }
295
+ return {
296
+ ...message,
297
+ sender: content.users[message.sender] ?? { username: 'unknown' },
298
+ mentions: message.mentions?.map(mention => content.users[mention]),
299
+ attachments
300
+ };
301
+ });
302
+ this.clearMessages();
303
+ this.messagesInserted(0, messages);
304
+ this._agentManager.setHistory(messages);
305
+ this.autosave = content.metadata?.autosave ?? false;
306
+ return true;
307
+ };
308
+ /**
309
+ * Serialize the model for backup
310
+ */
311
+ _serializeModel() {
312
+ const provider = this._agentManager.activeProvider;
313
+ const messages = [];
314
+ const users = {};
315
+ const attachmentMap = new Map(); // JSON → index
316
+ const attachmentsList = []; // Actual attachments
317
+ this.messages.forEach(message => {
318
+ let attachmentIndexes = [];
319
+ if (message.attachments) {
320
+ attachmentIndexes = message.attachments.map(attachment => {
321
+ const attachmentJson = JSON.stringify(attachment);
322
+ let index;
323
+ if (attachmentMap.has(attachmentJson)) {
324
+ index = attachmentMap.get(attachmentJson);
325
+ }
326
+ else {
327
+ index = attachmentsList.length;
328
+ attachmentMap.set(attachmentJson, index);
329
+ attachmentsList.push(attachment);
330
+ }
331
+ return index.toString();
332
+ });
333
+ }
334
+ messages.push({
335
+ ...message.content,
336
+ sender: message.sender.username,
337
+ mentions: message.mentions?.map(user => user.username),
338
+ attachments: attachmentIndexes
339
+ });
340
+ if (!users[message.sender.username]) {
341
+ users[message.sender.username] = message.sender;
342
+ }
343
+ });
344
+ const attachments = Object.fromEntries(attachmentsList.map((item, index) => [index, item]));
345
+ return {
346
+ messages,
347
+ users,
348
+ attachments,
349
+ metadata: {
350
+ provider,
351
+ autosave: this.autosave
352
+ }
353
+ };
354
+ }
175
355
  /**
176
356
  * Gets the AI user information for system messages.
177
357
  */
@@ -352,13 +532,18 @@ export class AIChatModel extends AbstractChatModel {
352
532
  };
353
533
  this._toolContexts.set(event.data.callId, context);
354
534
  const toolCallMessage = {
355
- body: Private.buildToolCallHtml({
356
- toolName: context.toolName,
357
- input: context.input,
358
- status: context.status,
359
- summary: context.summary,
360
- trans: this._trans
361
- }),
535
+ body: '',
536
+ mime_model: {
537
+ data: {
538
+ 'application/vnd.jupyter.chat.components': 'tool-call'
539
+ },
540
+ metadata: {
541
+ toolName: context.toolName,
542
+ input: context.input,
543
+ status: context.status,
544
+ summary: context.summary
545
+ }
546
+ },
362
547
  sender: this._getAIUser(),
363
548
  id: messageId,
364
549
  time: Date.now() / 1000,
@@ -383,7 +568,8 @@ export class AIChatModel extends AbstractChatModel {
383
568
  });
384
569
  for (const bundle of mimeBundles) {
385
570
  this.messageAdded({
386
- body: bundle,
571
+ body: '',
572
+ mime_model: bundle,
387
573
  sender: this._getAIUser(),
388
574
  id: UUID.uuid4(),
389
575
  time: Date.now() / 1000,
@@ -456,15 +642,20 @@ export class AIChatModel extends AbstractChatModel {
456
642
  }
457
643
  context.status = status;
458
644
  existingMessage.update({
459
- body: Private.buildToolCallHtml({
460
- toolName: context.toolName,
461
- input: context.input,
462
- status: context.status,
463
- summary: context.summary,
464
- output,
465
- approvalId: context.approvalId,
466
- trans: this._trans
467
- })
645
+ mime_model: {
646
+ data: {
647
+ 'application/vnd.jupyter.chat.components': 'tool-call'
648
+ },
649
+ metadata: {
650
+ toolName: context.toolName,
651
+ input: context.input,
652
+ status: context.status,
653
+ summary: context.summary,
654
+ output,
655
+ targetId: this.name,
656
+ approvalId: context.approvalId
657
+ }
658
+ }
468
659
  });
469
660
  }
470
661
  /**
@@ -695,7 +886,10 @@ export class AIChatModel extends AbstractChatModel {
695
886
  _agentManager;
696
887
  _currentStreamingMessage = null;
697
888
  _nameChanged = new Signal(this);
698
- _trans;
889
+ _contentsManager;
890
+ _autosave = false;
891
+ _autosaveChanged = new Signal(this);
892
+ _autosaveDebouncer;
699
893
  }
700
894
  var Private;
701
895
  (function (Private) {
@@ -780,120 +974,4 @@ var Private;
780
974
  }
781
975
  }
782
976
  Private.formatToolOutput = formatToolOutput;
783
- function escapeHtml(value) {
784
- // Prefer the same native escaping approach used in JupyterLab itself
785
- // (e.g. `@jupyterlab/completer`).
786
- if (typeof document !== 'undefined') {
787
- const node = document.createElement('span');
788
- node.textContent = value;
789
- return node.innerHTML;
790
- }
791
- // Fallback
792
- return value
793
- .replace(/&/g, '&amp;')
794
- .replace(/</g, '&lt;')
795
- .replace(/>/g, '&gt;')
796
- .replace(/"/g, '&quot;')
797
- .replace(/'/g, '&#39;');
798
- }
799
- Private.escapeHtml = escapeHtml;
800
- const STATUS_CONFIG = {
801
- pending: {
802
- cssClass: 'jp-ai-tool-pending',
803
- statusClass: 'jp-ai-tool-status-pending'
804
- },
805
- awaiting_approval: {
806
- cssClass: 'jp-ai-tool-pending',
807
- statusClass: 'jp-ai-tool-status-approval',
808
- open: true
809
- },
810
- approved: {
811
- cssClass: 'jp-ai-tool-pending',
812
- statusClass: 'jp-ai-tool-status-completed'
813
- },
814
- rejected: {
815
- cssClass: 'jp-ai-tool-error',
816
- statusClass: 'jp-ai-tool-status-error'
817
- },
818
- completed: {
819
- cssClass: 'jp-ai-tool-completed',
820
- statusClass: 'jp-ai-tool-status-completed'
821
- },
822
- error: {
823
- cssClass: 'jp-ai-tool-error',
824
- statusClass: 'jp-ai-tool-status-error'
825
- }
826
- };
827
- /**
828
- * Returns the translated status text for a given tool status.
829
- */
830
- const getStatusText = (status, trans) => {
831
- switch (status) {
832
- case 'pending':
833
- return trans.__('Running...');
834
- case 'awaiting_approval':
835
- return trans.__('Awaiting Approval');
836
- case 'approved':
837
- return trans.__('Approved - Executing...');
838
- case 'rejected':
839
- return trans.__('Rejected');
840
- case 'completed':
841
- return trans.__('Completed');
842
- case 'error':
843
- return trans.__('Error');
844
- }
845
- };
846
- /**
847
- * Builds HTML for a tool call display.
848
- */
849
- function buildToolCallHtml(options) {
850
- const { toolName, input, status, summary, output, approvalId, trans } = options;
851
- const config = STATUS_CONFIG[status];
852
- const statusText = getStatusText(status, trans);
853
- const escapedToolName = escapeHtml(toolName);
854
- const escapedInput = escapeHtml(input);
855
- const openAttr = config.open ? ' open' : '';
856
- const summaryHtml = summary
857
- ? `<span class="jp-ai-tool-summary">${escapeHtml(summary)}</span>`
858
- : '';
859
- let bodyContent = `
860
- <div class="jp-ai-tool-section">
861
- <div class="jp-ai-tool-label">${trans.__('Input')}</div>
862
- <pre class="jp-ai-tool-code"><code>${escapedInput}</code></pre>
863
- </div>`;
864
- // Add approval buttons if awaiting approval
865
- if (status === 'awaiting_approval' && approvalId) {
866
- bodyContent += `
867
- <div class="jp-ai-tool-approval-buttons jp-ai-approval-id--${approvalId}">
868
- <button class="jp-ai-approval-btn jp-ai-approval-approve">${trans.__('Approve')}</button>
869
- <button class="jp-ai-approval-btn jp-ai-approval-reject">${trans.__('Reject')}</button>
870
- </div>`;
871
- }
872
- // Add output/result section if provided
873
- if (output !== undefined) {
874
- const escapedOutput = escapeHtml(output);
875
- const label = status === 'error' ? trans.__('Error') : trans.__('Result');
876
- bodyContent += `
877
- <div class="jp-ai-tool-section">
878
- <div class="jp-ai-tool-label">${label}</div>
879
- <pre class="jp-ai-tool-code"><code>${escapedOutput}</code></pre>
880
- </div>`;
881
- }
882
- const HTMLContent = `<details class="jp-ai-tool-call ${config.cssClass}"${openAttr}>
883
- <summary class="jp-ai-tool-header">
884
- <div class="jp-ai-tool-icon">⚡</div>
885
- <div class="jp-ai-tool-title">${escapedToolName}${summaryHtml}</div>
886
- <div class="jp-ai-tool-status ${config.statusClass}">${statusText}</div>
887
- </summary>
888
- <div class="jp-ai-tool-body">${bodyContent}
889
- </div>
890
- </details>`;
891
- return {
892
- trusted: true,
893
- data: {
894
- 'text/html': HTMLContent
895
- }
896
- };
897
- }
898
- Private.buildToolCallHtml = buildToolCallHtml;
899
977
  })(Private || (Private = {}));
@@ -1,7 +1,6 @@
1
1
  import { CompletionHandler, IInlineCompletionContext, IInlineCompletionList, IInlineCompletionProvider } from '@jupyterlab/completer';
2
2
  import { ISecretsManager } from 'jupyter-secrets-manager';
3
- import { AISettingsModel } from '../models/settings-model';
4
- import { type IProviderRegistry } from '../tokens';
3
+ import { type IAISettingsModel, type IProviderRegistry } from '../tokens';
5
4
  /**
6
5
  * Configuration interface for provider-specific completion behavior
7
6
  */
@@ -68,7 +67,7 @@ export declare namespace AICompletionProvider {
68
67
  /**
69
68
  * The AI settings model.
70
69
  */
71
- settingsModel: AISettingsModel;
70
+ settingsModel: IAISettingsModel;
72
71
  /**
73
72
  * The provider registry
74
73
  */