@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/lib/agent.js
DELETED
|
@@ -1,1116 +0,0 @@
|
|
|
1
|
-
import { createMCPClient } from '@ai-sdk/mcp';
|
|
2
|
-
import { PromiseDelegate } from '@lumino/coreutils';
|
|
3
|
-
import { Signal } from '@lumino/signaling';
|
|
4
|
-
import { generateText, ToolLoopAgent, stepCountIs, APICallError } from 'ai';
|
|
5
|
-
import { createModel } from './providers/models';
|
|
6
|
-
import { getEffectiveContextWindow } from './providers/model-info';
|
|
7
|
-
import { createProviderTools } from './providers/provider-tools';
|
|
8
|
-
import { SECRETS_NAMESPACE } from './tokens';
|
|
9
|
-
/**
|
|
10
|
-
* The agent manager factory.
|
|
11
|
-
*/
|
|
12
|
-
export class AgentManagerFactory {
|
|
13
|
-
constructor(options) {
|
|
14
|
-
Private.setToken(options.token);
|
|
15
|
-
this._settingsModel = options.settingsModel;
|
|
16
|
-
this._skillRegistry = options.skillRegistry;
|
|
17
|
-
this._secretsManager = options.secretsManager;
|
|
18
|
-
this._mcpClients = [];
|
|
19
|
-
this._mcpConnectionChanged = new Signal(this);
|
|
20
|
-
if (this._skillRegistry) {
|
|
21
|
-
this._skillRegistry.skillsChanged.connect(() => {
|
|
22
|
-
this.refreshSkillSnapshots();
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
// Initialize agent on construction
|
|
26
|
-
this._initializeAgents().catch(error => console.warn('Failed to initialize agent in constructor:', error));
|
|
27
|
-
// Listen for settings changes
|
|
28
|
-
this._settingsModel.stateChanged.connect(this._onSettingsChanged, this);
|
|
29
|
-
// Disable the secrets manager if the token is empty.
|
|
30
|
-
if (!options.token) {
|
|
31
|
-
this._secretsManager = undefined;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Create a new agent.
|
|
36
|
-
*/
|
|
37
|
-
createAgent(options) {
|
|
38
|
-
const agentManager = new AgentManager({
|
|
39
|
-
...options,
|
|
40
|
-
skillRegistry: this._skillRegistry,
|
|
41
|
-
secretsManager: this._secretsManager
|
|
42
|
-
});
|
|
43
|
-
this._agentManagers.push(agentManager);
|
|
44
|
-
// New chats can be created before MCP setup finishes.
|
|
45
|
-
// Reinitialize them with connected MCP tools once it does.
|
|
46
|
-
this._initQueue
|
|
47
|
-
.then(() => this.getMCPTools())
|
|
48
|
-
.then(mcpTools => {
|
|
49
|
-
if (Object.keys(mcpTools).length > 0) {
|
|
50
|
-
agentManager.initializeAgent(mcpTools);
|
|
51
|
-
}
|
|
52
|
-
})
|
|
53
|
-
.catch(error => console.warn('Failed to pass MCP tools to new agent:', error));
|
|
54
|
-
return agentManager;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Signal emitted when MCP connection status changes
|
|
58
|
-
*/
|
|
59
|
-
get mcpConnectionChanged() {
|
|
60
|
-
return this._mcpConnectionChanged;
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Checks whether a specific MCP server is connected.
|
|
64
|
-
* @param serverName The name of the MCP server to check
|
|
65
|
-
* @returns True if the server is connected, false otherwise
|
|
66
|
-
*/
|
|
67
|
-
isMCPServerConnected(serverName) {
|
|
68
|
-
return this._mcpClients.some(wrapper => wrapper.name === serverName);
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Gets the MCP tools from connected servers
|
|
72
|
-
*/
|
|
73
|
-
async getMCPTools() {
|
|
74
|
-
const mcpTools = {};
|
|
75
|
-
for (const wrapper of this._mcpClients) {
|
|
76
|
-
try {
|
|
77
|
-
const tools = await wrapper.client.tools();
|
|
78
|
-
Object.assign(mcpTools, tools);
|
|
79
|
-
}
|
|
80
|
-
catch (error) {
|
|
81
|
-
console.warn(`Failed to get tools from MCP server ${wrapper.name}:`, error);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return mcpTools;
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Handles settings changes and reinitializes the agent.
|
|
88
|
-
*/
|
|
89
|
-
_onSettingsChanged() {
|
|
90
|
-
this._initializeAgents().catch(error => console.warn('Failed to initialize agent on settings change:', error));
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Initializes MCP (Model Context Protocol) clients based on current settings.
|
|
94
|
-
* Closes existing clients and connects to enabled servers from configuration.
|
|
95
|
-
*/
|
|
96
|
-
async _initializeMCPClients() {
|
|
97
|
-
const config = this._settingsModel.config;
|
|
98
|
-
const enabledServers = config.mcpServers.filter(server => server.enabled);
|
|
99
|
-
let connectionChanged = false;
|
|
100
|
-
// Close existing clients
|
|
101
|
-
for (const wrapper of this._mcpClients) {
|
|
102
|
-
try {
|
|
103
|
-
await wrapper.client.close();
|
|
104
|
-
connectionChanged = true;
|
|
105
|
-
}
|
|
106
|
-
catch (error) {
|
|
107
|
-
console.warn('Error closing MCP client:', error);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
this._mcpClients = [];
|
|
111
|
-
for (const serverConfig of enabledServers) {
|
|
112
|
-
try {
|
|
113
|
-
const client = await createMCPClient({
|
|
114
|
-
transport: {
|
|
115
|
-
type: 'http',
|
|
116
|
-
url: serverConfig.url
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
this._mcpClients.push({
|
|
120
|
-
name: serverConfig.name,
|
|
121
|
-
client
|
|
122
|
-
});
|
|
123
|
-
connectionChanged = true;
|
|
124
|
-
}
|
|
125
|
-
catch (error) {
|
|
126
|
-
console.warn(`Failed to connect to MCP server "${serverConfig.name}" at ${serverConfig.url}:`, error);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
// Emit connection change signal if there were any changes
|
|
130
|
-
if (connectionChanged) {
|
|
131
|
-
this._mcpConnectionChanged.emit(this._mcpClients.length > 0);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Initializes the AI agent with current settings and tools.
|
|
136
|
-
* Sets up the agent with model configuration, tools, and MCP servers.
|
|
137
|
-
*/
|
|
138
|
-
async _initializeAgents() {
|
|
139
|
-
this._initQueue = this._initQueue
|
|
140
|
-
.catch(() => undefined)
|
|
141
|
-
.then(async () => {
|
|
142
|
-
try {
|
|
143
|
-
await this._initializeMCPClients();
|
|
144
|
-
const mcpTools = await this.getMCPTools();
|
|
145
|
-
this._agentManagers.forEach(manager => {
|
|
146
|
-
manager.initializeAgent(mcpTools);
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
catch (error) {
|
|
150
|
-
console.warn('Failed to initialize agents:', error);
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
return this._initQueue;
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Refresh skill snapshots across all agents.
|
|
157
|
-
*/
|
|
158
|
-
refreshSkillSnapshots() {
|
|
159
|
-
this._agentManagers.forEach(manager => {
|
|
160
|
-
manager.refreshSkills();
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
_agentManagers = [];
|
|
164
|
-
_settingsModel;
|
|
165
|
-
_skillRegistry;
|
|
166
|
-
_secretsManager;
|
|
167
|
-
_mcpClients;
|
|
168
|
-
_mcpConnectionChanged;
|
|
169
|
-
_initQueue = Promise.resolve();
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* Default parameter values for agent configuration
|
|
173
|
-
*/
|
|
174
|
-
const DEFAULT_TEMPERATURE = 0.7;
|
|
175
|
-
const DEFAULT_MAX_TURNS = 25;
|
|
176
|
-
/**
|
|
177
|
-
* Manages the AI agent lifecycle and execution loop.
|
|
178
|
-
* Provides agent initialization, tool management, MCP server integration,
|
|
179
|
-
* and handles the complete agent execution cycle.
|
|
180
|
-
* Emits events for UI updates instead of directly manipulating the chat interface.
|
|
181
|
-
*/
|
|
182
|
-
export class AgentManager {
|
|
183
|
-
/**
|
|
184
|
-
* Creates a new AgentManager instance.
|
|
185
|
-
* @param options Configuration options for the agent manager
|
|
186
|
-
*/
|
|
187
|
-
constructor(options) {
|
|
188
|
-
this._settingsModel = options.settingsModel;
|
|
189
|
-
this._toolRegistry = options.toolRegistry;
|
|
190
|
-
this._providerRegistry = options.providerRegistry;
|
|
191
|
-
this._skillRegistry = options.skillRegistry;
|
|
192
|
-
this._secretsManager = options.secretsManager;
|
|
193
|
-
this._selectedToolNames = [];
|
|
194
|
-
this._agent = null;
|
|
195
|
-
this._history = [];
|
|
196
|
-
this._mcpTools = {};
|
|
197
|
-
this._controller = null;
|
|
198
|
-
this._agentEvent = new Signal(this);
|
|
199
|
-
this._tokenUsage = options.tokenUsage ?? {
|
|
200
|
-
inputTokens: 0,
|
|
201
|
-
outputTokens: 0
|
|
202
|
-
};
|
|
203
|
-
this._tokenUsageChanged = new Signal(this);
|
|
204
|
-
this._skills = [];
|
|
205
|
-
this._agentConfig = null;
|
|
206
|
-
this._renderMimeRegistry = options.renderMimeRegistry;
|
|
207
|
-
this._streaming.resolve();
|
|
208
|
-
this.activeProvider =
|
|
209
|
-
options.activeProvider ?? this._settingsModel.config.defaultProvider;
|
|
210
|
-
// Initialize selected tools to all available tools by default
|
|
211
|
-
if (this._toolRegistry) {
|
|
212
|
-
this._selectedToolNames = Object.keys(this._toolRegistry.tools);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Signal emitted when agent events occur
|
|
217
|
-
*/
|
|
218
|
-
get agentEvent() {
|
|
219
|
-
return this._agentEvent;
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* Signal emitted when the active provider has changed.
|
|
223
|
-
*/
|
|
224
|
-
get activeProviderChanged() {
|
|
225
|
-
return this._activeProviderChanged;
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* Gets the current token usage statistics.
|
|
229
|
-
*/
|
|
230
|
-
get tokenUsage() {
|
|
231
|
-
return this._tokenUsage;
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Signal emitted when token usage statistics change.
|
|
235
|
-
*/
|
|
236
|
-
get tokenUsageChanged() {
|
|
237
|
-
return this._tokenUsageChanged;
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* Refresh the skills snapshot and rebuild the agent if resources are ready.
|
|
241
|
-
*/
|
|
242
|
-
refreshSkills() {
|
|
243
|
-
this._initQueue = this._initQueue
|
|
244
|
-
.catch(() => undefined)
|
|
245
|
-
.then(async () => {
|
|
246
|
-
this._refreshSkills();
|
|
247
|
-
if (!this._agentConfig) {
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
this._rebuildAgent();
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* The active provider for this agent.
|
|
255
|
-
*/
|
|
256
|
-
get activeProvider() {
|
|
257
|
-
return this._activeProvider;
|
|
258
|
-
}
|
|
259
|
-
set activeProvider(value) {
|
|
260
|
-
const previousProvider = this._activeProvider;
|
|
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);
|
|
268
|
-
this.initializeAgent();
|
|
269
|
-
this._activeProviderChanged.emit(this._activeProvider);
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Sets the selected tools by name and reinitializes the agent.
|
|
273
|
-
* @param toolNames Array of tool names to select
|
|
274
|
-
*/
|
|
275
|
-
setSelectedTools(toolNames) {
|
|
276
|
-
this._selectedToolNames = [...toolNames];
|
|
277
|
-
this.initializeAgent().catch(error => console.warn('Failed to initialize agent on tools change:', error));
|
|
278
|
-
}
|
|
279
|
-
/**
|
|
280
|
-
* Gets the currently selected tools as a record.
|
|
281
|
-
* @returns Record of selected tools
|
|
282
|
-
*/
|
|
283
|
-
get selectedAgentTools() {
|
|
284
|
-
if (!this._toolRegistry) {
|
|
285
|
-
return {};
|
|
286
|
-
}
|
|
287
|
-
const result = {};
|
|
288
|
-
for (const name of this._selectedToolNames) {
|
|
289
|
-
const tool = this._toolRegistry.get(name);
|
|
290
|
-
if (tool) {
|
|
291
|
-
result[name] = tool;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
return result;
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* Checks if the current configuration is valid for agent operations.
|
|
298
|
-
* Uses the provider registry to determine if an API key is required.
|
|
299
|
-
* @returns True if the configuration is valid, false otherwise
|
|
300
|
-
*/
|
|
301
|
-
hasValidConfig() {
|
|
302
|
-
const activeProviderConfig = this._settingsModel.getProvider(this._activeProvider);
|
|
303
|
-
if (!activeProviderConfig) {
|
|
304
|
-
return false;
|
|
305
|
-
}
|
|
306
|
-
if (!activeProviderConfig.model) {
|
|
307
|
-
return false;
|
|
308
|
-
}
|
|
309
|
-
if (this._providerRegistry) {
|
|
310
|
-
const providerInfo = this._providerRegistry.getProviderInfo(activeProviderConfig.provider);
|
|
311
|
-
if (providerInfo?.apiKeyRequirement === 'required') {
|
|
312
|
-
return !!activeProviderConfig.apiKey;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
return true;
|
|
316
|
-
}
|
|
317
|
-
/**
|
|
318
|
-
* Clears conversation history and resets agent state.
|
|
319
|
-
*/
|
|
320
|
-
async clearHistory() {
|
|
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
|
|
339
|
-
this.stopStreaming();
|
|
340
|
-
for (const [approvalId, pending] of this._pendingApprovals) {
|
|
341
|
-
pending.resolve(false, 'Chat history changed');
|
|
342
|
-
this._agentEvent.emit({
|
|
343
|
-
type: 'tool_approval_resolved',
|
|
344
|
-
data: { approvalId, approved: false }
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
this._pendingApprovals.clear();
|
|
348
|
-
// Convert chat messages to model messages
|
|
349
|
-
const modelMessages = messages.map(msg => {
|
|
350
|
-
const role = msg.sender.username === 'ai-assistant' ? 'assistant' : 'user';
|
|
351
|
-
return {
|
|
352
|
-
role,
|
|
353
|
-
content: msg.body
|
|
354
|
-
};
|
|
355
|
-
});
|
|
356
|
-
this._history = Private.sanitizeModelMessages(modelMessages);
|
|
357
|
-
}
|
|
358
|
-
/**
|
|
359
|
-
* Stops the current streaming response by aborting the request.
|
|
360
|
-
* Resolve any pending approval.
|
|
361
|
-
*/
|
|
362
|
-
stopStreaming(reason) {
|
|
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();
|
|
373
|
-
}
|
|
374
|
-
/**
|
|
375
|
-
* Approves a pending tool call.
|
|
376
|
-
* @param approvalId The approval ID to approve
|
|
377
|
-
* @param reason Optional reason for approval
|
|
378
|
-
*/
|
|
379
|
-
approveToolCall(approvalId, reason) {
|
|
380
|
-
const pending = this._pendingApprovals.get(approvalId);
|
|
381
|
-
if (pending) {
|
|
382
|
-
pending.resolve(true, reason);
|
|
383
|
-
this._pendingApprovals.delete(approvalId);
|
|
384
|
-
this._agentEvent.emit({
|
|
385
|
-
type: 'tool_approval_resolved',
|
|
386
|
-
data: { approvalId, approved: true }
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
/**
|
|
391
|
-
* Rejects a pending tool call.
|
|
392
|
-
* @param approvalId The approval ID to reject
|
|
393
|
-
* @param reason Optional reason for rejection
|
|
394
|
-
*/
|
|
395
|
-
rejectToolCall(approvalId, reason) {
|
|
396
|
-
const pending = this._pendingApprovals.get(approvalId);
|
|
397
|
-
if (pending) {
|
|
398
|
-
pending.resolve(false, reason);
|
|
399
|
-
this._pendingApprovals.delete(approvalId);
|
|
400
|
-
this._agentEvent.emit({
|
|
401
|
-
type: 'tool_approval_resolved',
|
|
402
|
-
data: { approvalId, approved: false }
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* Generates AI response to user message using the agent.
|
|
408
|
-
* Handles the complete execution cycle including tool calls.
|
|
409
|
-
* @param message The user message to respond to (may include processed attachment content)
|
|
410
|
-
*/
|
|
411
|
-
async generateResponse(message) {
|
|
412
|
-
this._streaming = new PromiseDelegate();
|
|
413
|
-
this._controller = new AbortController();
|
|
414
|
-
const responseHistory = [];
|
|
415
|
-
// Add user message to history
|
|
416
|
-
responseHistory.push({
|
|
417
|
-
role: 'user',
|
|
418
|
-
content: message
|
|
419
|
-
});
|
|
420
|
-
try {
|
|
421
|
-
// Ensure we have an agent
|
|
422
|
-
if (!this._agent) {
|
|
423
|
-
await this.initializeAgent();
|
|
424
|
-
}
|
|
425
|
-
if (!this._agent) {
|
|
426
|
-
throw new Error('Failed to initialize agent');
|
|
427
|
-
}
|
|
428
|
-
let continueLoop = true;
|
|
429
|
-
while (continueLoop) {
|
|
430
|
-
const result = await this._agent.stream({
|
|
431
|
-
messages: [...this._history, ...responseHistory],
|
|
432
|
-
abortSignal: this._controller.signal
|
|
433
|
-
});
|
|
434
|
-
const streamResult = await this._processStreamResult(result);
|
|
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.
|
|
448
|
-
const responseMessages = await result.response;
|
|
449
|
-
// Add response messages to history
|
|
450
|
-
if (responseMessages.messages?.length) {
|
|
451
|
-
responseHistory.push(...responseMessages.messages);
|
|
452
|
-
}
|
|
453
|
-
// Add approval response if processed
|
|
454
|
-
if (streamResult.approvalResponse) {
|
|
455
|
-
// Check if the last message is a tool message we can append to
|
|
456
|
-
const lastMsg = responseHistory[responseHistory.length - 1];
|
|
457
|
-
if (lastMsg &&
|
|
458
|
-
lastMsg.role === 'tool' &&
|
|
459
|
-
Array.isArray(lastMsg.content) &&
|
|
460
|
-
Array.isArray(streamResult.approvalResponse.content)) {
|
|
461
|
-
const toolContent = lastMsg.content;
|
|
462
|
-
toolContent.push(...streamResult.approvalResponse.content);
|
|
463
|
-
}
|
|
464
|
-
else {
|
|
465
|
-
// Add as separate message
|
|
466
|
-
responseHistory.push(streamResult.approvalResponse);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
continueLoop = streamResult.approvalProcessed;
|
|
470
|
-
}
|
|
471
|
-
// Add the messages to the history only if the response ended without error.
|
|
472
|
-
this._history.push(...Private.sanitizeModelMessages(responseHistory));
|
|
473
|
-
}
|
|
474
|
-
catch (error) {
|
|
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
|
-
}
|
|
500
|
-
this._agentEvent.emit({
|
|
501
|
-
type: '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
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
finally {
|
|
512
|
-
this._controller = null;
|
|
513
|
-
this._streaming.resolve();
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
/**
|
|
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.
|
|
519
|
-
*/
|
|
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;
|
|
540
|
-
if (usage) {
|
|
541
|
-
this._tokenUsage.inputTokens += usage.inputTokens ?? 0;
|
|
542
|
-
this._tokenUsage.outputTokens += usage.outputTokens ?? 0;
|
|
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);
|
|
554
|
-
}
|
|
555
|
-
/**
|
|
556
|
-
* Initializes the AI agent with current settings and tools.
|
|
557
|
-
* Sets up the agent with model configuration, tools, and MCP tools.
|
|
558
|
-
*/
|
|
559
|
-
initializeAgent = async (mcpTools) => {
|
|
560
|
-
this._initQueue = this._initQueue
|
|
561
|
-
.catch(() => undefined)
|
|
562
|
-
.then(async () => {
|
|
563
|
-
try {
|
|
564
|
-
this._refreshSkills();
|
|
565
|
-
await this._prepareAgentConfig(mcpTools);
|
|
566
|
-
this._rebuildAgent();
|
|
567
|
-
}
|
|
568
|
-
catch (error) {
|
|
569
|
-
console.warn('Failed to initialize agent:', error);
|
|
570
|
-
this._agent = null;
|
|
571
|
-
}
|
|
572
|
-
});
|
|
573
|
-
return this._initQueue;
|
|
574
|
-
};
|
|
575
|
-
/**
|
|
576
|
-
* Refresh the in-memory skills snapshot from the skill registry.
|
|
577
|
-
*/
|
|
578
|
-
_refreshSkills() {
|
|
579
|
-
if (!this._skillRegistry) {
|
|
580
|
-
this._skills = [];
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
this._skills = this._skillRegistry.listSkills();
|
|
584
|
-
}
|
|
585
|
-
/**
|
|
586
|
-
* Prepare model, tools, and settings needed to (re)build the agent.
|
|
587
|
-
*/
|
|
588
|
-
async _prepareAgentConfig(mcpTools) {
|
|
589
|
-
const config = this._settingsModel.config;
|
|
590
|
-
if (mcpTools !== undefined) {
|
|
591
|
-
this._mcpTools = mcpTools;
|
|
592
|
-
}
|
|
593
|
-
const model = await this._createModel();
|
|
594
|
-
const supportsToolCalling = this._supportsToolCalling();
|
|
595
|
-
const canUseTools = config.toolsEnabled && supportsToolCalling;
|
|
596
|
-
const hasFunctionToolRegistry = !!(this._toolRegistry && Object.keys(this._toolRegistry.tools).length > 0);
|
|
597
|
-
const selectedFunctionTools = canUseTools && hasFunctionToolRegistry ? this.selectedAgentTools : {};
|
|
598
|
-
const functionTools = canUseTools
|
|
599
|
-
? { ...selectedFunctionTools, ...this._mcpTools }
|
|
600
|
-
: {};
|
|
601
|
-
const activeProviderConfig = this._settingsModel.getProvider(this._activeProvider);
|
|
602
|
-
const activeProviderInfo = activeProviderConfig && this._providerRegistry
|
|
603
|
-
? this._providerRegistry.getProviderInfo(activeProviderConfig.provider)
|
|
604
|
-
: null;
|
|
605
|
-
const contextWindow = getEffectiveContextWindow(activeProviderConfig, this._providerRegistry);
|
|
606
|
-
this._tokenUsage.contextWindow = contextWindow;
|
|
607
|
-
this._tokenUsageChanged.emit(this._tokenUsage);
|
|
608
|
-
const temperature = activeProviderConfig?.parameters?.temperature ?? DEFAULT_TEMPERATURE;
|
|
609
|
-
const maxTokens = activeProviderConfig?.parameters?.maxOutputTokens;
|
|
610
|
-
const maxTurns = activeProviderConfig?.parameters?.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
611
|
-
const tools = this._buildRuntimeTools({
|
|
612
|
-
providerInfo: activeProviderInfo,
|
|
613
|
-
customSettings: activeProviderConfig?.customSettings,
|
|
614
|
-
functionTools,
|
|
615
|
-
includeProviderTools: canUseTools
|
|
616
|
-
});
|
|
617
|
-
const shouldUseTools = canUseTools && Object.keys(tools).length > 0;
|
|
618
|
-
this._agentConfig = {
|
|
619
|
-
model,
|
|
620
|
-
tools,
|
|
621
|
-
temperature,
|
|
622
|
-
maxOutputTokens: maxTokens,
|
|
623
|
-
maxTurns,
|
|
624
|
-
baseSystemPrompt: config.systemPrompt || '',
|
|
625
|
-
shouldUseTools
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
/**
|
|
629
|
-
* Build the runtime tool map used by the agent.
|
|
630
|
-
*/
|
|
631
|
-
_buildRuntimeTools(options) {
|
|
632
|
-
const providerTools = options.includeProviderTools
|
|
633
|
-
? createProviderTools({
|
|
634
|
-
providerInfo: options.providerInfo,
|
|
635
|
-
customSettings: options.customSettings,
|
|
636
|
-
hasFunctionTools: Object.keys(options.functionTools).length > 0
|
|
637
|
-
})
|
|
638
|
-
: {};
|
|
639
|
-
return {
|
|
640
|
-
...providerTools,
|
|
641
|
-
...options.functionTools
|
|
642
|
-
};
|
|
643
|
-
}
|
|
644
|
-
/**
|
|
645
|
-
* Rebuild the agent using cached resources and the current skills snapshot.
|
|
646
|
-
*/
|
|
647
|
-
_rebuildAgent() {
|
|
648
|
-
if (!this._agentConfig) {
|
|
649
|
-
this._agent = null;
|
|
650
|
-
return;
|
|
651
|
-
}
|
|
652
|
-
const { model, tools, temperature, maxOutputTokens, maxTurns, baseSystemPrompt, shouldUseTools } = this._agentConfig;
|
|
653
|
-
const baseInstructions = shouldUseTools
|
|
654
|
-
? this._getEnhancedSystemPrompt(baseSystemPrompt, tools)
|
|
655
|
-
: baseSystemPrompt || 'You are a helpful assistant.';
|
|
656
|
-
const richOutputWorkflowInstruction = shouldUseTools
|
|
657
|
-
? '- When the user asks for visual or rich outputs, prefer running code/commands that produce those outputs and describe that they will be rendered in chat.'
|
|
658
|
-
: '- When tools are unavailable, explain the limitation clearly and provide concrete steps the user can run to produce the desired rich outputs.';
|
|
659
|
-
const supportedMimeTypesInstruction = this._getSupportedMimeTypesInstruction();
|
|
660
|
-
const instructions = `${baseInstructions}
|
|
661
|
-
|
|
662
|
-
RICH OUTPUT RENDERING:
|
|
663
|
-
- The chat UI can render rich MIME outputs as separate assistant messages.
|
|
664
|
-
- ${supportedMimeTypesInstruction}
|
|
665
|
-
- Use only MIME types from the supported list when creating MIME bundles. Do not invent MIME keys.
|
|
666
|
-
- Do not claim that you cannot display maps, images, or rich outputs in chat.
|
|
667
|
-
${richOutputWorkflowInstruction}`;
|
|
668
|
-
this._agent = new ToolLoopAgent({
|
|
669
|
-
model,
|
|
670
|
-
instructions,
|
|
671
|
-
tools,
|
|
672
|
-
temperature,
|
|
673
|
-
maxOutputTokens,
|
|
674
|
-
stopWhen: stepCountIs(maxTurns)
|
|
675
|
-
});
|
|
676
|
-
}
|
|
677
|
-
/**
|
|
678
|
-
* Processes the stream result from agent execution.
|
|
679
|
-
* Handles message streaming, tool calls, and emits appropriate events.
|
|
680
|
-
* @param result The stream result from agent execution
|
|
681
|
-
* @returns Processing result including approval info if applicable
|
|
682
|
-
*/
|
|
683
|
-
async _processStreamResult(result) {
|
|
684
|
-
let fullResponse = '';
|
|
685
|
-
let currentMessageId = null;
|
|
686
|
-
const processResult = {
|
|
687
|
-
approvalProcessed: false,
|
|
688
|
-
aborted: false
|
|
689
|
-
};
|
|
690
|
-
for await (const part of result.fullStream) {
|
|
691
|
-
switch (part.type) {
|
|
692
|
-
case 'text-delta':
|
|
693
|
-
if (!currentMessageId) {
|
|
694
|
-
currentMessageId = `msg-${Date.now()}-${Math.random()}`;
|
|
695
|
-
this._agentEvent.emit({
|
|
696
|
-
type: 'message_start',
|
|
697
|
-
data: { messageId: currentMessageId }
|
|
698
|
-
});
|
|
699
|
-
}
|
|
700
|
-
fullResponse += part.text;
|
|
701
|
-
this._agentEvent.emit({
|
|
702
|
-
type: 'message_chunk',
|
|
703
|
-
data: {
|
|
704
|
-
messageId: currentMessageId,
|
|
705
|
-
chunk: part.text,
|
|
706
|
-
fullContent: fullResponse
|
|
707
|
-
}
|
|
708
|
-
});
|
|
709
|
-
break;
|
|
710
|
-
case 'tool-call':
|
|
711
|
-
// Complete current message before tool call
|
|
712
|
-
if (currentMessageId && fullResponse) {
|
|
713
|
-
this._emitMessageComplete(currentMessageId, fullResponse);
|
|
714
|
-
currentMessageId = null;
|
|
715
|
-
fullResponse = '';
|
|
716
|
-
}
|
|
717
|
-
this._agentEvent.emit({
|
|
718
|
-
type: 'tool_call_start',
|
|
719
|
-
data: {
|
|
720
|
-
callId: part.toolCallId,
|
|
721
|
-
toolName: part.toolName,
|
|
722
|
-
input: this._formatToolInput(JSON.stringify(part.input))
|
|
723
|
-
}
|
|
724
|
-
});
|
|
725
|
-
break;
|
|
726
|
-
case 'tool-result':
|
|
727
|
-
this._handleToolResult(part);
|
|
728
|
-
break;
|
|
729
|
-
case 'tool-error':
|
|
730
|
-
this._handleToolError(part);
|
|
731
|
-
break;
|
|
732
|
-
case 'tool-output-denied':
|
|
733
|
-
this._handleToolOutputDenied(part);
|
|
734
|
-
break;
|
|
735
|
-
case 'tool-approval-request':
|
|
736
|
-
// Complete current message before approval
|
|
737
|
-
if (currentMessageId && fullResponse) {
|
|
738
|
-
this._emitMessageComplete(currentMessageId, fullResponse);
|
|
739
|
-
currentMessageId = null;
|
|
740
|
-
fullResponse = '';
|
|
741
|
-
}
|
|
742
|
-
await this._handleApprovalRequest(part, processResult);
|
|
743
|
-
break;
|
|
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
|
|
753
|
-
default:
|
|
754
|
-
break;
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
// Complete final message if content remains
|
|
758
|
-
if (currentMessageId && fullResponse) {
|
|
759
|
-
this._emitMessageComplete(currentMessageId, fullResponse);
|
|
760
|
-
}
|
|
761
|
-
return processResult;
|
|
762
|
-
}
|
|
763
|
-
/**
|
|
764
|
-
* Emits a message_complete event.
|
|
765
|
-
*/
|
|
766
|
-
_emitMessageComplete(messageId, content) {
|
|
767
|
-
this._agentEvent.emit({
|
|
768
|
-
type: 'message_complete',
|
|
769
|
-
data: { messageId, content }
|
|
770
|
-
});
|
|
771
|
-
}
|
|
772
|
-
/**
|
|
773
|
-
* Handles tool-result stream parts.
|
|
774
|
-
*/
|
|
775
|
-
_handleToolResult(part) {
|
|
776
|
-
const isError = typeof part.output === 'object' &&
|
|
777
|
-
part.output !== null &&
|
|
778
|
-
'success' in part.output &&
|
|
779
|
-
part.output.success === false;
|
|
780
|
-
this._agentEvent.emit({
|
|
781
|
-
type: 'tool_call_complete',
|
|
782
|
-
data: {
|
|
783
|
-
callId: part.toolCallId,
|
|
784
|
-
toolName: part.toolName,
|
|
785
|
-
outputData: part.output,
|
|
786
|
-
isError
|
|
787
|
-
}
|
|
788
|
-
});
|
|
789
|
-
}
|
|
790
|
-
/**
|
|
791
|
-
* Handles tool-error stream parts.
|
|
792
|
-
*/
|
|
793
|
-
_handleToolError(part) {
|
|
794
|
-
const output = typeof part.error === 'string'
|
|
795
|
-
? part.error
|
|
796
|
-
: part.error instanceof Error
|
|
797
|
-
? part.error.message
|
|
798
|
-
: JSON.stringify(part.error, null, 2);
|
|
799
|
-
this._agentEvent.emit({
|
|
800
|
-
type: 'tool_call_complete',
|
|
801
|
-
data: {
|
|
802
|
-
callId: part.toolCallId,
|
|
803
|
-
toolName: part.toolName,
|
|
804
|
-
outputData: output,
|
|
805
|
-
isError: true
|
|
806
|
-
}
|
|
807
|
-
});
|
|
808
|
-
}
|
|
809
|
-
/**
|
|
810
|
-
* Handles tool-output-denied stream parts.
|
|
811
|
-
*/
|
|
812
|
-
_handleToolOutputDenied(part) {
|
|
813
|
-
this._agentEvent.emit({
|
|
814
|
-
type: 'tool_call_complete',
|
|
815
|
-
data: {
|
|
816
|
-
callId: part.toolCallId,
|
|
817
|
-
toolName: part.toolName,
|
|
818
|
-
outputData: 'Tool output was denied.',
|
|
819
|
-
isError: true
|
|
820
|
-
}
|
|
821
|
-
});
|
|
822
|
-
}
|
|
823
|
-
/**
|
|
824
|
-
* Handles tool-approval-request stream parts.
|
|
825
|
-
*/
|
|
826
|
-
async _handleApprovalRequest(part, result) {
|
|
827
|
-
const { approvalId, toolCall } = part;
|
|
828
|
-
this._agentEvent.emit({
|
|
829
|
-
type: 'tool_approval_request',
|
|
830
|
-
data: {
|
|
831
|
-
approvalId,
|
|
832
|
-
toolCallId: toolCall.toolCallId,
|
|
833
|
-
toolName: toolCall.toolName,
|
|
834
|
-
args: toolCall.input
|
|
835
|
-
}
|
|
836
|
-
});
|
|
837
|
-
const approved = await this._waitForApproval(approvalId);
|
|
838
|
-
result.approvalProcessed = true;
|
|
839
|
-
result.approvalResponse = {
|
|
840
|
-
role: 'tool',
|
|
841
|
-
content: [
|
|
842
|
-
{
|
|
843
|
-
type: 'tool-approval-response',
|
|
844
|
-
approvalId,
|
|
845
|
-
approved
|
|
846
|
-
}
|
|
847
|
-
]
|
|
848
|
-
};
|
|
849
|
-
}
|
|
850
|
-
/**
|
|
851
|
-
* Waits for user approval of a tool call.
|
|
852
|
-
* @param approvalId The approval ID to wait for
|
|
853
|
-
* @returns Promise that resolves to true if approved, false if rejected
|
|
854
|
-
*/
|
|
855
|
-
_waitForApproval(approvalId) {
|
|
856
|
-
return new Promise(resolve => {
|
|
857
|
-
this._pendingApprovals.set(approvalId, {
|
|
858
|
-
resolve: (approved) => {
|
|
859
|
-
resolve(approved);
|
|
860
|
-
}
|
|
861
|
-
});
|
|
862
|
-
});
|
|
863
|
-
}
|
|
864
|
-
/**
|
|
865
|
-
* Formats tool input for display by pretty-printing JSON strings.
|
|
866
|
-
* @param input The tool input string to format
|
|
867
|
-
* @returns Pretty-printed JSON string
|
|
868
|
-
*/
|
|
869
|
-
_formatToolInput(input) {
|
|
870
|
-
try {
|
|
871
|
-
const parsed = JSON.parse(input);
|
|
872
|
-
return JSON.stringify(parsed, null, 2);
|
|
873
|
-
}
|
|
874
|
-
catch {
|
|
875
|
-
return input;
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
/**
|
|
879
|
-
* Checks if the current provider supports tool calling.
|
|
880
|
-
* @returns True if the provider supports tool calling, false otherwise
|
|
881
|
-
*/
|
|
882
|
-
_supportsToolCalling() {
|
|
883
|
-
const activeProviderConfig = this._settingsModel.getProvider(this._activeProvider);
|
|
884
|
-
if (!activeProviderConfig || !this._providerRegistry) {
|
|
885
|
-
return false;
|
|
886
|
-
}
|
|
887
|
-
const providerInfo = this._providerRegistry.getProviderInfo(activeProviderConfig.provider);
|
|
888
|
-
// Default to true if supportsToolCalling is not specified
|
|
889
|
-
return providerInfo?.supportsToolCalling !== false;
|
|
890
|
-
}
|
|
891
|
-
/**
|
|
892
|
-
* Creates a model instance based on current settings.
|
|
893
|
-
* @returns The configured model instance for the agent
|
|
894
|
-
*/
|
|
895
|
-
async _createModel() {
|
|
896
|
-
if (!this._activeProvider) {
|
|
897
|
-
throw new Error('No active provider configured');
|
|
898
|
-
}
|
|
899
|
-
const activeProviderConfig = this._settingsModel.getProvider(this._activeProvider);
|
|
900
|
-
if (!activeProviderConfig) {
|
|
901
|
-
throw new Error('No active provider configured');
|
|
902
|
-
}
|
|
903
|
-
const provider = activeProviderConfig.provider;
|
|
904
|
-
const model = activeProviderConfig.model;
|
|
905
|
-
const baseURL = activeProviderConfig.baseURL;
|
|
906
|
-
let apiKey;
|
|
907
|
-
if (this._secretsManager && this._settingsModel.config.useSecretsManager) {
|
|
908
|
-
const token = Private.getToken();
|
|
909
|
-
if (!token) {
|
|
910
|
-
// This should never happen, the secrets manager should be disabled.
|
|
911
|
-
console.error('@jupyterlite/ai::AgentManager error: the settings manager token is not set.\nYou should disable the the secrets manager from the AI settings.');
|
|
912
|
-
apiKey = '';
|
|
913
|
-
}
|
|
914
|
-
else {
|
|
915
|
-
apiKey =
|
|
916
|
-
(await this._secretsManager.get(token, SECRETS_NAMESPACE, `${provider}:apiKey`))?.value ?? '';
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
else {
|
|
920
|
-
apiKey = this._settingsModel.getApiKey(activeProviderConfig.id);
|
|
921
|
-
}
|
|
922
|
-
return createModel({
|
|
923
|
-
provider,
|
|
924
|
-
model,
|
|
925
|
-
apiKey,
|
|
926
|
-
baseURL
|
|
927
|
-
}, this._providerRegistry);
|
|
928
|
-
}
|
|
929
|
-
/**
|
|
930
|
-
* Enhances the base system prompt with dynamic context like skills.
|
|
931
|
-
* @param baseSystemPrompt The base system prompt from settings
|
|
932
|
-
* @returns The enhanced system prompt with dynamic additions
|
|
933
|
-
*/
|
|
934
|
-
_getEnhancedSystemPrompt(baseSystemPrompt, tools) {
|
|
935
|
-
let prompt = baseSystemPrompt;
|
|
936
|
-
if (this._skills.length > 0) {
|
|
937
|
-
const lines = this._skills.map(skill => `- ${skill.name}: ${skill.description}`);
|
|
938
|
-
const skillsPrompt = `
|
|
939
|
-
|
|
940
|
-
AGENT SKILLS:
|
|
941
|
-
Skills are provided via the skills registry and accessed through tools (not commands).
|
|
942
|
-
When a skill is relevant to the user's task, activate it by calling load_skill with the skill name to load its full instructions, then follow those instructions.
|
|
943
|
-
If the user explicitly asks for the latest list of skills, call discover_skills (optionally with a query).
|
|
944
|
-
Do NOT call discover_skills just to list skills; use the preloaded snapshot below instead unless you need to verify a skill not present in the snapshot.
|
|
945
|
-
If the load_skill result includes a non-empty "resources" array, those are bundled files (scripts, references, templates) you MUST load before proceeding. Only load the listed resource paths; never invent resource names. For each resource path, execute load_skill again with the resource argument, e.g.: load_skill({ name: "<skill>", resource: "<path>" }). Load all listed resources before starting the task.
|
|
946
|
-
|
|
947
|
-
AVAILABLE SKILLS (preloaded snapshot):
|
|
948
|
-
${lines.join('\n')}
|
|
949
|
-
`;
|
|
950
|
-
prompt += skillsPrompt;
|
|
951
|
-
}
|
|
952
|
-
const toolNames = new Set(Object.keys(tools));
|
|
953
|
-
const hasBrowserFetch = toolNames.has('browser_fetch');
|
|
954
|
-
const hasWebFetch = toolNames.has('web_fetch');
|
|
955
|
-
const hasWebSearch = toolNames.has('web_search');
|
|
956
|
-
if (hasBrowserFetch || hasWebFetch || hasWebSearch) {
|
|
957
|
-
const webRetrievalPrompt = `
|
|
958
|
-
|
|
959
|
-
WEB RETRIEVAL POLICY:
|
|
960
|
-
- If the user asks about a specific URL and browser_fetch is available, call browser_fetch first for that URL.
|
|
961
|
-
- If browser_fetch fails due to CORS/network/access, try web_fetch (if available) for that same URL.
|
|
962
|
-
- If web_fetch fails with access/policy errors (for example: url_not_accessible or url_not_allowed) and browser_fetch is available, you MUST call browser_fetch for that same URL before searching.
|
|
963
|
-
- If either fetch method fails with temporary access/network issues (for example: network_or_cors), try the other fetch method if available before searching.
|
|
964
|
-
- Only fall back to web_search after both fetch methods fail or are unavailable.
|
|
965
|
-
- If the user explicitly asks to inspect one exact URL, do not skip directly to search unless both fetch methods fail or are unavailable.
|
|
966
|
-
- In your final response, state which retrieval method succeeded (browser_fetch, web_fetch, or web_search) and mention relevant limitations.
|
|
967
|
-
`;
|
|
968
|
-
prompt += webRetrievalPrompt;
|
|
969
|
-
}
|
|
970
|
-
return prompt;
|
|
971
|
-
}
|
|
972
|
-
/**
|
|
973
|
-
* Build an instruction line describing MIME types supported by this session.
|
|
974
|
-
*/
|
|
975
|
-
_getSupportedMimeTypesInstruction() {
|
|
976
|
-
const mimeTypes = this._renderMimeRegistry?.mimeTypes ?? [];
|
|
977
|
-
const safeMimeTypes = mimeTypes.filter(mimeType => {
|
|
978
|
-
const factory = this._renderMimeRegistry?.getFactory(mimeType);
|
|
979
|
-
return !!factory?.safe;
|
|
980
|
-
});
|
|
981
|
-
if (safeMimeTypes.length === 0) {
|
|
982
|
-
return 'Supported MIME types are determined by the active JupyterLab renderers in this session.';
|
|
983
|
-
}
|
|
984
|
-
return `Supported MIME types in this session: ${safeMimeTypes.join(', ')}`;
|
|
985
|
-
}
|
|
986
|
-
// Private attributes
|
|
987
|
-
_settingsModel;
|
|
988
|
-
_toolRegistry;
|
|
989
|
-
_providerRegistry;
|
|
990
|
-
_skillRegistry;
|
|
991
|
-
_secretsManager;
|
|
992
|
-
_selectedToolNames;
|
|
993
|
-
_agent;
|
|
994
|
-
_history;
|
|
995
|
-
_mcpTools;
|
|
996
|
-
_controller;
|
|
997
|
-
_agentEvent;
|
|
998
|
-
_tokenUsage;
|
|
999
|
-
_tokenUsageChanged;
|
|
1000
|
-
_activeProvider = '';
|
|
1001
|
-
_activeProviderChanged = new Signal(this);
|
|
1002
|
-
_skills;
|
|
1003
|
-
_renderMimeRegistry;
|
|
1004
|
-
_initQueue = Promise.resolve();
|
|
1005
|
-
_agentConfig;
|
|
1006
|
-
_pendingApprovals = new Map();
|
|
1007
|
-
_streaming = new PromiseDelegate();
|
|
1008
|
-
}
|
|
1009
|
-
var Private;
|
|
1010
|
-
(function (Private) {
|
|
1011
|
-
/**
|
|
1012
|
-
* Sanitize the messages before adding them to the history.
|
|
1013
|
-
*
|
|
1014
|
-
* 1- Make sure the message sequence is not altered:
|
|
1015
|
-
* - tool-call messages should have a corresponding tool-result (and vice-versa)
|
|
1016
|
-
* - tool-approval-request should have a tool-approval-response (and vice-versa)
|
|
1017
|
-
*
|
|
1018
|
-
* 2- Keep only serializable messages by doing a JSON round-trip.
|
|
1019
|
-
* Messages that cannot be serialized are dropped.
|
|
1020
|
-
*/
|
|
1021
|
-
Private.sanitizeModelMessages = (messages) => {
|
|
1022
|
-
const sanitized = [];
|
|
1023
|
-
for (const message of messages) {
|
|
1024
|
-
if (message.role === 'assistant') {
|
|
1025
|
-
let newMessage;
|
|
1026
|
-
if (!Array.isArray(message.content)) {
|
|
1027
|
-
newMessage = message;
|
|
1028
|
-
}
|
|
1029
|
-
else {
|
|
1030
|
-
// Remove assistant message content without a required response.
|
|
1031
|
-
const newContent = [];
|
|
1032
|
-
for (const assistantContent of message.content) {
|
|
1033
|
-
let isContentValid = true;
|
|
1034
|
-
if (assistantContent.type === 'tool-call') {
|
|
1035
|
-
const toolCallId = assistantContent.toolCallId;
|
|
1036
|
-
isContentValid = !!messages.find(msg => msg.role === 'tool' &&
|
|
1037
|
-
Array.isArray(msg.content) &&
|
|
1038
|
-
msg.content.find(content => content.type === 'tool-result' &&
|
|
1039
|
-
content.toolCallId === toolCallId));
|
|
1040
|
-
}
|
|
1041
|
-
else if (assistantContent.type === 'tool-approval-request') {
|
|
1042
|
-
const approvalId = assistantContent.approvalId;
|
|
1043
|
-
isContentValid = !!messages.find(msg => msg.role === 'tool' &&
|
|
1044
|
-
Array.isArray(msg.content) &&
|
|
1045
|
-
msg.content.find(content => content.type === 'tool-approval-response' &&
|
|
1046
|
-
content.approvalId === approvalId));
|
|
1047
|
-
}
|
|
1048
|
-
if (isContentValid) {
|
|
1049
|
-
newContent.push(assistantContent);
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
if (newContent.length) {
|
|
1053
|
-
newMessage = { ...message, content: newContent };
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
if (newMessage) {
|
|
1057
|
-
try {
|
|
1058
|
-
sanitized.push(JSON.parse(JSON.stringify(newMessage)));
|
|
1059
|
-
}
|
|
1060
|
-
catch {
|
|
1061
|
-
// Drop messages that cannot be serialized
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
else if (message.role === 'tool') {
|
|
1066
|
-
// Remove tool message content without request.
|
|
1067
|
-
const newContent = [];
|
|
1068
|
-
for (const toolContent of message.content) {
|
|
1069
|
-
let isContentValid = true;
|
|
1070
|
-
if (toolContent.type === 'tool-result') {
|
|
1071
|
-
const toolCallId = toolContent.toolCallId;
|
|
1072
|
-
isContentValid = !!sanitized.find(msg => msg.role === 'assistant' &&
|
|
1073
|
-
Array.isArray(msg.content) &&
|
|
1074
|
-
msg.content.find(content => content.type === 'tool-call' &&
|
|
1075
|
-
content.toolCallId === toolCallId));
|
|
1076
|
-
}
|
|
1077
|
-
else if (toolContent.type === 'tool-approval-response') {
|
|
1078
|
-
const approvalId = toolContent.approvalId;
|
|
1079
|
-
isContentValid = !!sanitized.find(msg => msg.role === 'assistant' &&
|
|
1080
|
-
Array.isArray(msg.content) &&
|
|
1081
|
-
msg.content.find(content => content.type === 'tool-approval-request' &&
|
|
1082
|
-
content.approvalId === approvalId));
|
|
1083
|
-
}
|
|
1084
|
-
if (isContentValid) {
|
|
1085
|
-
newContent.push(toolContent);
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
if (newContent.length) {
|
|
1089
|
-
try {
|
|
1090
|
-
sanitized.push(JSON.parse(JSON.stringify({ ...message, content: newContent })));
|
|
1091
|
-
}
|
|
1092
|
-
catch {
|
|
1093
|
-
// Drop messages that cannot be serialized
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
else {
|
|
1098
|
-
// Message is a system or user message.
|
|
1099
|
-
sanitized.push(message);
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
return sanitized.length === messages.length ? sanitized : [];
|
|
1103
|
-
};
|
|
1104
|
-
/**
|
|
1105
|
-
* The token to use with the secrets manager, setter and getter.
|
|
1106
|
-
*/
|
|
1107
|
-
let secretsToken;
|
|
1108
|
-
function setToken(value) {
|
|
1109
|
-
secretsToken = value;
|
|
1110
|
-
}
|
|
1111
|
-
Private.setToken = setToken;
|
|
1112
|
-
function getToken() {
|
|
1113
|
-
return secretsToken;
|
|
1114
|
-
}
|
|
1115
|
-
Private.getToken = getToken;
|
|
1116
|
-
})(Private || (Private = {}));
|