@jupyterlite/ai 0.17.0 → 0.19.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/chat-commands/clear.d.ts +1 -0
- package/lib/chat-commands/index.d.ts +1 -0
- package/lib/chat-commands/skills.d.ts +2 -1
- package/lib/chat-model-handler.d.ts +4 -3
- package/lib/chat-model-handler.js +2 -1
- package/lib/chat-model.d.ts +148 -8
- package/lib/chat-model.js +368 -79
- package/lib/completion/completion-provider.d.ts +3 -1
- package/lib/completion/completion-provider.js +1 -2
- package/lib/completion/index.d.ts +1 -0
- package/lib/components/clear-button.d.ts +1 -0
- package/lib/components/clear-button.js +3 -4
- package/lib/components/completion-status.d.ts +1 -0
- package/lib/components/completion-status.js +5 -4
- package/lib/components/index.d.ts +1 -0
- package/lib/components/model-select.d.ts +1 -0
- package/lib/components/model-select.js +62 -67
- package/lib/components/save-button.d.ts +3 -2
- package/lib/components/save-button.js +4 -5
- package/lib/components/stop-button.d.ts +1 -0
- package/lib/components/stop-button.js +3 -4
- package/lib/components/tool-select.d.ts +3 -1
- package/lib/components/tool-select.js +47 -60
- package/lib/components/usage-display.d.ts +4 -2
- package/lib/components/usage-display.js +50 -61
- package/lib/diff-manager.d.ts +3 -1
- package/lib/index.d.ts +3 -2
- package/lib/index.js +50 -59
- package/lib/models/settings-model.d.ts +3 -1
- package/lib/models/settings-model.js +1 -0
- package/lib/rendered-message-outputarea.d.ts +1 -0
- package/lib/tokens.d.ts +48 -597
- package/lib/tokens.js +2 -31
- package/lib/widgets/ai-settings.d.ts +3 -1
- package/lib/widgets/ai-settings.js +185 -344
- package/lib/widgets/main-area-chat.d.ts +3 -3
- package/lib/widgets/main-area-chat.js +2 -4
- package/lib/widgets/provider-config-dialog.d.ts +2 -1
- package/lib/widgets/provider-config-dialog.js +102 -167
- package/package.json +111 -258
- package/schema/settings-model.json +6 -0
- package/src/chat-commands/skills.ts +2 -2
- package/src/chat-model-handler.ts +10 -6
- package/src/chat-model.ts +488 -96
- package/src/completion/completion-provider.ts +6 -6
- package/src/components/clear-button.tsx +0 -2
- package/src/components/completion-status.tsx +2 -2
- package/src/components/model-select.tsx +1 -1
- package/src/components/save-button.tsx +3 -3
- package/src/components/stop-button.tsx +0 -2
- package/src/components/tool-select.tsx +10 -9
- package/src/components/usage-display.tsx +4 -2
- package/src/diff-manager.ts +4 -3
- package/src/index.ts +103 -107
- package/src/models/settings-model.ts +7 -6
- package/src/tokens.ts +54 -744
- package/src/widgets/ai-settings.tsx +40 -11
- package/src/widgets/main-area-chat.ts +5 -8
- package/src/widgets/provider-config-dialog.tsx +8 -8
- package/LICENSE +0 -30
- package/README.md +0 -49
- package/lib/agent.d.ts +0 -277
- package/lib/agent.js +0 -1116
- package/lib/icons.d.ts +0 -3
- package/lib/icons.js +0 -8
- package/lib/providers/built-in-providers.d.ts +0 -21
- package/lib/providers/built-in-providers.js +0 -233
- package/lib/providers/generated-context-windows.d.ts +0 -8
- package/lib/providers/generated-context-windows.js +0 -96
- package/lib/providers/model-info.d.ts +0 -3
- package/lib/providers/model-info.js +0 -58
- package/lib/providers/models.d.ts +0 -37
- package/lib/providers/models.js +0 -28
- package/lib/providers/provider-registry.d.ts +0 -49
- package/lib/providers/provider-registry.js +0 -72
- package/lib/providers/provider-tools.d.ts +0 -36
- package/lib/providers/provider-tools.js +0 -93
- package/lib/skills/index.d.ts +0 -4
- package/lib/skills/index.js +0 -7
- package/lib/skills/parse-skill.d.ts +0 -25
- package/lib/skills/parse-skill.js +0 -69
- package/lib/skills/skill-loader.d.ts +0 -25
- package/lib/skills/skill-loader.js +0 -133
- package/lib/skills/skill-registry.d.ts +0 -31
- package/lib/skills/skill-registry.js +0 -100
- package/lib/skills/types.d.ts +0 -29
- package/lib/skills/types.js +0 -5
- package/lib/tools/commands.d.ts +0 -11
- package/lib/tools/commands.js +0 -154
- package/lib/tools/skills.d.ts +0 -9
- package/lib/tools/skills.js +0 -73
- package/lib/tools/tool-registry.d.ts +0 -35
- package/lib/tools/tool-registry.js +0 -55
- package/lib/tools/web.d.ts +0 -8
- package/lib/tools/web.js +0 -196
- package/src/agent.ts +0 -1441
- package/src/icons.ts +0 -11
- package/src/providers/built-in-providers.ts +0 -241
- package/src/providers/generated-context-windows.ts +0 -102
- package/src/providers/model-info.ts +0 -88
- package/src/providers/models.ts +0 -76
- package/src/providers/provider-registry.ts +0 -88
- package/src/providers/provider-tools.ts +0 -179
- package/src/skills/index.ts +0 -14
- package/src/skills/parse-skill.ts +0 -91
- package/src/skills/skill-loader.ts +0 -175
- package/src/skills/skill-registry.ts +0 -137
- package/src/skills/types.ts +0 -37
- package/src/tools/commands.ts +0 -210
- package/src/tools/skills.ts +0 -84
- package/src/tools/tool-registry.ts +0 -63
- package/src/tools/web.ts +0 -238
- package/src/types.d.ts +0 -4
- package/style/icons/jupyternaut-lite.svg +0 -7
package/src/chat-model.ts
CHANGED
|
@@ -26,6 +26,14 @@ import { IRenderMime } from '@jupyterlab/rendermime';
|
|
|
26
26
|
|
|
27
27
|
import { Contents } from '@jupyterlab/services';
|
|
28
28
|
|
|
29
|
+
import { AI_AVATAR } from '@jupyternaut/agent';
|
|
30
|
+
|
|
31
|
+
import type {
|
|
32
|
+
IAgentManager,
|
|
33
|
+
IProviderRegistry,
|
|
34
|
+
ITokenUsage
|
|
35
|
+
} from '@jupyternaut/agent';
|
|
36
|
+
|
|
29
37
|
import { UUID } from '@lumino/coreutils';
|
|
30
38
|
|
|
31
39
|
import { Debouncer } from '@lumino/polling';
|
|
@@ -34,9 +42,13 @@ import { ISignal, Signal } from '@lumino/signaling';
|
|
|
34
42
|
|
|
35
43
|
import type { UserContent, ImagePart, FilePart, ModelMessage } from 'ai';
|
|
36
44
|
|
|
37
|
-
import {
|
|
45
|
+
import type { IAIChatModel, IAISettingsModel } from './tokens';
|
|
38
46
|
|
|
39
|
-
import
|
|
47
|
+
import {
|
|
48
|
+
modelSupportsAudio,
|
|
49
|
+
modelSupportsImages,
|
|
50
|
+
modelSupportsPdf
|
|
51
|
+
} from '@jupyternaut/agent';
|
|
40
52
|
|
|
41
53
|
/**
|
|
42
54
|
* Tool call status types.
|
|
@@ -69,10 +81,6 @@ interface IToolExecutionContext {
|
|
|
69
81
|
* The tool input (formatted).
|
|
70
82
|
*/
|
|
71
83
|
input: string;
|
|
72
|
-
/**
|
|
73
|
-
* Optional approval ID if awaiting approval.
|
|
74
|
-
*/
|
|
75
|
-
approvalId?: string;
|
|
76
84
|
/**
|
|
77
85
|
* Current status.
|
|
78
86
|
*/
|
|
@@ -91,7 +99,7 @@ interface IToolExecutionContext {
|
|
|
91
99
|
* AI Chat Model implementation that provides chat functionality tool integration,
|
|
92
100
|
* and MCP server support.
|
|
93
101
|
*/
|
|
94
|
-
export class AIChatModel extends AbstractChatModel {
|
|
102
|
+
export class AIChatModel extends AbstractChatModel implements IAIChatModel {
|
|
95
103
|
/**
|
|
96
104
|
* Constructs a new AIChatModel instance.
|
|
97
105
|
* @param options Configuration options for the chat model
|
|
@@ -109,6 +117,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
109
117
|
this._user = options.user;
|
|
110
118
|
this._agentManager = options.agentManager;
|
|
111
119
|
this._contentsManager = options.contentsManager;
|
|
120
|
+
this._providerRegistry = options.providerRegistry;
|
|
112
121
|
|
|
113
122
|
// Listen for agent events
|
|
114
123
|
this._agentManager.agentEvent.connect(this._onAgentEvent, this);
|
|
@@ -116,6 +125,13 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
116
125
|
// Listen for settings changes to update chat behavior
|
|
117
126
|
this._settingsModel.stateChanged.connect(this._onSettingsChanged, this);
|
|
118
127
|
|
|
128
|
+
// Rebuild history when the model changes
|
|
129
|
+
this._agentManager.activeProviderChanged.connect(
|
|
130
|
+
this._onModelChanged,
|
|
131
|
+
this
|
|
132
|
+
);
|
|
133
|
+
this._settingsModel.stateChanged.connect(this._onModelChanged, this);
|
|
134
|
+
|
|
119
135
|
this._autosaveDebouncer = new Debouncer(this.save, 3000);
|
|
120
136
|
}
|
|
121
137
|
|
|
@@ -139,7 +155,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
139
155
|
/**
|
|
140
156
|
* A signal emitting when the chat name has changed.
|
|
141
157
|
*/
|
|
142
|
-
get nameChanged(): ISignal<
|
|
158
|
+
get nameChanged(): ISignal<IAIChatModel, string> {
|
|
143
159
|
return this._nameChanged;
|
|
144
160
|
}
|
|
145
161
|
|
|
@@ -160,7 +176,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
160
176
|
/**
|
|
161
177
|
* A signal emitting when the chat title has changed.
|
|
162
178
|
*/
|
|
163
|
-
get titleChanged(): ISignal<
|
|
179
|
+
get titleChanged(): ISignal<IAIChatModel, string | null> {
|
|
164
180
|
return this._titleChanged;
|
|
165
181
|
}
|
|
166
182
|
|
|
@@ -171,6 +187,9 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
171
187
|
return this._autosave;
|
|
172
188
|
}
|
|
173
189
|
set autosave(value: boolean) {
|
|
190
|
+
if (value === this._autosave) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
174
193
|
this._autosave = value;
|
|
175
194
|
this._autosaveChanged.emit(value);
|
|
176
195
|
if (value) {
|
|
@@ -182,7 +201,6 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
182
201
|
this._autosaveDebouncer.invoke,
|
|
183
202
|
this._autosaveDebouncer
|
|
184
203
|
);
|
|
185
|
-
this._autosaveDebouncer.invoke();
|
|
186
204
|
} else {
|
|
187
205
|
this.messagesUpdated.disconnect(
|
|
188
206
|
this._autosaveDebouncer.invoke,
|
|
@@ -193,12 +211,13 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
193
211
|
this._autosaveDebouncer
|
|
194
212
|
);
|
|
195
213
|
}
|
|
214
|
+
this._autosaveDebouncer.invoke();
|
|
196
215
|
}
|
|
197
216
|
|
|
198
217
|
/**
|
|
199
218
|
* A signal emitting when the autosave flag changed.
|
|
200
219
|
*/
|
|
201
|
-
get autosaveChanged(): ISignal<
|
|
220
|
+
get autosaveChanged(): ISignal<IAIChatModel, boolean> {
|
|
202
221
|
return this._autosaveChanged;
|
|
203
222
|
}
|
|
204
223
|
|
|
@@ -217,7 +236,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
217
236
|
}
|
|
218
237
|
|
|
219
238
|
/**
|
|
220
|
-
*
|
|
239
|
+
* The agent manager used in the model.
|
|
221
240
|
*/
|
|
222
241
|
get agentManager(): IAgentManager {
|
|
223
242
|
return this._agentManager;
|
|
@@ -234,6 +253,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
234
253
|
* Dispose of the model.
|
|
235
254
|
*/
|
|
236
255
|
dispose(): void {
|
|
256
|
+
this.stopStreaming();
|
|
237
257
|
this.messagesUpdated.disconnect(
|
|
238
258
|
this._autosaveDebouncer.invoke,
|
|
239
259
|
this._autosaveDebouncer
|
|
@@ -253,7 +273,11 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
253
273
|
stopStreaming: () => this.stopStreaming(),
|
|
254
274
|
clearMessages: () => this.clearMessages(),
|
|
255
275
|
agentManager: this._agentManager,
|
|
256
|
-
addSystemMessage: (body: string) => this.
|
|
276
|
+
addSystemMessage: (body: string) => this._addSystemMessage(body),
|
|
277
|
+
removeQueuedMessage: (id: string) => this.removeQueuedMessage(id),
|
|
278
|
+
reorderQueuedMessages: (ids: string[]) => this.reorderQueuedMessages(ids),
|
|
279
|
+
editQueuedMessage: (id: string, body: string) =>
|
|
280
|
+
this.editQueuedMessage(id, body)
|
|
257
281
|
};
|
|
258
282
|
}
|
|
259
283
|
|
|
@@ -268,15 +292,31 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
268
292
|
* Clears all messages from the chat and resets conversation state.
|
|
269
293
|
*/
|
|
270
294
|
clearMessages = async (): Promise<void> => {
|
|
295
|
+
this.stopStreaming();
|
|
296
|
+
this._messageQueue = [];
|
|
297
|
+
this._isBusy = false;
|
|
298
|
+
this._queueMessageId = null;
|
|
299
|
+
this._currentStreamingMessage = null;
|
|
271
300
|
this.messagesDeleted(0, this.messages.length);
|
|
301
|
+
this.title = null;
|
|
272
302
|
this._toolContexts.clear();
|
|
273
303
|
await this._agentManager.clearHistory();
|
|
274
304
|
};
|
|
275
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Overrides messageAdded to ensure queued messages stay at the bottom.
|
|
308
|
+
*/
|
|
309
|
+
override messageAdded(message: IMessageContent): void {
|
|
310
|
+
super.messageAdded(message);
|
|
311
|
+
if (this._queueMessageId && message.id !== this._queueMessageId) {
|
|
312
|
+
this._updateQueueUI();
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
276
316
|
/**
|
|
277
317
|
* Adds a non-user message to the chat (used by chat commands).
|
|
278
318
|
*/
|
|
279
|
-
|
|
319
|
+
private _addSystemMessage(body: string): void {
|
|
280
320
|
const message: IMessageContent = {
|
|
281
321
|
body,
|
|
282
322
|
sender: this._getAIUser(),
|
|
@@ -309,10 +349,10 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
309
349
|
raw_time: false,
|
|
310
350
|
attachments: [...this.input.attachments]
|
|
311
351
|
};
|
|
312
|
-
this.messageAdded(userMessage);
|
|
313
352
|
|
|
314
353
|
// Check if we have valid configuration
|
|
315
354
|
if (!this._agentManager.hasValidConfig()) {
|
|
355
|
+
this.messageAdded(userMessage);
|
|
316
356
|
const errorMessage: IMessageContent = {
|
|
317
357
|
body: 'Please configure your AI settings first. Open the AI Settings to set your API key and model.',
|
|
318
358
|
sender: this._getAIUser(),
|
|
@@ -325,35 +365,71 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
325
365
|
return;
|
|
326
366
|
}
|
|
327
367
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
368
|
+
if (this._isBusy) {
|
|
369
|
+
this._messageQueue.push({
|
|
370
|
+
id: UUID.uuid4(),
|
|
371
|
+
body: message.body,
|
|
372
|
+
_originalMsg: userMessage
|
|
373
|
+
});
|
|
374
|
+
this.input.clearAttachments();
|
|
375
|
+
this._updateQueueUI();
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
337
378
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
'\n\n--- Attached Files ---\n' + textContents.join('\n\n');
|
|
342
|
-
}
|
|
379
|
+
this._isBusy = true;
|
|
380
|
+
this.messageAdded(userMessage);
|
|
381
|
+
this.input.clearAttachments();
|
|
343
382
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
} else {
|
|
347
|
-
enhancedMessage = textPart;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
383
|
+
await this._processMessage(userMessage);
|
|
384
|
+
}
|
|
350
385
|
|
|
386
|
+
/**
|
|
387
|
+
* Internal method to process attachments and send the message to the agent.
|
|
388
|
+
*/
|
|
389
|
+
private async _processMessage(userMessage: IMessageContent): Promise<void> {
|
|
390
|
+
try {
|
|
351
391
|
this.updateWriters([{ user: this._getAIUser() }]);
|
|
352
392
|
|
|
393
|
+
let enhancedMessage: UserContent = userMessage.body;
|
|
394
|
+
if (userMessage.attachments && userMessage.attachments.length > 0) {
|
|
395
|
+
const providerConfig = this._settingsModel.getProvider(
|
|
396
|
+
this._agentManager.activeProvider
|
|
397
|
+
);
|
|
398
|
+
const supportsImages = modelSupportsImages(
|
|
399
|
+
providerConfig,
|
|
400
|
+
this._providerRegistry
|
|
401
|
+
);
|
|
402
|
+
const supportsPdf = modelSupportsPdf(
|
|
403
|
+
providerConfig,
|
|
404
|
+
this._providerRegistry
|
|
405
|
+
);
|
|
406
|
+
const supportsAudio = modelSupportsAudio(
|
|
407
|
+
providerConfig,
|
|
408
|
+
this._providerRegistry
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
enhancedMessage = await Private.processAttachments(
|
|
412
|
+
userMessage.attachments,
|
|
413
|
+
this.input.documentManager,
|
|
414
|
+
userMessage.body,
|
|
415
|
+
supportsImages,
|
|
416
|
+
supportsPdf,
|
|
417
|
+
supportsAudio
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
353
421
|
await this._agentManager.generateResponse(enhancedMessage);
|
|
354
422
|
} catch (error) {
|
|
355
423
|
const errorMessage: IMessageContent = {
|
|
356
|
-
body:
|
|
424
|
+
body: '',
|
|
425
|
+
mime_model: {
|
|
426
|
+
data: {
|
|
427
|
+
'application/vnd.jupyter.chat.components': 'error'
|
|
428
|
+
},
|
|
429
|
+
metadata: {
|
|
430
|
+
errorMessage: `Error generating AI response: ${(error as Error).message}`
|
|
431
|
+
}
|
|
432
|
+
},
|
|
357
433
|
sender: this._getAIUser(),
|
|
358
434
|
id: UUID.uuid4(),
|
|
359
435
|
time: Date.now() / 1000,
|
|
@@ -362,7 +438,127 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
362
438
|
};
|
|
363
439
|
this.messageAdded(errorMessage);
|
|
364
440
|
} finally {
|
|
441
|
+
this._drainQueue();
|
|
442
|
+
|
|
443
|
+
if (
|
|
444
|
+
this._settingsModel.config.autoTitle &&
|
|
445
|
+
(this.messages.filter(msg => msg.sender.username !== 'ai-assistant')
|
|
446
|
+
.length <= 5 ||
|
|
447
|
+
this.title === null)
|
|
448
|
+
) {
|
|
449
|
+
try {
|
|
450
|
+
this.title = await this.requestTitle();
|
|
451
|
+
} catch (e) {
|
|
452
|
+
console.warn('Error while generating a title\n', e);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Removes the message-queue chat component.
|
|
460
|
+
*/
|
|
461
|
+
private _removeQueueUI(): void {
|
|
462
|
+
if (this._queueMessageId) {
|
|
463
|
+
const existingMsg = this.messages.find(
|
|
464
|
+
msg => msg.id === this._queueMessageId
|
|
465
|
+
);
|
|
466
|
+
if (existingMsg) {
|
|
467
|
+
const idx = this.messages.indexOf(existingMsg);
|
|
468
|
+
if (idx !== -1) {
|
|
469
|
+
this.messagesDeleted(idx, 1);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
this._queueMessageId = null;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Creates or updates the message-queue chat component.
|
|
478
|
+
*/
|
|
479
|
+
private _updateQueueUI(): void {
|
|
480
|
+
this._removeQueueUI();
|
|
481
|
+
|
|
482
|
+
if (this._messageQueue.length === 0) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const queueBody = {
|
|
487
|
+
data: {
|
|
488
|
+
'application/vnd.jupyter.chat.components': 'message-queue'
|
|
489
|
+
},
|
|
490
|
+
metadata: {
|
|
491
|
+
messages: this._messageQueue.map(m => ({
|
|
492
|
+
id: m.id,
|
|
493
|
+
body: m.body,
|
|
494
|
+
attachments: m._originalMsg.attachments
|
|
495
|
+
})),
|
|
496
|
+
targetId: this.name
|
|
497
|
+
}
|
|
498
|
+
} as IMimeModelBody;
|
|
499
|
+
|
|
500
|
+
this._queueMessageId = UUID.uuid4();
|
|
501
|
+
const queueMessage: IMessageContent = {
|
|
502
|
+
body: '',
|
|
503
|
+
mime_model: queueBody,
|
|
504
|
+
sender: { username: 'system', display_name: '' },
|
|
505
|
+
id: this._queueMessageId,
|
|
506
|
+
time: Date.now() / 1000,
|
|
507
|
+
type: 'msg',
|
|
508
|
+
raw_time: false
|
|
509
|
+
};
|
|
510
|
+
this.messageAdded(queueMessage);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Processes the next message in the queue, or marks the agent as idle.
|
|
515
|
+
*/
|
|
516
|
+
private async _drainQueue(): Promise<void> {
|
|
517
|
+
if (this._messageQueue.length === 0) {
|
|
518
|
+
this._isBusy = false;
|
|
365
519
|
this.updateWriters([]);
|
|
520
|
+
this._removeQueueUI();
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Dequeue and push to chat
|
|
525
|
+
const next = this._messageQueue.shift()!;
|
|
526
|
+
next._originalMsg.time = Date.now() / 1000;
|
|
527
|
+
this.messageAdded(next._originalMsg);
|
|
528
|
+
|
|
529
|
+
await this._processMessage(next._originalMsg);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Removes a queued message by its ID.
|
|
534
|
+
* @param messageId The ID of the queued message to remove
|
|
535
|
+
*/
|
|
536
|
+
removeQueuedMessage(messageId: string): void {
|
|
537
|
+
this.messageQueue = this._messageQueue.filter(msg => msg.id !== messageId);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Reorders queued messages by their IDs.
|
|
542
|
+
* @param messageIds Array of message IDs in the desired order
|
|
543
|
+
*/
|
|
544
|
+
reorderQueuedMessages(messageIds: string[]): void {
|
|
545
|
+
const byId = new Map(this._messageQueue.map(m => [m.id, m]));
|
|
546
|
+
this.messageQueue = messageIds
|
|
547
|
+
.map(id => byId.get(id))
|
|
548
|
+
.filter((m): m is Private.IQueuedItem => m !== undefined);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Edits a queued message by its ID.
|
|
553
|
+
* @param messageId The ID of the queued message to edit
|
|
554
|
+
* @param newBody The new body of the message
|
|
555
|
+
*/
|
|
556
|
+
editQueuedMessage(messageId: string, newBody: string): void {
|
|
557
|
+
const queue = [...this._messageQueue];
|
|
558
|
+
const index = queue.findIndex(m => m.id === messageId);
|
|
559
|
+
if (index !== -1) {
|
|
560
|
+
queue[index] = { ...queue[index], body: newBody };
|
|
561
|
+
this.messageQueue = queue;
|
|
366
562
|
}
|
|
367
563
|
}
|
|
368
564
|
|
|
@@ -434,7 +630,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
434
630
|
);
|
|
435
631
|
}
|
|
436
632
|
} else if (!silent) {
|
|
437
|
-
console.log(`Provider not
|
|
633
|
+
console.log(`Provider not provided when restoring ${filepath}.`);
|
|
438
634
|
}
|
|
439
635
|
|
|
440
636
|
const messages: IMessageContent[] = content.messages.map(message => {
|
|
@@ -452,7 +648,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
452
648
|
});
|
|
453
649
|
await this.clearMessages();
|
|
454
650
|
this.messagesInserted(0, messages);
|
|
455
|
-
this.
|
|
651
|
+
await this._rebuildHistory();
|
|
456
652
|
this.autosave = content.metadata?.autosave ?? false;
|
|
457
653
|
this.title = content.metadata?.title ?? null;
|
|
458
654
|
return true;
|
|
@@ -473,7 +669,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
473
669
|
{
|
|
474
670
|
role: 'system',
|
|
475
671
|
content:
|
|
476
|
-
"Generate a concise title (no more than 10 words) for the following conversation. Do not use formatting. Focus on the user'
|
|
672
|
+
"Generate a concise title (no more than 10 words) for the following conversation. Do not use formatting, quotes, or punctuation. Focus on the subject matter and specific content the user is working on, not on the actions taken (e.g. prefer 'Pandas DataFrame filtering' over 'Opening a notebook'). The title should be a noun phrase describing the topic."
|
|
477
673
|
},
|
|
478
674
|
{
|
|
479
675
|
role: 'user',
|
|
@@ -494,6 +690,13 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
494
690
|
const attachmentsList: IAttachment[] = []; // Actual attachments
|
|
495
691
|
|
|
496
692
|
this.messages.forEach(message => {
|
|
693
|
+
if (
|
|
694
|
+
message.content?.mime_model?.data?.[
|
|
695
|
+
'application/vnd.jupyter.chat.components'
|
|
696
|
+
] === 'message-queue'
|
|
697
|
+
) {
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
497
700
|
let attachmentIndexes: string[] = [];
|
|
498
701
|
if (message.attachments) {
|
|
499
702
|
attachmentIndexes = message.attachments.map(attachment => {
|
|
@@ -559,6 +762,75 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
559
762
|
// Agent manager handles agent recreation automatically via its own settings listener
|
|
560
763
|
}
|
|
561
764
|
|
|
765
|
+
/**
|
|
766
|
+
* Rebuild history when the active model changes.
|
|
767
|
+
*/
|
|
768
|
+
private _onModelChanged(): void {
|
|
769
|
+
const providerConfig = this._settingsModel.getProvider(
|
|
770
|
+
this._agentManager.activeProvider
|
|
771
|
+
);
|
|
772
|
+
const modelKey = providerConfig
|
|
773
|
+
? `${providerConfig.provider}:${providerConfig.model}`
|
|
774
|
+
: undefined;
|
|
775
|
+
if (modelKey && modelKey !== this._currentModelKey) {
|
|
776
|
+
this._currentModelKey = modelKey;
|
|
777
|
+
this._rebuildHistory().catch(e =>
|
|
778
|
+
console.warn('Failed to rebuild history on model change:', e)
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Rebuilds the agent history from the current messages.
|
|
785
|
+
* For vision-capable models, re-reads binary attachments from disk.
|
|
786
|
+
* For text-only models, uses message text only.
|
|
787
|
+
*/
|
|
788
|
+
private async _rebuildHistory(): Promise<void> {
|
|
789
|
+
const providerConfig = this._settingsModel.getProvider(
|
|
790
|
+
this._agentManager.activeProvider
|
|
791
|
+
);
|
|
792
|
+
const supportsImages = modelSupportsImages(
|
|
793
|
+
providerConfig,
|
|
794
|
+
this._providerRegistry
|
|
795
|
+
);
|
|
796
|
+
const supportsPdf = modelSupportsPdf(
|
|
797
|
+
providerConfig,
|
|
798
|
+
this._providerRegistry
|
|
799
|
+
);
|
|
800
|
+
const supportsAudio = modelSupportsAudio(
|
|
801
|
+
providerConfig,
|
|
802
|
+
this._providerRegistry
|
|
803
|
+
);
|
|
804
|
+
|
|
805
|
+
const modelMessages: ModelMessage[] = [];
|
|
806
|
+
for (const msg of this.messages) {
|
|
807
|
+
const isAI = msg.sender.username === 'ai-assistant';
|
|
808
|
+
if (!isAI && msg.attachments?.length) {
|
|
809
|
+
const enhancedContent = await Private.processAttachments(
|
|
810
|
+
msg.attachments,
|
|
811
|
+
this.input.documentManager,
|
|
812
|
+
msg.body,
|
|
813
|
+
supportsImages,
|
|
814
|
+
supportsPdf,
|
|
815
|
+
supportsAudio
|
|
816
|
+
);
|
|
817
|
+
|
|
818
|
+
modelMessages.push({
|
|
819
|
+
role: 'user',
|
|
820
|
+
content: enhancedContent
|
|
821
|
+
} as ModelMessage);
|
|
822
|
+
} else if (msg.body) {
|
|
823
|
+
modelMessages.push({
|
|
824
|
+
role: isAI ? 'assistant' : 'user',
|
|
825
|
+
content: msg.body
|
|
826
|
+
} as ModelMessage);
|
|
827
|
+
}
|
|
828
|
+
// Skip messages with empty body like tool calls
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
this._agentManager.setHistory(modelMessages);
|
|
832
|
+
}
|
|
833
|
+
|
|
562
834
|
/**
|
|
563
835
|
* Handles events emitted by the agent manager.
|
|
564
836
|
* @param event The event data containing type and payload
|
|
@@ -613,6 +885,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
613
885
|
this.messageAdded(aiMessage);
|
|
614
886
|
this._currentStreamingMessage =
|
|
615
887
|
this.messages.find(message => message.id === aiMessage.id) ?? null;
|
|
888
|
+
this._updateQueueUI();
|
|
616
889
|
}
|
|
617
890
|
|
|
618
891
|
/**
|
|
@@ -759,13 +1032,18 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
759
1032
|
body: '',
|
|
760
1033
|
mime_model: {
|
|
761
1034
|
data: {
|
|
762
|
-
'application/vnd.jupyter.chat.components': 'tool-
|
|
1035
|
+
'application/vnd.jupyter.chat.components': 'grouped-tool-calls'
|
|
763
1036
|
},
|
|
764
1037
|
metadata: {
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
1038
|
+
toolCalls: [
|
|
1039
|
+
{
|
|
1040
|
+
toolCallId: context.toolCallId,
|
|
1041
|
+
title: `${context.toolName}${context.summary ? ' : ' + context.summary : ''}`,
|
|
1042
|
+
kind: context.toolName,
|
|
1043
|
+
status: 'in_progress',
|
|
1044
|
+
rawInput: context.input
|
|
1045
|
+
}
|
|
1046
|
+
]
|
|
769
1047
|
}
|
|
770
1048
|
},
|
|
771
1049
|
sender: this._getAIUser(),
|
|
@@ -776,6 +1054,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
776
1054
|
};
|
|
777
1055
|
|
|
778
1056
|
this.messageAdded(toolCallMessage);
|
|
1057
|
+
this._updateQueueUI();
|
|
779
1058
|
}
|
|
780
1059
|
|
|
781
1060
|
/**
|
|
@@ -837,13 +1116,22 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
837
1116
|
*/
|
|
838
1117
|
private _handleErrorEvent(event: IAgentManager.IAgentEvent<'error'>): void {
|
|
839
1118
|
this.messageAdded({
|
|
840
|
-
body:
|
|
1119
|
+
body: '',
|
|
1120
|
+
mime_model: {
|
|
1121
|
+
data: {
|
|
1122
|
+
'application/vnd.jupyter.chat.components': 'error'
|
|
1123
|
+
},
|
|
1124
|
+
metadata: {
|
|
1125
|
+
errorMessage: `Error generating response: ${event.data.error.message}`
|
|
1126
|
+
}
|
|
1127
|
+
},
|
|
841
1128
|
sender: this._getAIUser(),
|
|
842
1129
|
id: UUID.uuid4(),
|
|
843
1130
|
time: Date.now() / 1000,
|
|
844
1131
|
type: 'msg',
|
|
845
1132
|
raw_time: false
|
|
846
1133
|
});
|
|
1134
|
+
this._updateQueueUI();
|
|
847
1135
|
}
|
|
848
1136
|
|
|
849
1137
|
/**
|
|
@@ -856,7 +1144,6 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
856
1144
|
if (!context) {
|
|
857
1145
|
return;
|
|
858
1146
|
}
|
|
859
|
-
context.approvalId = event.data.approvalId;
|
|
860
1147
|
context.input = JSON.stringify(event.data.args, null, 2);
|
|
861
1148
|
this._updateToolCallUI(event.data.toolCallId, 'awaiting_approval');
|
|
862
1149
|
}
|
|
@@ -867,15 +1154,13 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
867
1154
|
private _handleToolApprovalResolved(
|
|
868
1155
|
event: IAgentManager.IAgentEvent<'tool_approval_resolved'>
|
|
869
1156
|
): void {
|
|
870
|
-
const context =
|
|
871
|
-
ctx => ctx.approvalId === event.data.approvalId
|
|
872
|
-
);
|
|
1157
|
+
const context = this._toolContexts.get(event.data.toolCallId);
|
|
873
1158
|
if (!context) {
|
|
874
1159
|
return;
|
|
875
1160
|
}
|
|
876
1161
|
|
|
877
1162
|
const status = event.data.approved ? 'approved' : 'rejected';
|
|
878
|
-
this._updateToolCallUI(
|
|
1163
|
+
this._updateToolCallUI(event.data.toolCallId, status);
|
|
879
1164
|
|
|
880
1165
|
if (!event.data.approved) {
|
|
881
1166
|
this._toolContexts.delete(context.toolCallId);
|
|
@@ -906,47 +1191,96 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
906
1191
|
existingMessage.update({
|
|
907
1192
|
mime_model: {
|
|
908
1193
|
data: {
|
|
909
|
-
'application/vnd.jupyter.chat.components': 'tool-
|
|
1194
|
+
'application/vnd.jupyter.chat.components': 'grouped-tool-calls'
|
|
910
1195
|
},
|
|
911
1196
|
metadata: {
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
1197
|
+
toolCalls: [
|
|
1198
|
+
{
|
|
1199
|
+
toolCallId: context.toolCallId,
|
|
1200
|
+
title: `${context.toolName}${context.summary ? ' : ' + context.summary : ''}`,
|
|
1201
|
+
kind: context.toolName,
|
|
1202
|
+
status: context.status,
|
|
1203
|
+
rawInput: context.input,
|
|
1204
|
+
rawOutput: output,
|
|
1205
|
+
sessionId: this.name,
|
|
1206
|
+
permissionStatus:
|
|
1207
|
+
status === 'awaiting_approval' ? 'pending' : 'resolved',
|
|
1208
|
+
...(status === 'awaiting_approval' && {
|
|
1209
|
+
permissionOptions: [
|
|
1210
|
+
{ optionId: 'approve', name: 'Approve', kind: 'allow_once' },
|
|
1211
|
+
{ optionId: 'reject', name: 'Reject', kind: 'reject_once' }
|
|
1212
|
+
]
|
|
1213
|
+
})
|
|
1214
|
+
}
|
|
1215
|
+
]
|
|
919
1216
|
}
|
|
920
1217
|
}
|
|
921
1218
|
});
|
|
922
1219
|
}
|
|
923
1220
|
|
|
1221
|
+
/**
|
|
1222
|
+
* The current message queue
|
|
1223
|
+
*/
|
|
1224
|
+
get messageQueue(): Private.IQueuedItem[] {
|
|
1225
|
+
return this._messageQueue;
|
|
1226
|
+
}
|
|
1227
|
+
set messageQueue(value: Private.IQueuedItem[]) {
|
|
1228
|
+
this._messageQueue = value;
|
|
1229
|
+
this._updateQueueUI();
|
|
1230
|
+
if (this._messageQueue.length > 0 && !this._isBusy) {
|
|
1231
|
+
this._drainQueue();
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
/**
|
|
1236
|
+
* Whether the chat is busy
|
|
1237
|
+
*/
|
|
1238
|
+
get isBusy(): boolean {
|
|
1239
|
+
return this._isBusy;
|
|
1240
|
+
}
|
|
1241
|
+
set isBusy(value: boolean) {
|
|
1242
|
+
this._isBusy = value;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
924
1245
|
// Private fields
|
|
925
1246
|
private _settingsModel: IAISettingsModel;
|
|
926
1247
|
private _user: IUser;
|
|
927
1248
|
private _toolContexts: Map<string, IToolExecutionContext> = new Map();
|
|
928
1249
|
private _agentManager: IAgentManager;
|
|
1250
|
+
private _providerRegistry?: IProviderRegistry;
|
|
1251
|
+
private _currentModelKey: string | undefined;
|
|
929
1252
|
private _currentStreamingMessage: IMessage | null = null;
|
|
930
|
-
private _nameChanged = new Signal<
|
|
1253
|
+
private _nameChanged = new Signal<IAIChatModel, string>(this);
|
|
931
1254
|
private _contentsManager?: Contents.IManager;
|
|
932
1255
|
private _autosave: boolean = false;
|
|
933
|
-
private _autosaveChanged = new Signal<
|
|
1256
|
+
private _autosaveChanged = new Signal<IAIChatModel, boolean>(this);
|
|
934
1257
|
private _autosaveDebouncer: Debouncer;
|
|
1258
|
+
private _messageQueue: Private.IQueuedItem[] = [];
|
|
1259
|
+
private _isBusy: boolean = false;
|
|
1260
|
+
private _queueMessageId: string | null = null;
|
|
935
1261
|
private _title: string | null = null;
|
|
936
|
-
private _titleChanged = new Signal<
|
|
1262
|
+
private _titleChanged = new Signal<IAIChatModel, string | null>(this);
|
|
937
1263
|
}
|
|
938
1264
|
|
|
939
1265
|
namespace Private {
|
|
1266
|
+
export interface IQueuedItem {
|
|
1267
|
+
id: string;
|
|
1268
|
+
body: string;
|
|
1269
|
+
_originalMsg: IMessageContent;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
940
1272
|
type IDisplayOutput =
|
|
941
1273
|
| nbformat.IDisplayData
|
|
942
1274
|
| nbformat.IDisplayUpdate
|
|
943
1275
|
| nbformat.IExecuteResult;
|
|
944
1276
|
|
|
945
|
-
const isPlainObject = (
|
|
1277
|
+
export const isPlainObject = (
|
|
1278
|
+
value: unknown
|
|
1279
|
+
): value is Record<string, unknown> => {
|
|
946
1280
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
947
1281
|
};
|
|
948
1282
|
|
|
949
|
-
const isDisplayOutput = (value: unknown): value is IDisplayOutput => {
|
|
1283
|
+
export const isDisplayOutput = (value: unknown): value is IDisplayOutput => {
|
|
950
1284
|
if (!isPlainObject(value)) {
|
|
951
1285
|
return false;
|
|
952
1286
|
}
|
|
@@ -959,7 +1293,7 @@ namespace Private {
|
|
|
959
1293
|
);
|
|
960
1294
|
};
|
|
961
1295
|
|
|
962
|
-
const toMimeBundle = (
|
|
1296
|
+
export const toMimeBundle = (
|
|
963
1297
|
value: IDisplayOutput,
|
|
964
1298
|
trustedMimeTypes: ReadonlySet<string>
|
|
965
1299
|
): IMimeModelBody | null => {
|
|
@@ -987,7 +1321,7 @@ namespace Private {
|
|
|
987
1321
|
* Tool outputs are not guaranteed to be raw Jupyter IOPub messages; they are
|
|
988
1322
|
* often wrapped objects (for example `{ success, result: { outputs: [...] } }`).
|
|
989
1323
|
*/
|
|
990
|
-
const toDisplayOutputs = (value: unknown): IDisplayOutput[] => {
|
|
1324
|
+
export const toDisplayOutputs = (value: unknown): IDisplayOutput[] => {
|
|
991
1325
|
if (isDisplayOutput(value)) {
|
|
992
1326
|
return [value];
|
|
993
1327
|
}
|
|
@@ -1043,23 +1377,29 @@ namespace Private {
|
|
|
1043
1377
|
}
|
|
1044
1378
|
|
|
1045
1379
|
/**
|
|
1046
|
-
* Processes file attachments and returns
|
|
1380
|
+
* Processes file attachments and returns the message content with the attachments.
|
|
1047
1381
|
* @param attachments Array of file attachments to process
|
|
1048
1382
|
* @param documentManager Optional document manager for file operations
|
|
1049
|
-
* @
|
|
1383
|
+
* @param body The message body
|
|
1384
|
+
* @param supportsImages Whether the model supports images
|
|
1385
|
+
* @param supportsPdf Whether the model supports pdfs
|
|
1386
|
+
* @param supportsAudio Whether the model supports audio
|
|
1387
|
+
* @returns Enhanced message content
|
|
1050
1388
|
*/
|
|
1051
1389
|
export async function processAttachments(
|
|
1052
1390
|
attachments: IAttachment[],
|
|
1053
|
-
documentManager: IDocumentManager | null | undefined
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1391
|
+
documentManager: IDocumentManager | null | undefined,
|
|
1392
|
+
body: string,
|
|
1393
|
+
supportsImages: boolean,
|
|
1394
|
+
supportsPdf: boolean,
|
|
1395
|
+
supportsAudio: boolean
|
|
1396
|
+
): Promise<UserContent> {
|
|
1058
1397
|
const textContents: string[] = [];
|
|
1059
|
-
const
|
|
1398
|
+
const includedParts: Array<ImagePart | FilePart> = [];
|
|
1399
|
+
const omittedNames: string[] = [];
|
|
1060
1400
|
|
|
1061
1401
|
if (!documentManager) {
|
|
1062
|
-
return
|
|
1402
|
+
return body;
|
|
1063
1403
|
}
|
|
1064
1404
|
|
|
1065
1405
|
for (const attachment of attachments) {
|
|
@@ -1093,29 +1433,54 @@ namespace Private {
|
|
|
1093
1433
|
}
|
|
1094
1434
|
|
|
1095
1435
|
if (mimetype?.startsWith('image/')) {
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1436
|
+
if (supportsImages) {
|
|
1437
|
+
const data = await readBinaryAttachment(
|
|
1438
|
+
attachment,
|
|
1439
|
+
documentManager
|
|
1440
|
+
);
|
|
1441
|
+
if (data) {
|
|
1442
|
+
includedParts.push({
|
|
1443
|
+
type: 'image',
|
|
1444
|
+
image: data,
|
|
1445
|
+
mediaType: mimetype
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
} else {
|
|
1449
|
+
omittedNames.push(PathExt.basename(attachment.value));
|
|
1106
1450
|
}
|
|
1107
1451
|
} else if (mimetype === 'application/pdf') {
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1452
|
+
if (supportsPdf) {
|
|
1453
|
+
const data = await readBinaryAttachment(
|
|
1454
|
+
attachment,
|
|
1455
|
+
documentManager
|
|
1456
|
+
);
|
|
1457
|
+
if (data) {
|
|
1458
|
+
includedParts.push({
|
|
1459
|
+
type: 'file',
|
|
1460
|
+
data,
|
|
1461
|
+
mediaType: mimetype,
|
|
1462
|
+
filename: PathExt.basename(attachment.value)
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
} else {
|
|
1466
|
+
omittedNames.push(PathExt.basename(attachment.value));
|
|
1467
|
+
}
|
|
1468
|
+
} else if (mimetype?.startsWith('audio/')) {
|
|
1469
|
+
if (supportsAudio) {
|
|
1470
|
+
const data = await readBinaryAttachment(
|
|
1471
|
+
attachment,
|
|
1472
|
+
documentManager
|
|
1473
|
+
);
|
|
1474
|
+
if (data) {
|
|
1475
|
+
includedParts.push({
|
|
1476
|
+
type: 'file',
|
|
1477
|
+
data,
|
|
1478
|
+
mediaType: mimetype,
|
|
1479
|
+
filename: PathExt.basename(attachment.value)
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
} else {
|
|
1483
|
+
omittedNames.push(PathExt.basename(attachment.value));
|
|
1119
1484
|
}
|
|
1120
1485
|
} else {
|
|
1121
1486
|
const fileContent = await readFileAttachment(
|
|
@@ -1142,7 +1507,18 @@ namespace Private {
|
|
|
1142
1507
|
}
|
|
1143
1508
|
}
|
|
1144
1509
|
|
|
1145
|
-
|
|
1510
|
+
let textPart = body;
|
|
1511
|
+
if (textContents.length > 0) {
|
|
1512
|
+
textPart += '\n\n--- Attached Files ---\n' + textContents.join('\n\n');
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
if (omittedNames.length > 0) {
|
|
1516
|
+
textPart += `\n[Attachments omitted (not supported by this model): ${omittedNames.join(', ')}.]`;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
return includedParts.length > 0
|
|
1520
|
+
? [{ type: 'text', text: textPart }, ...includedParts]
|
|
1521
|
+
: textPart;
|
|
1146
1522
|
}
|
|
1147
1523
|
|
|
1148
1524
|
/**
|
|
@@ -1462,6 +1838,10 @@ export namespace AIChatModel {
|
|
|
1462
1838
|
* The contents manager.
|
|
1463
1839
|
*/
|
|
1464
1840
|
contentsManager?: Contents.IManager;
|
|
1841
|
+
/**
|
|
1842
|
+
* Optional provider registry for model capability lookups.
|
|
1843
|
+
*/
|
|
1844
|
+
providerRegistry?: IProviderRegistry;
|
|
1465
1845
|
/**
|
|
1466
1846
|
* Whether to restore or not the message (default to true)
|
|
1467
1847
|
*/
|
|
@@ -1488,6 +1868,18 @@ export namespace AIChatModel {
|
|
|
1488
1868
|
* The agent manager of the chat.
|
|
1489
1869
|
*/
|
|
1490
1870
|
agentManager: IAgentManager;
|
|
1871
|
+
/**
|
|
1872
|
+
* Removes a queued message by its ID.
|
|
1873
|
+
*/
|
|
1874
|
+
removeQueuedMessage: (id: string) => void;
|
|
1875
|
+
/**
|
|
1876
|
+
* Reorders queued messages by their IDs.
|
|
1877
|
+
*/
|
|
1878
|
+
reorderQueuedMessages: (ids: string[]) => void;
|
|
1879
|
+
/**
|
|
1880
|
+
* Edits a queued message by its ID.
|
|
1881
|
+
*/
|
|
1882
|
+
editQueuedMessage: (id: string, body: string) => void;
|
|
1491
1883
|
}
|
|
1492
1884
|
|
|
1493
1885
|
/**
|