@jupyterlite/ai 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/agent.d.ts +12 -2
- package/lib/agent.js +112 -17
- package/lib/chat-commands/clear.js +1 -1
- package/lib/chat-model-handler.js +4 -1
- package/lib/chat-model.d.ts +25 -24
- package/lib/chat-model.js +262 -132
- package/lib/components/clear-button.d.ts +1 -1
- package/lib/components/clear-button.js +1 -1
- package/lib/components/index.d.ts +1 -1
- package/lib/components/index.js +1 -1
- package/lib/components/{token-usage-display.d.ts → usage-display.d.ts} +11 -11
- package/lib/components/usage-display.js +109 -0
- package/lib/index.js +205 -20
- package/lib/models/settings-model.js +1 -0
- package/lib/providers/built-in-providers.js +5 -0
- package/lib/providers/generated-context-windows.d.ts +8 -0
- package/lib/providers/generated-context-windows.js +96 -0
- package/lib/providers/model-info.d.ts +3 -0
- package/lib/providers/model-info.js +58 -0
- package/lib/tokens.d.ts +34 -3
- package/lib/tokens.js +8 -7
- package/lib/widgets/ai-settings.js +9 -0
- package/lib/widgets/main-area-chat.d.ts +1 -0
- package/lib/widgets/main-area-chat.js +10 -4
- package/lib/widgets/provider-config-dialog.js +18 -5
- package/package.json +3 -2
- package/schema/settings-model.json +11 -0
- package/src/agent.ts +151 -21
- package/src/chat-commands/clear.ts +1 -1
- package/src/chat-model-handler.ts +6 -1
- package/src/chat-model.ts +350 -175
- package/src/components/clear-button.tsx +3 -3
- package/src/components/index.ts +1 -1
- package/src/components/usage-display.tsx +208 -0
- package/src/index.ts +250 -26
- package/src/models/settings-model.ts +1 -0
- package/src/providers/built-in-providers.ts +5 -0
- package/src/providers/generated-context-windows.ts +102 -0
- package/src/providers/model-info.ts +88 -0
- package/src/tokens.ts +46 -10
- package/src/widgets/ai-settings.tsx +42 -0
- package/src/widgets/main-area-chat.ts +12 -4
- package/src/widgets/provider-config-dialog.tsx +45 -5
- package/lib/components/token-usage-display.js +0 -72
- package/src/components/token-usage-display.tsx +0 -137
package/lib/agent.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IMessageContent } from '@jupyter/chat';
|
|
2
2
|
import { ISignal } from '@lumino/signaling';
|
|
3
|
+
import { type ModelMessage, type UserContent } from 'ai';
|
|
3
4
|
import { ISecretsManager } from 'jupyter-secrets-manager';
|
|
4
5
|
import { type IAgentManager, type IAgentManagerFactory, type IAISettingsModel, type ISkillRegistry, type ITokenUsage, type ToolMap } from './tokens';
|
|
5
6
|
/**
|
|
@@ -158,11 +159,20 @@ export declare class AgentManager implements IAgentManager {
|
|
|
158
159
|
* Handles the complete execution cycle including tool calls.
|
|
159
160
|
* @param message The user message to respond to (may include processed attachment content)
|
|
160
161
|
*/
|
|
161
|
-
generateResponse(message:
|
|
162
|
+
generateResponse(message: UserContent): Promise<void>;
|
|
162
163
|
/**
|
|
163
|
-
*
|
|
164
|
+
* Create a transient language model to request a text response which won't be added to history.
|
|
165
|
+
* @param messages - the messages sequence to send to the model.
|
|
166
|
+
*/
|
|
167
|
+
textResponse(messages: ModelMessage[]): Promise<string>;
|
|
168
|
+
/**
|
|
169
|
+
* Updates cumulative token usage statistics from a completed model step.
|
|
164
170
|
*/
|
|
165
171
|
private _updateTokenUsage;
|
|
172
|
+
/**
|
|
173
|
+
* Gets the configured context window for the active provider.
|
|
174
|
+
*/
|
|
175
|
+
private _getActiveContextWindow;
|
|
166
176
|
/**
|
|
167
177
|
* Initializes the AI agent with current settings and tools.
|
|
168
178
|
* Sets up the agent with model configuration, tools, and MCP tools.
|
package/lib/agent.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createMCPClient } from '@ai-sdk/mcp';
|
|
2
2
|
import { PromiseDelegate } from '@lumino/coreutils';
|
|
3
3
|
import { Signal } from '@lumino/signaling';
|
|
4
|
-
import { ToolLoopAgent, stepCountIs } from 'ai';
|
|
4
|
+
import { generateText, ToolLoopAgent, stepCountIs, APICallError } from 'ai';
|
|
5
5
|
import { createModel } from './providers/models';
|
|
6
|
+
import { getEffectiveContextWindow } from './providers/model-info';
|
|
6
7
|
import { createProviderTools } from './providers/provider-tools';
|
|
7
8
|
import { SECRETS_NAMESPACE } from './tokens';
|
|
8
9
|
/**
|
|
@@ -256,7 +257,14 @@ export class AgentManager {
|
|
|
256
257
|
return this._activeProvider;
|
|
257
258
|
}
|
|
258
259
|
set activeProvider(value) {
|
|
260
|
+
const previousProvider = this._activeProvider;
|
|
259
261
|
this._activeProvider = value;
|
|
262
|
+
// Reset request-level context estimate only when switching between providers.
|
|
263
|
+
if (previousProvider && previousProvider !== value) {
|
|
264
|
+
this._tokenUsage.lastRequestInputTokens = undefined;
|
|
265
|
+
}
|
|
266
|
+
this._tokenUsage.contextWindow = this._getActiveContextWindow();
|
|
267
|
+
this._tokenUsageChanged.emit(this._tokenUsage);
|
|
260
268
|
this.initializeAgent();
|
|
261
269
|
this._activeProviderChanged.emit(this._activeProvider);
|
|
262
270
|
}
|
|
@@ -315,7 +323,11 @@ export class AgentManager {
|
|
|
315
323
|
await this._streaming.promise;
|
|
316
324
|
// Clear history and token usage
|
|
317
325
|
this._history = [];
|
|
318
|
-
this._tokenUsage = {
|
|
326
|
+
this._tokenUsage = {
|
|
327
|
+
inputTokens: 0,
|
|
328
|
+
outputTokens: 0,
|
|
329
|
+
contextWindow: this._getActiveContextWindow()
|
|
330
|
+
};
|
|
319
331
|
this._tokenUsageChanged.emit(this._tokenUsage);
|
|
320
332
|
}
|
|
321
333
|
/**
|
|
@@ -335,9 +347,9 @@ export class AgentManager {
|
|
|
335
347
|
this._pendingApprovals.clear();
|
|
336
348
|
// Convert chat messages to model messages
|
|
337
349
|
const modelMessages = messages.map(msg => {
|
|
338
|
-
const
|
|
350
|
+
const role = msg.sender.username === 'ai-assistant' ? 'assistant' : 'user';
|
|
339
351
|
return {
|
|
340
|
-
role
|
|
352
|
+
role,
|
|
341
353
|
content: msg.body
|
|
342
354
|
};
|
|
343
355
|
});
|
|
@@ -400,6 +412,11 @@ export class AgentManager {
|
|
|
400
412
|
this._streaming = new PromiseDelegate();
|
|
401
413
|
this._controller = new AbortController();
|
|
402
414
|
const responseHistory = [];
|
|
415
|
+
// Add user message to history
|
|
416
|
+
responseHistory.push({
|
|
417
|
+
role: 'user',
|
|
418
|
+
content: message
|
|
419
|
+
});
|
|
403
420
|
try {
|
|
404
421
|
// Ensure we have an agent
|
|
405
422
|
if (!this._agent) {
|
|
@@ -408,11 +425,6 @@ export class AgentManager {
|
|
|
408
425
|
if (!this._agent) {
|
|
409
426
|
throw new Error('Failed to initialize agent');
|
|
410
427
|
}
|
|
411
|
-
// Add user message to history
|
|
412
|
-
responseHistory.push({
|
|
413
|
-
role: 'user',
|
|
414
|
-
content: message
|
|
415
|
-
});
|
|
416
428
|
let continueLoop = true;
|
|
417
429
|
while (continueLoop) {
|
|
418
430
|
const result = await this._agent.stream({
|
|
@@ -420,9 +432,20 @@ export class AgentManager {
|
|
|
420
432
|
abortSignal: this._controller.signal
|
|
421
433
|
});
|
|
422
434
|
const streamResult = await this._processStreamResult(result);
|
|
423
|
-
|
|
435
|
+
if (streamResult.aborted) {
|
|
436
|
+
try {
|
|
437
|
+
const responseMessages = await result.response;
|
|
438
|
+
if (responseMessages.messages?.length) {
|
|
439
|
+
this._history.push(...Private.sanitizeModelMessages(responseMessages.messages));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
catch {
|
|
443
|
+
// Aborting before a step finishes leaves no completed response to persist.
|
|
444
|
+
}
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
447
|
+
// Get response messages for completed steps.
|
|
424
448
|
const responseMessages = await result.response;
|
|
425
|
-
this._updateTokenUsage(await result.usage);
|
|
426
449
|
// Add response messages to history
|
|
427
450
|
if (responseMessages.messages?.length) {
|
|
428
451
|
responseHistory.push(...responseMessages.messages);
|
|
@@ -450,9 +473,38 @@ export class AgentManager {
|
|
|
450
473
|
}
|
|
451
474
|
catch (error) {
|
|
452
475
|
if (error.name !== 'AbortError') {
|
|
476
|
+
let helpMessage = `${error.message}`;
|
|
477
|
+
// Remove attachments from history on payload rejection errors
|
|
478
|
+
if (APICallError.isInstance(error) &&
|
|
479
|
+
(error.statusCode === 400 ||
|
|
480
|
+
error.statusCode === 404 ||
|
|
481
|
+
error.statusCode === 413 ||
|
|
482
|
+
error.statusCode === 415 ||
|
|
483
|
+
error.statusCode === 422)) {
|
|
484
|
+
for (const msg of [...this._history, ...responseHistory]) {
|
|
485
|
+
if (msg.role === 'user' && Array.isArray(msg.content)) {
|
|
486
|
+
const hasMedia = msg.content.some(p => p.type !== 'text');
|
|
487
|
+
if (hasMedia) {
|
|
488
|
+
const textContent = msg.content
|
|
489
|
+
.filter(p => p.type === 'text')
|
|
490
|
+
.map(p => p.text)
|
|
491
|
+
.join('\n');
|
|
492
|
+
msg.content =
|
|
493
|
+
textContent || '_Attachment removed due to error_';
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
helpMessage +=
|
|
498
|
+
'\n\nAttachments have been removed from history. Please send your prompt again.';
|
|
499
|
+
}
|
|
453
500
|
this._agentEvent.emit({
|
|
454
501
|
type: 'error',
|
|
455
|
-
data: { error:
|
|
502
|
+
data: { error: new Error(helpMessage) }
|
|
503
|
+
});
|
|
504
|
+
this._history.push(...Private.sanitizeModelMessages(responseHistory));
|
|
505
|
+
this._history.push({
|
|
506
|
+
role: 'assistant',
|
|
507
|
+
content: helpMessage
|
|
456
508
|
});
|
|
457
509
|
}
|
|
458
510
|
}
|
|
@@ -462,14 +514,43 @@ export class AgentManager {
|
|
|
462
514
|
}
|
|
463
515
|
}
|
|
464
516
|
/**
|
|
465
|
-
*
|
|
517
|
+
* Create a transient language model to request a text response which won't be added to history.
|
|
518
|
+
* @param messages - the messages sequence to send to the model.
|
|
466
519
|
*/
|
|
467
|
-
|
|
520
|
+
async textResponse(messages) {
|
|
521
|
+
try {
|
|
522
|
+
const model = await this._createModel();
|
|
523
|
+
const result = await generateText({
|
|
524
|
+
model,
|
|
525
|
+
messages
|
|
526
|
+
});
|
|
527
|
+
this._updateTokenUsage(result.totalUsage, result.totalUsage.inputTokens);
|
|
528
|
+
return result.text;
|
|
529
|
+
}
|
|
530
|
+
catch (e) {
|
|
531
|
+
throw `Error while getting the topic of the chat\n${e}`;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Updates cumulative token usage statistics from a completed model step.
|
|
536
|
+
*/
|
|
537
|
+
_updateTokenUsage(usage, lastRequestInputTokens) {
|
|
538
|
+
const contextWindow = this._getActiveContextWindow();
|
|
539
|
+
const estimatedRequestInputTokens = lastRequestInputTokens ?? usage?.inputTokens;
|
|
468
540
|
if (usage) {
|
|
469
541
|
this._tokenUsage.inputTokens += usage.inputTokens ?? 0;
|
|
470
542
|
this._tokenUsage.outputTokens += usage.outputTokens ?? 0;
|
|
471
|
-
this._tokenUsageChanged.emit(this._tokenUsage);
|
|
472
543
|
}
|
|
544
|
+
this._tokenUsage.lastRequestInputTokens = estimatedRequestInputTokens;
|
|
545
|
+
this._tokenUsage.contextWindow = contextWindow;
|
|
546
|
+
this._tokenUsageChanged.emit(this._tokenUsage);
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Gets the configured context window for the active provider.
|
|
550
|
+
*/
|
|
551
|
+
_getActiveContextWindow() {
|
|
552
|
+
const activeProviderConfig = this._settingsModel.getProvider(this._activeProvider);
|
|
553
|
+
return getEffectiveContextWindow(activeProviderConfig, this._providerRegistry);
|
|
473
554
|
}
|
|
474
555
|
/**
|
|
475
556
|
* Initializes the AI agent with current settings and tools.
|
|
@@ -521,6 +602,9 @@ export class AgentManager {
|
|
|
521
602
|
const activeProviderInfo = activeProviderConfig && this._providerRegistry
|
|
522
603
|
? this._providerRegistry.getProviderInfo(activeProviderConfig.provider)
|
|
523
604
|
: null;
|
|
605
|
+
const contextWindow = getEffectiveContextWindow(activeProviderConfig, this._providerRegistry);
|
|
606
|
+
this._tokenUsage.contextWindow = contextWindow;
|
|
607
|
+
this._tokenUsageChanged.emit(this._tokenUsage);
|
|
524
608
|
const temperature = activeProviderConfig?.parameters?.temperature ?? DEFAULT_TEMPERATURE;
|
|
525
609
|
const maxTokens = activeProviderConfig?.parameters?.maxOutputTokens;
|
|
526
610
|
const maxTurns = activeProviderConfig?.parameters?.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
@@ -599,7 +683,10 @@ ${richOutputWorkflowInstruction}`;
|
|
|
599
683
|
async _processStreamResult(result) {
|
|
600
684
|
let fullResponse = '';
|
|
601
685
|
let currentMessageId = null;
|
|
602
|
-
const processResult = {
|
|
686
|
+
const processResult = {
|
|
687
|
+
approvalProcessed: false,
|
|
688
|
+
aborted: false
|
|
689
|
+
};
|
|
603
690
|
for await (const part of result.fullStream) {
|
|
604
691
|
switch (part.type) {
|
|
605
692
|
case 'text-delta':
|
|
@@ -654,7 +741,15 @@ ${richOutputWorkflowInstruction}`;
|
|
|
654
741
|
}
|
|
655
742
|
await this._handleApprovalRequest(part, processResult);
|
|
656
743
|
break;
|
|
657
|
-
|
|
744
|
+
case 'error':
|
|
745
|
+
throw part.error;
|
|
746
|
+
case 'finish-step':
|
|
747
|
+
this._updateTokenUsage(part.usage, part.usage.inputTokens);
|
|
748
|
+
break;
|
|
749
|
+
case 'abort':
|
|
750
|
+
processResult.aborted = true;
|
|
751
|
+
break;
|
|
752
|
+
// Ignore: text-start, text-end, finish, and others
|
|
658
753
|
default:
|
|
659
754
|
break;
|
|
660
755
|
}
|
|
@@ -14,7 +14,7 @@ export class ChatModelHandler {
|
|
|
14
14
|
this._contentsManager = options.contentsManager;
|
|
15
15
|
}
|
|
16
16
|
createModel(options) {
|
|
17
|
-
const { name, activeProvider, tokenUsage, messages, autosave } = options;
|
|
17
|
+
const { name, activeProvider, tokenUsage, messages, autosave, title } = options;
|
|
18
18
|
// Create Agent Manager first so it can be shared
|
|
19
19
|
const agentManager = this._agentManagerFactory.createAgent({
|
|
20
20
|
settingsModel: this._settingsModel,
|
|
@@ -38,6 +38,9 @@ export class ChatModelHandler {
|
|
|
38
38
|
});
|
|
39
39
|
model.autosave = autosave ?? false;
|
|
40
40
|
model.name = name;
|
|
41
|
+
if (title) {
|
|
42
|
+
model.title = title;
|
|
43
|
+
}
|
|
41
44
|
return model;
|
|
42
45
|
}
|
|
43
46
|
/**
|
package/lib/chat-model.d.ts
CHANGED
|
@@ -18,6 +18,19 @@ export declare class AIChatModel extends AbstractChatModel {
|
|
|
18
18
|
*/
|
|
19
19
|
get name(): string;
|
|
20
20
|
set name(value: string);
|
|
21
|
+
/**
|
|
22
|
+
* A signal emitting when the chat name has changed.
|
|
23
|
+
*/
|
|
24
|
+
get nameChanged(): ISignal<AIChatModel, string>;
|
|
25
|
+
/**
|
|
26
|
+
* The title of the chat.
|
|
27
|
+
*/
|
|
28
|
+
get title(): string | null;
|
|
29
|
+
set title(value: string | null);
|
|
30
|
+
/**
|
|
31
|
+
* A signal emitting when the chat title has changed.
|
|
32
|
+
*/
|
|
33
|
+
get titleChanged(): ISignal<AIChatModel, string | null>;
|
|
21
34
|
/**
|
|
22
35
|
* Whether to save the chat automatically.
|
|
23
36
|
*/
|
|
@@ -27,10 +40,6 @@ export declare class AIChatModel extends AbstractChatModel {
|
|
|
27
40
|
* A signal emitting when the autosave flag changed.
|
|
28
41
|
*/
|
|
29
42
|
get autosaveChanged(): ISignal<AIChatModel, boolean>;
|
|
30
|
-
/**
|
|
31
|
-
* A signal emitting when the chat name has changed.
|
|
32
|
-
*/
|
|
33
|
-
get nameChanged(): ISignal<AIChatModel, string>;
|
|
34
43
|
/**
|
|
35
44
|
* Gets the current user information.
|
|
36
45
|
*/
|
|
@@ -62,7 +71,7 @@ export declare class AIChatModel extends AbstractChatModel {
|
|
|
62
71
|
/**
|
|
63
72
|
* Clears all messages from the chat and resets conversation state.
|
|
64
73
|
*/
|
|
65
|
-
clearMessages: () => void
|
|
74
|
+
clearMessages: () => Promise<void>;
|
|
66
75
|
/**
|
|
67
76
|
* Adds a non-user message to the chat (used by chat commands).
|
|
68
77
|
*/
|
|
@@ -83,6 +92,10 @@ export declare class AIChatModel extends AbstractChatModel {
|
|
|
83
92
|
* restoration is not possible.
|
|
84
93
|
*/
|
|
85
94
|
restore: (filepath: string, silent?: boolean) => Promise<boolean>;
|
|
95
|
+
/**
|
|
96
|
+
* Request a title to this chat, regarding the message history.
|
|
97
|
+
*/
|
|
98
|
+
requestTitle(): Promise<string>;
|
|
86
99
|
/**
|
|
87
100
|
* Serialize the model for backup
|
|
88
101
|
*/
|
|
@@ -155,24 +168,6 @@ export declare class AIChatModel extends AbstractChatModel {
|
|
|
155
168
|
* Updates a tool call's UI with new status and optional output.
|
|
156
169
|
*/
|
|
157
170
|
private _updateToolCallUI;
|
|
158
|
-
/**
|
|
159
|
-
* Processes file attachments and returns their content as formatted strings.
|
|
160
|
-
* @param attachments Array of file attachments to process
|
|
161
|
-
* @returns Array of formatted attachment contents
|
|
162
|
-
*/
|
|
163
|
-
private _processAttachments;
|
|
164
|
-
/**
|
|
165
|
-
* Reads the content of a notebook cell.
|
|
166
|
-
* @param attachment The notebook attachment to read
|
|
167
|
-
* @returns Cell content as string or null if unable to read
|
|
168
|
-
*/
|
|
169
|
-
private _readNotebookCells;
|
|
170
|
-
/**
|
|
171
|
-
* Reads the content of a file attachment.
|
|
172
|
-
* @param attachment The file attachment to read
|
|
173
|
-
* @returns File content as string or null if unable to read
|
|
174
|
-
*/
|
|
175
|
-
private _readFileAttachment;
|
|
176
171
|
private _settingsModel;
|
|
177
172
|
private _user;
|
|
178
173
|
private _toolContexts;
|
|
@@ -183,6 +178,8 @@ export declare class AIChatModel extends AbstractChatModel {
|
|
|
183
178
|
private _autosave;
|
|
184
179
|
private _autosaveChanged;
|
|
185
180
|
private _autosaveDebouncer;
|
|
181
|
+
private _title;
|
|
182
|
+
private _titleChanged;
|
|
186
183
|
}
|
|
187
184
|
/**
|
|
188
185
|
* Namespace containing types and interfaces for AIChatModel.
|
|
@@ -232,7 +229,7 @@ export declare namespace AIChatModel {
|
|
|
232
229
|
/**
|
|
233
230
|
* The clear messages callback.
|
|
234
231
|
*/
|
|
235
|
-
clearMessages: () => void
|
|
232
|
+
clearMessages: () => Promise<void>;
|
|
236
233
|
/**
|
|
237
234
|
* Adds an assistant/system message to the chat.
|
|
238
235
|
*/
|
|
@@ -274,6 +271,10 @@ export declare namespace AIChatModel {
|
|
|
274
271
|
* Whether the chat is automatically saved.
|
|
275
272
|
*/
|
|
276
273
|
autosave?: boolean;
|
|
274
|
+
/**
|
|
275
|
+
* An optional title of the chat.
|
|
276
|
+
*/
|
|
277
|
+
title?: string;
|
|
277
278
|
};
|
|
278
279
|
};
|
|
279
280
|
}
|