@jupyterlite/ai 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/agent.d.ts +33 -115
- package/lib/agent.js +192 -106
- package/lib/chat-model-handler.d.ts +9 -11
- package/lib/chat-model-handler.js +9 -4
- package/lib/chat-model.d.ts +84 -13
- package/lib/chat-model.js +214 -136
- package/lib/completion/completion-provider.d.ts +2 -3
- package/lib/components/completion-status.d.ts +2 -2
- package/lib/components/index.d.ts +1 -1
- package/lib/components/index.js +1 -1
- package/lib/components/model-select.d.ts +3 -3
- package/lib/components/save-button.d.ts +31 -0
- package/lib/components/save-button.js +41 -0
- package/lib/components/tool-select.d.ts +3 -4
- package/lib/components/{token-usage-display.d.ts → usage-display.d.ts} +13 -14
- package/lib/components/usage-display.js +109 -0
- package/lib/diff-manager.d.ts +2 -3
- package/lib/index.d.ts +2 -4
- package/lib/index.js +186 -28
- package/lib/models/settings-model.d.ts +11 -53
- package/lib/models/settings-model.js +38 -22
- package/lib/providers/built-in-providers.js +22 -36
- package/lib/providers/generated-context-windows.d.ts +8 -0
- package/lib/providers/generated-context-windows.js +96 -0
- package/lib/providers/model-info.d.ts +3 -0
- package/lib/providers/model-info.js +58 -0
- package/lib/tokens.d.ts +361 -36
- package/lib/tokens.js +18 -13
- package/lib/tools/commands.d.ts +2 -3
- package/lib/widgets/ai-settings.d.ts +3 -5
- package/lib/widgets/ai-settings.js +12 -0
- package/lib/widgets/main-area-chat.d.ts +2 -3
- package/lib/widgets/main-area-chat.js +12 -12
- package/lib/widgets/provider-config-dialog.d.ts +1 -2
- package/lib/widgets/provider-config-dialog.js +34 -34
- package/package.json +17 -10
- package/schema/settings-model.json +18 -1
- package/src/agent.ts +275 -248
- package/src/chat-model-handler.ts +25 -21
- package/src/chat-model.ts +307 -196
- package/src/completion/completion-provider.ts +7 -4
- package/src/components/completion-status.tsx +3 -3
- package/src/components/index.ts +1 -1
- package/src/components/model-select.tsx +4 -3
- package/src/components/save-button.tsx +84 -0
- package/src/components/tool-select.tsx +10 -4
- package/src/components/usage-display.tsx +208 -0
- package/src/diff-manager.ts +4 -4
- package/src/index.ts +250 -58
- package/src/models/settings-model.ts +46 -88
- package/src/providers/built-in-providers.ts +22 -36
- package/src/providers/generated-context-windows.ts +102 -0
- package/src/providers/model-info.ts +88 -0
- package/src/tokens.ts +438 -58
- package/src/tools/commands.ts +2 -3
- package/src/widgets/ai-settings.tsx +69 -15
- package/src/widgets/main-area-chat.ts +18 -15
- package/src/widgets/provider-config-dialog.tsx +96 -61
- package/style/base.css +17 -195
- package/lib/approval-buttons.d.ts +0 -49
- package/lib/approval-buttons.js +0 -79
- package/lib/components/token-usage-display.js +0 -72
- package/src/approval-buttons.ts +0 -115
- package/src/components/token-usage-display.tsx +0 -138
package/lib/agent.d.ts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
+
import type { IMessageContent } from '@jupyter/chat';
|
|
1
2
|
import { ISignal } from '@lumino/signaling';
|
|
2
|
-
import { type Tool } from 'ai';
|
|
3
3
|
import { ISecretsManager } from 'jupyter-secrets-manager';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
type ToolMap = Record<string, Tool>;
|
|
4
|
+
import { type IAgentManager, type IAgentManagerFactory, type IAISettingsModel, type ISkillRegistry, type ITokenUsage, type ToolMap } from './tokens';
|
|
5
|
+
/**
|
|
6
|
+
* The agent manager factory namespace.
|
|
7
|
+
*/
|
|
9
8
|
export declare namespace AgentManagerFactory {
|
|
10
9
|
interface IOptions {
|
|
11
10
|
/**
|
|
12
11
|
* The settings model.
|
|
13
12
|
*/
|
|
14
|
-
settingsModel:
|
|
13
|
+
settingsModel: IAISettingsModel;
|
|
15
14
|
/**
|
|
16
15
|
* The skill registry for discovering skills.
|
|
17
16
|
*/
|
|
@@ -26,15 +25,21 @@ export declare namespace AgentManagerFactory {
|
|
|
26
25
|
token: symbol | null;
|
|
27
26
|
}
|
|
28
27
|
}
|
|
29
|
-
|
|
28
|
+
/**
|
|
29
|
+
* The agent manager factory.
|
|
30
|
+
*/
|
|
31
|
+
export declare class AgentManagerFactory implements IAgentManagerFactory {
|
|
30
32
|
constructor(options: AgentManagerFactory.IOptions);
|
|
31
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Create a new agent.
|
|
35
|
+
*/
|
|
36
|
+
createAgent(options: IAgentManager.IOptions): IAgentManager;
|
|
32
37
|
/**
|
|
33
38
|
* Signal emitted when MCP connection status changes
|
|
34
39
|
*/
|
|
35
40
|
get mcpConnectionChanged(): ISignal<this, boolean>;
|
|
36
41
|
/**
|
|
37
|
-
* Checks
|
|
42
|
+
* Checks whether a specific MCP server is connected.
|
|
38
43
|
* @param serverName The name of the MCP server to check
|
|
39
44
|
* @returns True if the server is connected, false otherwise
|
|
40
45
|
*/
|
|
@@ -69,107 +74,22 @@ export declare class AgentManagerFactory {
|
|
|
69
74
|
private _mcpConnectionChanged;
|
|
70
75
|
private _initQueue;
|
|
71
76
|
}
|
|
72
|
-
/**
|
|
73
|
-
* Event type mapping for type safety with inlined interface definitions
|
|
74
|
-
*/
|
|
75
|
-
export interface IAgentEventTypeMap {
|
|
76
|
-
message_start: {
|
|
77
|
-
messageId: string;
|
|
78
|
-
};
|
|
79
|
-
message_chunk: {
|
|
80
|
-
messageId: string;
|
|
81
|
-
chunk: string;
|
|
82
|
-
fullContent: string;
|
|
83
|
-
};
|
|
84
|
-
message_complete: {
|
|
85
|
-
messageId: string;
|
|
86
|
-
content: string;
|
|
87
|
-
};
|
|
88
|
-
tool_call_start: {
|
|
89
|
-
callId: string;
|
|
90
|
-
toolName: string;
|
|
91
|
-
input: string;
|
|
92
|
-
};
|
|
93
|
-
tool_call_complete: {
|
|
94
|
-
callId: string;
|
|
95
|
-
toolName: string;
|
|
96
|
-
outputData: unknown;
|
|
97
|
-
isError: boolean;
|
|
98
|
-
};
|
|
99
|
-
tool_approval_request: {
|
|
100
|
-
approvalId: string;
|
|
101
|
-
toolCallId: string;
|
|
102
|
-
toolName: string;
|
|
103
|
-
args: unknown;
|
|
104
|
-
};
|
|
105
|
-
tool_approval_resolved: {
|
|
106
|
-
approvalId: string;
|
|
107
|
-
approved: boolean;
|
|
108
|
-
};
|
|
109
|
-
error: {
|
|
110
|
-
error: Error;
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Events emitted by the AgentManager
|
|
115
|
-
*/
|
|
116
|
-
export type IAgentEvent<T extends keyof IAgentEventTypeMap = keyof IAgentEventTypeMap> = T extends keyof IAgentEventTypeMap ? {
|
|
117
|
-
type: T;
|
|
118
|
-
data: IAgentEventTypeMap[T];
|
|
119
|
-
} : never;
|
|
120
|
-
/**
|
|
121
|
-
* Configuration options for the AgentManager
|
|
122
|
-
*/
|
|
123
|
-
export interface IAgentManagerOptions {
|
|
124
|
-
/**
|
|
125
|
-
* AI settings model for configuration
|
|
126
|
-
*/
|
|
127
|
-
settingsModel: AISettingsModel;
|
|
128
|
-
/**
|
|
129
|
-
* Optional tool registry for managing available tools
|
|
130
|
-
*/
|
|
131
|
-
toolRegistry?: IToolRegistry;
|
|
132
|
-
/**
|
|
133
|
-
* Optional provider registry for model creation
|
|
134
|
-
*/
|
|
135
|
-
providerRegistry?: IProviderRegistry;
|
|
136
|
-
/**
|
|
137
|
-
* The skill registry for discovering skills.
|
|
138
|
-
*/
|
|
139
|
-
skillRegistry?: ISkillRegistry;
|
|
140
|
-
/**
|
|
141
|
-
* The secrets manager.
|
|
142
|
-
*/
|
|
143
|
-
secretsManager?: ISecretsManager;
|
|
144
|
-
/**
|
|
145
|
-
* The active provider to use with this agent.
|
|
146
|
-
*/
|
|
147
|
-
activeProvider?: string;
|
|
148
|
-
/**
|
|
149
|
-
* Initial token usage.
|
|
150
|
-
*/
|
|
151
|
-
tokenUsage?: ITokenUsage;
|
|
152
|
-
/**
|
|
153
|
-
* JupyterLab render mime registry for discovering supported MIME types.
|
|
154
|
-
*/
|
|
155
|
-
renderMimeRegistry?: IRenderMimeRegistry;
|
|
156
|
-
}
|
|
157
77
|
/**
|
|
158
78
|
* Manages the AI agent lifecycle and execution loop.
|
|
159
79
|
* Provides agent initialization, tool management, MCP server integration,
|
|
160
80
|
* and handles the complete agent execution cycle.
|
|
161
81
|
* Emits events for UI updates instead of directly manipulating the chat interface.
|
|
162
82
|
*/
|
|
163
|
-
export declare class AgentManager {
|
|
83
|
+
export declare class AgentManager implements IAgentManager {
|
|
164
84
|
/**
|
|
165
85
|
* Creates a new AgentManager instance.
|
|
166
86
|
* @param options Configuration options for the agent manager
|
|
167
87
|
*/
|
|
168
|
-
constructor(options:
|
|
88
|
+
constructor(options: IAgentManager.IOptions);
|
|
169
89
|
/**
|
|
170
90
|
* Signal emitted when agent events occur
|
|
171
91
|
*/
|
|
172
|
-
get agentEvent(): ISignal<this, IAgentEvent>;
|
|
92
|
+
get agentEvent(): ISignal<this, IAgentManager.IAgentEvent>;
|
|
173
93
|
/**
|
|
174
94
|
* Signal emitted when the active provider has changed.
|
|
175
95
|
*/
|
|
@@ -200,7 +120,7 @@ export declare class AgentManager {
|
|
|
200
120
|
* Gets the currently selected tools as a record.
|
|
201
121
|
* @returns Record of selected tools
|
|
202
122
|
*/
|
|
203
|
-
get selectedAgentTools():
|
|
123
|
+
get selectedAgentTools(): ToolMap;
|
|
204
124
|
/**
|
|
205
125
|
* Checks if the current configuration is valid for agent operations.
|
|
206
126
|
* Uses the provider registry to determine if an API key is required.
|
|
@@ -210,11 +130,17 @@ export declare class AgentManager {
|
|
|
210
130
|
/**
|
|
211
131
|
* Clears conversation history and resets agent state.
|
|
212
132
|
*/
|
|
213
|
-
clearHistory(): void
|
|
133
|
+
clearHistory(): Promise<void>;
|
|
134
|
+
/**
|
|
135
|
+
* Sets the history with a list of messages from the chat.
|
|
136
|
+
* @param messages The chat messages to set as history
|
|
137
|
+
*/
|
|
138
|
+
setHistory(messages: IMessageContent[]): void;
|
|
214
139
|
/**
|
|
215
140
|
* Stops the current streaming response by aborting the request.
|
|
141
|
+
* Resolve any pending approval.
|
|
216
142
|
*/
|
|
217
|
-
stopStreaming(): void;
|
|
143
|
+
stopStreaming(reason?: string): void;
|
|
218
144
|
/**
|
|
219
145
|
* Approves a pending tool call.
|
|
220
146
|
* @param approvalId The approval ID to approve
|
|
@@ -234,9 +160,13 @@ export declare class AgentManager {
|
|
|
234
160
|
*/
|
|
235
161
|
generateResponse(message: string): Promise<void>;
|
|
236
162
|
/**
|
|
237
|
-
* Updates token usage statistics.
|
|
163
|
+
* Updates cumulative token usage statistics from a completed model step.
|
|
238
164
|
*/
|
|
239
165
|
private _updateTokenUsage;
|
|
166
|
+
/**
|
|
167
|
+
* Gets the configured context window for the active provider.
|
|
168
|
+
*/
|
|
169
|
+
private _getActiveContextWindow;
|
|
240
170
|
/**
|
|
241
171
|
* Initializes the AI agent with current settings and tools.
|
|
242
172
|
* Sets up the agent with model configuration, tools, and MCP tools.
|
|
@@ -317,18 +247,6 @@ export declare class AgentManager {
|
|
|
317
247
|
* Build an instruction line describing MIME types supported by this session.
|
|
318
248
|
*/
|
|
319
249
|
private _getSupportedMimeTypesInstruction;
|
|
320
|
-
/**
|
|
321
|
-
* Sanitizes history to ensure it's in a valid state in case of abort or error.
|
|
322
|
-
*/
|
|
323
|
-
private _sanitizeHistory;
|
|
324
|
-
/**
|
|
325
|
-
* Extracts tool call IDs from a message
|
|
326
|
-
*/
|
|
327
|
-
private _getToolCallIds;
|
|
328
|
-
/**
|
|
329
|
-
* Checks if a tool message contains results for all specified tool call IDs
|
|
330
|
-
*/
|
|
331
|
-
private _matchesAllToolCalls;
|
|
332
250
|
private _settingsModel;
|
|
333
251
|
private _toolRegistry?;
|
|
334
252
|
private _providerRegistry?;
|
|
@@ -349,5 +267,5 @@ export declare class AgentManager {
|
|
|
349
267
|
private _initQueue;
|
|
350
268
|
private _agentConfig;
|
|
351
269
|
private _pendingApprovals;
|
|
270
|
+
private _streaming;
|
|
352
271
|
}
|
|
353
|
-
export {};
|
package/lib/agent.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { createMCPClient } from '@ai-sdk/mcp';
|
|
2
|
+
import { PromiseDelegate } from '@lumino/coreutils';
|
|
1
3
|
import { Signal } from '@lumino/signaling';
|
|
2
4
|
import { ToolLoopAgent, stepCountIs } from 'ai';
|
|
3
|
-
import { createMCPClient } from '@ai-sdk/mcp';
|
|
4
5
|
import { createModel } from './providers/models';
|
|
6
|
+
import { getEffectiveContextWindow } from './providers/model-info';
|
|
5
7
|
import { createProviderTools } from './providers/provider-tools';
|
|
6
8
|
import { SECRETS_NAMESPACE } from './tokens';
|
|
9
|
+
/**
|
|
10
|
+
* The agent manager factory.
|
|
11
|
+
*/
|
|
7
12
|
export class AgentManagerFactory {
|
|
8
13
|
constructor(options) {
|
|
9
14
|
Private.setToken(options.token);
|
|
@@ -26,6 +31,9 @@ export class AgentManagerFactory {
|
|
|
26
31
|
this._secretsManager = undefined;
|
|
27
32
|
}
|
|
28
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Create a new agent.
|
|
36
|
+
*/
|
|
29
37
|
createAgent(options) {
|
|
30
38
|
const agentManager = new AgentManager({
|
|
31
39
|
...options,
|
|
@@ -52,7 +60,7 @@ export class AgentManagerFactory {
|
|
|
52
60
|
return this._mcpConnectionChanged;
|
|
53
61
|
}
|
|
54
62
|
/**
|
|
55
|
-
* Checks
|
|
63
|
+
* Checks whether a specific MCP server is connected.
|
|
56
64
|
* @param serverName The name of the MCP server to check
|
|
57
65
|
* @returns True if the server is connected, false otherwise
|
|
58
66
|
*/
|
|
@@ -196,6 +204,7 @@ export class AgentManager {
|
|
|
196
204
|
this._skills = [];
|
|
197
205
|
this._agentConfig = null;
|
|
198
206
|
this._renderMimeRegistry = options.renderMimeRegistry;
|
|
207
|
+
this._streaming.resolve();
|
|
199
208
|
this.activeProvider =
|
|
200
209
|
options.activeProvider ?? this._settingsModel.config.defaultProvider;
|
|
201
210
|
// Initialize selected tools to all available tools by default
|
|
@@ -248,7 +257,14 @@ export class AgentManager {
|
|
|
248
257
|
return this._activeProvider;
|
|
249
258
|
}
|
|
250
259
|
set activeProvider(value) {
|
|
260
|
+
const previousProvider = this._activeProvider;
|
|
251
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);
|
|
252
268
|
this.initializeAgent();
|
|
253
269
|
this._activeProviderChanged.emit(this._activeProvider);
|
|
254
270
|
}
|
|
@@ -301,28 +317,59 @@ export class AgentManager {
|
|
|
301
317
|
/**
|
|
302
318
|
* Clears conversation history and resets agent state.
|
|
303
319
|
*/
|
|
304
|
-
clearHistory() {
|
|
320
|
+
async clearHistory() {
|
|
305
321
|
// Stop any ongoing streaming
|
|
322
|
+
this.stopStreaming('Chat cleared');
|
|
323
|
+
await this._streaming.promise;
|
|
324
|
+
// Clear history and token usage
|
|
325
|
+
this._history = [];
|
|
326
|
+
this._tokenUsage = {
|
|
327
|
+
inputTokens: 0,
|
|
328
|
+
outputTokens: 0,
|
|
329
|
+
contextWindow: this._getActiveContextWindow()
|
|
330
|
+
};
|
|
331
|
+
this._tokenUsageChanged.emit(this._tokenUsage);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Sets the history with a list of messages from the chat.
|
|
335
|
+
* @param messages The chat messages to set as history
|
|
336
|
+
*/
|
|
337
|
+
setHistory(messages) {
|
|
338
|
+
// Stop any ongoing streaming and reject awaiting approvals
|
|
306
339
|
this.stopStreaming();
|
|
307
|
-
// Reject any pending approvals
|
|
308
340
|
for (const [approvalId, pending] of this._pendingApprovals) {
|
|
309
|
-
pending.resolve(false, 'Chat
|
|
341
|
+
pending.resolve(false, 'Chat history changed');
|
|
310
342
|
this._agentEvent.emit({
|
|
311
343
|
type: 'tool_approval_resolved',
|
|
312
344
|
data: { approvalId, approved: false }
|
|
313
345
|
});
|
|
314
346
|
}
|
|
315
347
|
this._pendingApprovals.clear();
|
|
316
|
-
//
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
348
|
+
// Convert chat messages to model messages
|
|
349
|
+
const modelMessages = messages.map(msg => {
|
|
350
|
+
const isAIMessage = msg.sender.username === 'ai-assistant';
|
|
351
|
+
return {
|
|
352
|
+
role: isAIMessage ? 'assistant' : 'user',
|
|
353
|
+
content: msg.body
|
|
354
|
+
};
|
|
355
|
+
});
|
|
356
|
+
this._history = Private.sanitizeModelMessages(modelMessages);
|
|
320
357
|
}
|
|
321
358
|
/**
|
|
322
359
|
* Stops the current streaming response by aborting the request.
|
|
360
|
+
* Resolve any pending approval.
|
|
323
361
|
*/
|
|
324
|
-
stopStreaming() {
|
|
362
|
+
stopStreaming(reason) {
|
|
325
363
|
this._controller?.abort();
|
|
364
|
+
// Reject any pending approvals
|
|
365
|
+
for (const [approvalId, pending] of this._pendingApprovals) {
|
|
366
|
+
pending.resolve(false, reason ?? 'Stream ended by user');
|
|
367
|
+
this._agentEvent.emit({
|
|
368
|
+
type: 'tool_approval_resolved',
|
|
369
|
+
data: { approvalId, approved: false }
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
this._pendingApprovals.clear();
|
|
326
373
|
}
|
|
327
374
|
/**
|
|
328
375
|
* Approves a pending tool call.
|
|
@@ -362,7 +409,9 @@ export class AgentManager {
|
|
|
362
409
|
* @param message The user message to respond to (may include processed attachment content)
|
|
363
410
|
*/
|
|
364
411
|
async generateResponse(message) {
|
|
412
|
+
this._streaming = new PromiseDelegate();
|
|
365
413
|
this._controller = new AbortController();
|
|
414
|
+
const responseHistory = [];
|
|
366
415
|
try {
|
|
367
416
|
// Ensure we have an agent
|
|
368
417
|
if (!this._agent) {
|
|
@@ -372,28 +421,39 @@ export class AgentManager {
|
|
|
372
421
|
throw new Error('Failed to initialize agent');
|
|
373
422
|
}
|
|
374
423
|
// Add user message to history
|
|
375
|
-
|
|
424
|
+
responseHistory.push({
|
|
376
425
|
role: 'user',
|
|
377
426
|
content: message
|
|
378
427
|
});
|
|
379
428
|
let continueLoop = true;
|
|
380
429
|
while (continueLoop) {
|
|
381
430
|
const result = await this._agent.stream({
|
|
382
|
-
messages: this._history,
|
|
431
|
+
messages: [...this._history, ...responseHistory],
|
|
383
432
|
abortSignal: this._controller.signal
|
|
384
433
|
});
|
|
385
434
|
const streamResult = await this._processStreamResult(result);
|
|
386
|
-
|
|
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.
|
|
387
448
|
const responseMessages = await result.response;
|
|
388
|
-
this._updateTokenUsage(await result.usage);
|
|
389
449
|
// Add response messages to history
|
|
390
450
|
if (responseMessages.messages?.length) {
|
|
391
|
-
|
|
451
|
+
responseHistory.push(...responseMessages.messages);
|
|
392
452
|
}
|
|
393
453
|
// Add approval response if processed
|
|
394
454
|
if (streamResult.approvalResponse) {
|
|
395
455
|
// Check if the last message is a tool message we can append to
|
|
396
|
-
const lastMsg =
|
|
456
|
+
const lastMsg = responseHistory[responseHistory.length - 1];
|
|
397
457
|
if (lastMsg &&
|
|
398
458
|
lastMsg.role === 'tool' &&
|
|
399
459
|
Array.isArray(lastMsg.content) &&
|
|
@@ -403,11 +463,13 @@ export class AgentManager {
|
|
|
403
463
|
}
|
|
404
464
|
else {
|
|
405
465
|
// Add as separate message
|
|
406
|
-
|
|
466
|
+
responseHistory.push(streamResult.approvalResponse);
|
|
407
467
|
}
|
|
408
468
|
}
|
|
409
469
|
continueLoop = streamResult.approvalProcessed;
|
|
410
470
|
}
|
|
471
|
+
// Add the messages to the history only if the response ended without error.
|
|
472
|
+
this._history.push(...Private.sanitizeModelMessages(responseHistory));
|
|
411
473
|
}
|
|
412
474
|
catch (error) {
|
|
413
475
|
if (error.name !== 'AbortError') {
|
|
@@ -416,23 +478,32 @@ export class AgentManager {
|
|
|
416
478
|
data: { error: error }
|
|
417
479
|
});
|
|
418
480
|
}
|
|
419
|
-
// After an error (including AbortError), sanitize the history
|
|
420
|
-
// to remove any trailing assistant messages without tool results
|
|
421
|
-
this._sanitizeHistory();
|
|
422
481
|
}
|
|
423
482
|
finally {
|
|
424
483
|
this._controller = null;
|
|
484
|
+
this._streaming.resolve();
|
|
425
485
|
}
|
|
426
486
|
}
|
|
427
487
|
/**
|
|
428
|
-
* Updates token usage statistics.
|
|
488
|
+
* Updates cumulative token usage statistics from a completed model step.
|
|
429
489
|
*/
|
|
430
|
-
_updateTokenUsage(usage) {
|
|
490
|
+
_updateTokenUsage(usage, lastRequestInputTokens) {
|
|
491
|
+
const contextWindow = this._getActiveContextWindow();
|
|
492
|
+
const estimatedRequestInputTokens = lastRequestInputTokens ?? usage?.inputTokens;
|
|
431
493
|
if (usage) {
|
|
432
494
|
this._tokenUsage.inputTokens += usage.inputTokens ?? 0;
|
|
433
495
|
this._tokenUsage.outputTokens += usage.outputTokens ?? 0;
|
|
434
|
-
this._tokenUsageChanged.emit(this._tokenUsage);
|
|
435
496
|
}
|
|
497
|
+
this._tokenUsage.lastRequestInputTokens = estimatedRequestInputTokens;
|
|
498
|
+
this._tokenUsage.contextWindow = contextWindow;
|
|
499
|
+
this._tokenUsageChanged.emit(this._tokenUsage);
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Gets the configured context window for the active provider.
|
|
503
|
+
*/
|
|
504
|
+
_getActiveContextWindow() {
|
|
505
|
+
const activeProviderConfig = this._settingsModel.getProvider(this._activeProvider);
|
|
506
|
+
return getEffectiveContextWindow(activeProviderConfig, this._providerRegistry);
|
|
436
507
|
}
|
|
437
508
|
/**
|
|
438
509
|
* Initializes the AI agent with current settings and tools.
|
|
@@ -484,6 +555,9 @@ export class AgentManager {
|
|
|
484
555
|
const activeProviderInfo = activeProviderConfig && this._providerRegistry
|
|
485
556
|
? this._providerRegistry.getProviderInfo(activeProviderConfig.provider)
|
|
486
557
|
: null;
|
|
558
|
+
const contextWindow = getEffectiveContextWindow(activeProviderConfig, this._providerRegistry);
|
|
559
|
+
this._tokenUsage.contextWindow = contextWindow;
|
|
560
|
+
this._tokenUsageChanged.emit(this._tokenUsage);
|
|
487
561
|
const temperature = activeProviderConfig?.parameters?.temperature ?? DEFAULT_TEMPERATURE;
|
|
488
562
|
const maxTokens = activeProviderConfig?.parameters?.maxOutputTokens;
|
|
489
563
|
const maxTurns = activeProviderConfig?.parameters?.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
@@ -562,7 +636,10 @@ ${richOutputWorkflowInstruction}`;
|
|
|
562
636
|
async _processStreamResult(result) {
|
|
563
637
|
let fullResponse = '';
|
|
564
638
|
let currentMessageId = null;
|
|
565
|
-
const processResult = {
|
|
639
|
+
const processResult = {
|
|
640
|
+
approvalProcessed: false,
|
|
641
|
+
aborted: false
|
|
642
|
+
};
|
|
566
643
|
for await (const part of result.fullStream) {
|
|
567
644
|
switch (part.type) {
|
|
568
645
|
case 'text-delta':
|
|
@@ -617,6 +694,12 @@ ${richOutputWorkflowInstruction}`;
|
|
|
617
694
|
}
|
|
618
695
|
await this._handleApprovalRequest(part, processResult);
|
|
619
696
|
break;
|
|
697
|
+
case 'finish-step':
|
|
698
|
+
this._updateTokenUsage(part.usage, part.usage.inputTokens);
|
|
699
|
+
break;
|
|
700
|
+
case 'abort':
|
|
701
|
+
processResult.aborted = true;
|
|
702
|
+
break;
|
|
620
703
|
// Ignore: text-start, text-end, finish, error, and others
|
|
621
704
|
default:
|
|
622
705
|
break;
|
|
@@ -851,81 +934,6 @@ WEB RETRIEVAL POLICY:
|
|
|
851
934
|
}
|
|
852
935
|
return `Supported MIME types in this session: ${safeMimeTypes.join(', ')}`;
|
|
853
936
|
}
|
|
854
|
-
/**
|
|
855
|
-
* Sanitizes history to ensure it's in a valid state in case of abort or error.
|
|
856
|
-
*/
|
|
857
|
-
_sanitizeHistory() {
|
|
858
|
-
if (this._history.length === 0) {
|
|
859
|
-
return;
|
|
860
|
-
}
|
|
861
|
-
const newHistory = [];
|
|
862
|
-
for (let i = 0; i < this._history.length; i++) {
|
|
863
|
-
const msg = this._history[i];
|
|
864
|
-
if (msg.role === 'assistant') {
|
|
865
|
-
const toolCallIds = this._getToolCallIds(msg);
|
|
866
|
-
if (toolCallIds.length > 0) {
|
|
867
|
-
// Find if there's a following tool message with results for these calls
|
|
868
|
-
const nextMsg = this._history[i + 1];
|
|
869
|
-
if (nextMsg &&
|
|
870
|
-
nextMsg.role === 'tool' &&
|
|
871
|
-
this._matchesAllToolCalls(nextMsg, toolCallIds)) {
|
|
872
|
-
newHistory.push(msg);
|
|
873
|
-
}
|
|
874
|
-
else {
|
|
875
|
-
// Message has unmatched tool calls drop it and everything after it
|
|
876
|
-
break;
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
else {
|
|
880
|
-
newHistory.push(msg);
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
else if (msg.role === 'tool') {
|
|
884
|
-
// Tool messages are valid if they were preceded by a valid assistant message
|
|
885
|
-
newHistory.push(msg);
|
|
886
|
-
}
|
|
887
|
-
else {
|
|
888
|
-
newHistory.push(msg);
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
this._history = newHistory;
|
|
892
|
-
}
|
|
893
|
-
/**
|
|
894
|
-
* Extracts tool call IDs from a message
|
|
895
|
-
*/
|
|
896
|
-
_getToolCallIds(message) {
|
|
897
|
-
const ids = [];
|
|
898
|
-
// Check content array for tool-call parts
|
|
899
|
-
if (Array.isArray(message.content)) {
|
|
900
|
-
for (const part of message.content) {
|
|
901
|
-
if (typeof part === 'object' &&
|
|
902
|
-
part !== null &&
|
|
903
|
-
'type' in part &&
|
|
904
|
-
part.type === 'tool-call') {
|
|
905
|
-
ids.push(part.toolCallId);
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
return ids;
|
|
910
|
-
}
|
|
911
|
-
/**
|
|
912
|
-
* Checks if a tool message contains results for all specified tool call IDs
|
|
913
|
-
*/
|
|
914
|
-
_matchesAllToolCalls(message, callIds) {
|
|
915
|
-
if (message.role !== 'tool' || !Array.isArray(message.content)) {
|
|
916
|
-
return false;
|
|
917
|
-
}
|
|
918
|
-
const resultIds = new Set();
|
|
919
|
-
for (const part of message.content) {
|
|
920
|
-
if (typeof part === 'object' &&
|
|
921
|
-
part !== null &&
|
|
922
|
-
'type' in part &&
|
|
923
|
-
part.type === 'tool-result') {
|
|
924
|
-
resultIds.add(part.toolCallId);
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
return callIds.every(id => resultIds.has(id));
|
|
928
|
-
}
|
|
929
937
|
// Private attributes
|
|
930
938
|
_settingsModel;
|
|
931
939
|
_toolRegistry;
|
|
@@ -947,24 +955,102 @@ WEB RETRIEVAL POLICY:
|
|
|
947
955
|
_initQueue = Promise.resolve();
|
|
948
956
|
_agentConfig;
|
|
949
957
|
_pendingApprovals = new Map();
|
|
958
|
+
_streaming = new PromiseDelegate();
|
|
950
959
|
}
|
|
951
960
|
var Private;
|
|
952
961
|
(function (Private) {
|
|
953
962
|
/**
|
|
954
|
-
*
|
|
955
|
-
*
|
|
963
|
+
* Sanitize the messages before adding them to the history.
|
|
964
|
+
*
|
|
965
|
+
* 1- Make sure the message sequence is not altered:
|
|
966
|
+
* - tool-call messages should have a corresponding tool-result (and vice-versa)
|
|
967
|
+
* - tool-approval-request should have a tool-approval-response (and vice-versa)
|
|
968
|
+
*
|
|
969
|
+
* 2- Keep only serializable messages by doing a JSON round-trip.
|
|
970
|
+
* Messages that cannot be serialized are dropped.
|
|
956
971
|
*/
|
|
957
972
|
Private.sanitizeModelMessages = (messages) => {
|
|
958
973
|
const sanitized = [];
|
|
959
974
|
for (const message of messages) {
|
|
960
|
-
|
|
961
|
-
|
|
975
|
+
if (message.role === 'assistant') {
|
|
976
|
+
let newMessage;
|
|
977
|
+
if (!Array.isArray(message.content)) {
|
|
978
|
+
newMessage = message;
|
|
979
|
+
}
|
|
980
|
+
else {
|
|
981
|
+
// Remove assistant message content without a required response.
|
|
982
|
+
const newContent = [];
|
|
983
|
+
for (const assistantContent of message.content) {
|
|
984
|
+
let isContentValid = true;
|
|
985
|
+
if (assistantContent.type === 'tool-call') {
|
|
986
|
+
const toolCallId = assistantContent.toolCallId;
|
|
987
|
+
isContentValid = !!messages.find(msg => msg.role === 'tool' &&
|
|
988
|
+
Array.isArray(msg.content) &&
|
|
989
|
+
msg.content.find(content => content.type === 'tool-result' &&
|
|
990
|
+
content.toolCallId === toolCallId));
|
|
991
|
+
}
|
|
992
|
+
else if (assistantContent.type === 'tool-approval-request') {
|
|
993
|
+
const approvalId = assistantContent.approvalId;
|
|
994
|
+
isContentValid = !!messages.find(msg => msg.role === 'tool' &&
|
|
995
|
+
Array.isArray(msg.content) &&
|
|
996
|
+
msg.content.find(content => content.type === 'tool-approval-response' &&
|
|
997
|
+
content.approvalId === approvalId));
|
|
998
|
+
}
|
|
999
|
+
if (isContentValid) {
|
|
1000
|
+
newContent.push(assistantContent);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
if (newContent.length) {
|
|
1004
|
+
newMessage = { ...message, content: newContent };
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
if (newMessage) {
|
|
1008
|
+
try {
|
|
1009
|
+
sanitized.push(JSON.parse(JSON.stringify(newMessage)));
|
|
1010
|
+
}
|
|
1011
|
+
catch {
|
|
1012
|
+
// Drop messages that cannot be serialized
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
962
1015
|
}
|
|
963
|
-
|
|
964
|
-
//
|
|
1016
|
+
else if (message.role === 'tool') {
|
|
1017
|
+
// Remove tool message content without request.
|
|
1018
|
+
const newContent = [];
|
|
1019
|
+
for (const toolContent of message.content) {
|
|
1020
|
+
let isContentValid = true;
|
|
1021
|
+
if (toolContent.type === 'tool-result') {
|
|
1022
|
+
const toolCallId = toolContent.toolCallId;
|
|
1023
|
+
isContentValid = !!sanitized.find(msg => msg.role === 'assistant' &&
|
|
1024
|
+
Array.isArray(msg.content) &&
|
|
1025
|
+
msg.content.find(content => content.type === 'tool-call' &&
|
|
1026
|
+
content.toolCallId === toolCallId));
|
|
1027
|
+
}
|
|
1028
|
+
else if (toolContent.type === 'tool-approval-response') {
|
|
1029
|
+
const approvalId = toolContent.approvalId;
|
|
1030
|
+
isContentValid = !!sanitized.find(msg => msg.role === 'assistant' &&
|
|
1031
|
+
Array.isArray(msg.content) &&
|
|
1032
|
+
msg.content.find(content => content.type === 'tool-approval-request' &&
|
|
1033
|
+
content.approvalId === approvalId));
|
|
1034
|
+
}
|
|
1035
|
+
if (isContentValid) {
|
|
1036
|
+
newContent.push(toolContent);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
if (newContent.length) {
|
|
1040
|
+
try {
|
|
1041
|
+
sanitized.push(JSON.parse(JSON.stringify({ ...message, content: newContent })));
|
|
1042
|
+
}
|
|
1043
|
+
catch {
|
|
1044
|
+
// Drop messages that cannot be serialized
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
else {
|
|
1049
|
+
// Message is a system or user message.
|
|
1050
|
+
sanitized.push(message);
|
|
965
1051
|
}
|
|
966
1052
|
}
|
|
967
|
-
return sanitized;
|
|
1053
|
+
return sanitized.length === messages.length ? sanitized : [];
|
|
968
1054
|
};
|
|
969
1055
|
/**
|
|
970
1056
|
* The token to use with the secrets manager, setter and getter.
|