@jupyterlite/ai 0.8.0 → 0.9.0-a0
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 +233 -0
- package/lib/agent.js +604 -0
- package/lib/chat-model.d.ts +195 -0
- package/lib/chat-model.js +590 -0
- package/lib/completion/completion-provider.d.ts +83 -0
- package/lib/completion/completion-provider.js +209 -0
- package/lib/completion/index.d.ts +1 -0
- package/lib/completion/index.js +1 -0
- package/lib/components/clear-button.d.ts +18 -0
- package/lib/components/clear-button.js +31 -0
- package/lib/components/index.d.ts +3 -0
- package/lib/components/index.js +3 -0
- package/lib/components/model-select.d.ts +19 -0
- package/lib/components/model-select.js +154 -0
- package/lib/components/stop-button.d.ts +3 -3
- package/lib/components/stop-button.js +8 -9
- package/lib/components/token-usage-display.d.ts +45 -0
- package/lib/components/token-usage-display.js +74 -0
- package/lib/components/tool-select.d.ts +27 -0
- package/lib/components/tool-select.js +130 -0
- package/lib/icons.d.ts +3 -1
- package/lib/icons.js +10 -13
- package/lib/index.d.ts +4 -5
- package/lib/index.js +322 -167
- package/lib/mcp/browser.d.ts +68 -0
- package/lib/mcp/browser.js +132 -0
- package/lib/models/settings-model.d.ts +69 -0
- package/lib/models/settings-model.js +295 -0
- package/lib/providers/built-in-providers.d.ts +9 -0
- package/lib/providers/built-in-providers.js +192 -0
- package/lib/providers/models.d.ts +37 -0
- package/lib/providers/models.js +28 -0
- package/lib/providers/provider-registry.d.ts +94 -0
- package/lib/providers/provider-registry.js +155 -0
- package/lib/tokens.d.ts +157 -86
- package/lib/tokens.js +16 -12
- package/lib/tools/commands.d.ts +11 -0
- package/lib/tools/commands.js +126 -0
- package/lib/tools/file.d.ts +27 -0
- package/lib/tools/file.js +262 -0
- package/lib/tools/notebook.d.ts +40 -0
- package/lib/tools/notebook.js +762 -0
- package/lib/tools/tool-registry.d.ts +35 -0
- package/lib/tools/tool-registry.js +55 -0
- package/lib/widgets/ai-settings.d.ts +39 -0
- package/lib/widgets/ai-settings.js +506 -0
- package/lib/widgets/chat-wrapper.d.ts +144 -0
- package/lib/widgets/chat-wrapper.js +390 -0
- package/lib/widgets/provider-config-dialog.d.ts +13 -0
- package/lib/widgets/provider-config-dialog.js +104 -0
- package/package.json +150 -41
- package/schema/settings-model.json +153 -0
- package/src/agent.ts +800 -0
- package/src/chat-model.ts +770 -0
- package/src/completion/completion-provider.ts +308 -0
- package/src/completion/index.ts +1 -0
- package/src/components/clear-button.tsx +56 -0
- package/src/components/index.ts +3 -0
- package/src/components/model-select.tsx +245 -0
- package/src/components/stop-button.tsx +11 -11
- package/src/components/token-usage-display.tsx +130 -0
- package/src/components/tool-select.tsx +218 -0
- package/src/icons.ts +12 -14
- package/src/index.ts +468 -238
- package/src/mcp/browser.ts +213 -0
- package/src/models/settings-model.ts +409 -0
- package/src/providers/built-in-providers.ts +216 -0
- package/src/providers/models.ts +79 -0
- package/src/providers/provider-registry.ts +189 -0
- package/src/tokens.ts +203 -90
- package/src/tools/commands.ts +151 -0
- package/src/tools/file.ts +307 -0
- package/src/tools/notebook.ts +964 -0
- package/src/tools/tool-registry.ts +63 -0
- package/src/types.d.ts +4 -0
- package/src/widgets/ai-settings.tsx +1100 -0
- package/src/widgets/chat-wrapper.tsx +543 -0
- package/src/widgets/provider-config-dialog.tsx +256 -0
- package/style/base.css +335 -14
- package/style/icons/jupyternaut-lite.svg +1 -1
- package/lib/base-completer.d.ts +0 -49
- package/lib/base-completer.js +0 -14
- package/lib/chat-handler.d.ts +0 -56
- package/lib/chat-handler.js +0 -201
- package/lib/completion-provider.d.ts +0 -34
- package/lib/completion-provider.js +0 -32
- package/lib/default-prompts.d.ts +0 -2
- package/lib/default-prompts.js +0 -31
- package/lib/default-providers/Anthropic/completer.d.ts +0 -12
- package/lib/default-providers/Anthropic/completer.js +0 -46
- package/lib/default-providers/Anthropic/settings-schema.json +0 -70
- package/lib/default-providers/ChromeAI/completer.d.ts +0 -12
- package/lib/default-providers/ChromeAI/completer.js +0 -56
- package/lib/default-providers/ChromeAI/instructions.d.ts +0 -6
- package/lib/default-providers/ChromeAI/instructions.js +0 -42
- package/lib/default-providers/ChromeAI/settings-schema.json +0 -18
- package/lib/default-providers/Gemini/completer.d.ts +0 -12
- package/lib/default-providers/Gemini/completer.js +0 -48
- package/lib/default-providers/Gemini/instructions.d.ts +0 -2
- package/lib/default-providers/Gemini/instructions.js +0 -9
- package/lib/default-providers/Gemini/settings-schema.json +0 -64
- package/lib/default-providers/MistralAI/completer.d.ts +0 -13
- package/lib/default-providers/MistralAI/completer.js +0 -52
- package/lib/default-providers/MistralAI/instructions.d.ts +0 -2
- package/lib/default-providers/MistralAI/instructions.js +0 -18
- package/lib/default-providers/MistralAI/settings-schema.json +0 -75
- package/lib/default-providers/Ollama/completer.d.ts +0 -12
- package/lib/default-providers/Ollama/completer.js +0 -43
- package/lib/default-providers/Ollama/instructions.d.ts +0 -2
- package/lib/default-providers/Ollama/instructions.js +0 -70
- package/lib/default-providers/Ollama/settings-schema.json +0 -143
- package/lib/default-providers/OpenAI/completer.d.ts +0 -12
- package/lib/default-providers/OpenAI/completer.js +0 -43
- package/lib/default-providers/OpenAI/settings-schema.json +0 -628
- package/lib/default-providers/WebLLM/completer.d.ts +0 -21
- package/lib/default-providers/WebLLM/completer.js +0 -127
- package/lib/default-providers/WebLLM/instructions.d.ts +0 -6
- package/lib/default-providers/WebLLM/instructions.js +0 -32
- package/lib/default-providers/WebLLM/settings-schema.json +0 -19
- package/lib/default-providers/index.d.ts +0 -2
- package/lib/default-providers/index.js +0 -179
- package/lib/provider.d.ts +0 -144
- package/lib/provider.js +0 -412
- package/lib/settings/base.json +0 -7
- package/lib/settings/index.d.ts +0 -3
- package/lib/settings/index.js +0 -3
- package/lib/settings/panel.d.ts +0 -226
- package/lib/settings/panel.js +0 -510
- package/lib/settings/textarea.d.ts +0 -2
- package/lib/settings/textarea.js +0 -18
- package/lib/settings/utils.d.ts +0 -2
- package/lib/settings/utils.js +0 -4
- package/lib/types/ai-model.d.ts +0 -24
- package/lib/types/ai-model.js +0 -5
- package/schema/chat.json +0 -28
- package/schema/provider-registry.json +0 -29
- package/schema/system-prompts.json +0 -22
- package/src/base-completer.ts +0 -75
- package/src/chat-handler.ts +0 -262
- package/src/completion-provider.ts +0 -64
- package/src/default-prompts.ts +0 -33
- package/src/default-providers/Anthropic/completer.ts +0 -59
- package/src/default-providers/ChromeAI/completer.ts +0 -73
- package/src/default-providers/ChromeAI/instructions.ts +0 -45
- package/src/default-providers/Gemini/completer.ts +0 -61
- package/src/default-providers/Gemini/instructions.ts +0 -9
- package/src/default-providers/MistralAI/completer.ts +0 -69
- package/src/default-providers/MistralAI/instructions.ts +0 -18
- package/src/default-providers/Ollama/completer.ts +0 -54
- package/src/default-providers/Ollama/instructions.ts +0 -70
- package/src/default-providers/OpenAI/completer.ts +0 -54
- package/src/default-providers/WebLLM/completer.ts +0 -151
- package/src/default-providers/WebLLM/instructions.ts +0 -33
- package/src/default-providers/index.ts +0 -211
- package/src/global.d.ts +0 -9
- package/src/provider.ts +0 -514
- package/src/settings/index.ts +0 -3
- package/src/settings/panel.tsx +0 -773
- package/src/settings/textarea.tsx +0 -33
- package/src/settings/utils.ts +0 -5
- package/src/types/ai-model.ts +0 -37
- package/src/types/service-worker.d.ts +0 -6
package/lib/agent.js
ADDED
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
import { Signal } from '@lumino/signaling';
|
|
2
|
+
import { Agent, Runner, user } from '@openai/agents';
|
|
3
|
+
import { BrowserMCPServerStreamableHttp } from './mcp/browser';
|
|
4
|
+
import { createModel } from './providers/models';
|
|
5
|
+
/**
|
|
6
|
+
* Manages the AI agent lifecycle and execution loop.
|
|
7
|
+
* Provides agent initialization, tool management, MCP server integration,
|
|
8
|
+
* and handles the complete agent execution cycle including tool approvals.
|
|
9
|
+
* Emits events for UI updates instead of directly manipulating the chat interface.
|
|
10
|
+
*/
|
|
11
|
+
export class AgentManager {
|
|
12
|
+
/**
|
|
13
|
+
* Creates a new AgentManager instance.
|
|
14
|
+
* @param options Configuration options for the agent manager
|
|
15
|
+
*/
|
|
16
|
+
constructor(options) {
|
|
17
|
+
this._settingsModel = options.settingsModel;
|
|
18
|
+
this._toolRegistry = options.toolRegistry;
|
|
19
|
+
this._chatProviderRegistry = options.chatProviderRegistry;
|
|
20
|
+
this._selectedToolNames = [];
|
|
21
|
+
this._agent = null;
|
|
22
|
+
this._runner = new Runner({ tracingDisabled: true });
|
|
23
|
+
this._history = [];
|
|
24
|
+
this._mcpServers = [];
|
|
25
|
+
this._isInitializing = false;
|
|
26
|
+
this._controller = null;
|
|
27
|
+
this._pendingApprovals = new Map();
|
|
28
|
+
this._interruptedState = null;
|
|
29
|
+
this._agentEvent = new Signal(this);
|
|
30
|
+
this._mcpConnectionChanged = new Signal(this);
|
|
31
|
+
this._tokenUsage = { inputTokens: 0, outputTokens: 0 };
|
|
32
|
+
this._tokenUsageChanged = new Signal(this);
|
|
33
|
+
// Initialize selected tools to all available tools by default
|
|
34
|
+
if (this._toolRegistry) {
|
|
35
|
+
this._selectedToolNames = Object.keys(this._toolRegistry.tools);
|
|
36
|
+
}
|
|
37
|
+
// Initialize agent on construction
|
|
38
|
+
this._initializeAgent().catch(error => console.warn('Failed to initialize agent in constructor:', error));
|
|
39
|
+
// Listen for settings changes
|
|
40
|
+
this._settingsModel.stateChanged.connect(this._onSettingsChanged, this);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Signal emitted when agent events occur
|
|
44
|
+
*/
|
|
45
|
+
get agentEvent() {
|
|
46
|
+
return this._agentEvent;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Signal emitted when MCP connection status changes
|
|
50
|
+
*/
|
|
51
|
+
get mcpConnectionChanged() {
|
|
52
|
+
return this._mcpConnectionChanged;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Gets the current token usage statistics.
|
|
56
|
+
*/
|
|
57
|
+
get tokenUsage() {
|
|
58
|
+
return this._tokenUsage;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Signal emitted when token usage statistics change.
|
|
62
|
+
*/
|
|
63
|
+
get tokenUsageChanged() {
|
|
64
|
+
return this._tokenUsageChanged;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Sets the selected tools by name and reinitializes the agent.
|
|
68
|
+
* @param toolNames Array of tool names to select
|
|
69
|
+
*/
|
|
70
|
+
setSelectedTools(toolNames) {
|
|
71
|
+
this._selectedToolNames = [...toolNames];
|
|
72
|
+
this._initializeAgent().catch(error => console.warn('Failed to initialize agent on tools change:', error));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Gets the currently selected tools as OpenAI agents tools.
|
|
76
|
+
* @returns Array of selected tools formatted for OpenAI agents
|
|
77
|
+
*/
|
|
78
|
+
get selectedAgentTools() {
|
|
79
|
+
if (!this._toolRegistry) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
const result = [];
|
|
83
|
+
for (const name of this._selectedToolNames) {
|
|
84
|
+
const tool = this._toolRegistry.get(name);
|
|
85
|
+
if (tool) {
|
|
86
|
+
result.push(tool);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Checks if a specific MCP server is connected by server name.
|
|
93
|
+
* @param serverName The name of the MCP server to check
|
|
94
|
+
* @returns True if the server is connected, false otherwise
|
|
95
|
+
*/
|
|
96
|
+
isMCPServerConnected(serverName) {
|
|
97
|
+
return this._mcpServers.some(server => server.name === serverName);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Checks if the current configuration is valid for agent operations.
|
|
101
|
+
* Uses the provider registry to determine if an API key is required.
|
|
102
|
+
* @returns True if the configuration is valid, false otherwise
|
|
103
|
+
*/
|
|
104
|
+
hasValidConfig() {
|
|
105
|
+
const activeProvider = this._settingsModel.getActiveProvider();
|
|
106
|
+
if (!activeProvider) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
if (!activeProvider.model) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
if (this._chatProviderRegistry) {
|
|
113
|
+
const providerInfo = this._chatProviderRegistry.getProviderInfo(activeProvider.provider);
|
|
114
|
+
if (providerInfo?.requiresApiKey) {
|
|
115
|
+
return !!activeProvider.apiKey;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Clears conversation history and resets agent state.
|
|
122
|
+
* Removes all conversation history, pending approvals, and interrupted state.
|
|
123
|
+
*/
|
|
124
|
+
clearHistory() {
|
|
125
|
+
this._history = [];
|
|
126
|
+
this._runner = new Runner();
|
|
127
|
+
this._pendingApprovals.clear();
|
|
128
|
+
this._interruptedState = null;
|
|
129
|
+
// Reset token usage
|
|
130
|
+
this._tokenUsage = { inputTokens: 0, outputTokens: 0 };
|
|
131
|
+
this._tokenUsageChanged.emit(this._tokenUsage);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Stops the current streaming response by aborting the request.
|
|
135
|
+
*/
|
|
136
|
+
stopStreaming() {
|
|
137
|
+
this._controller?.abort();
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Generates AI response to user message using the agent.
|
|
141
|
+
* Handles the complete execution cycle including tool calls and approvals.
|
|
142
|
+
* @param message The user message to respond to (may include processed attachment content)
|
|
143
|
+
*/
|
|
144
|
+
async generateResponse(message) {
|
|
145
|
+
const config = this._settingsModel.config;
|
|
146
|
+
this._controller = new AbortController();
|
|
147
|
+
try {
|
|
148
|
+
// Ensure we have an agent
|
|
149
|
+
if (!this._agent) {
|
|
150
|
+
await this._initializeAgent();
|
|
151
|
+
}
|
|
152
|
+
if (!this._agent) {
|
|
153
|
+
throw new Error('Failed to initialize agent');
|
|
154
|
+
}
|
|
155
|
+
const shouldUseTools = config.toolsEnabled &&
|
|
156
|
+
this._selectedToolNames.length > 0 &&
|
|
157
|
+
this._toolRegistry &&
|
|
158
|
+
Object.keys(this._toolRegistry.tools).length > 0 &&
|
|
159
|
+
this._supportsToolCalling();
|
|
160
|
+
// Add user message to history
|
|
161
|
+
this._history.push(user(message));
|
|
162
|
+
// Main agentic loop
|
|
163
|
+
let result = await this._runner.run(this._agent, this._history, {
|
|
164
|
+
stream: true,
|
|
165
|
+
signal: this._controller.signal,
|
|
166
|
+
...(shouldUseTools && { maxTurns: config.maxTurns })
|
|
167
|
+
});
|
|
168
|
+
await this._processRunResult(result);
|
|
169
|
+
let hasInterruptions = result.interruptions && result.interruptions.length > 0;
|
|
170
|
+
while (hasInterruptions) {
|
|
171
|
+
this._interruptedState = result;
|
|
172
|
+
const interruptions = result.interruptions;
|
|
173
|
+
if (interruptions.length > 1) {
|
|
174
|
+
await this._handleGroupedToolApprovals(interruptions);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
await this._handleSingleToolApproval(interruptions[0]);
|
|
178
|
+
}
|
|
179
|
+
// Wait for all approvals to be resolved
|
|
180
|
+
while (this._pendingApprovals.size > 0) {
|
|
181
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
182
|
+
}
|
|
183
|
+
// Continue execution
|
|
184
|
+
result = await this._runner.run(this._agent, result.state, {
|
|
185
|
+
stream: true,
|
|
186
|
+
signal: this._controller.signal,
|
|
187
|
+
maxTurns: config.maxTurns
|
|
188
|
+
});
|
|
189
|
+
await this._processRunResult(result);
|
|
190
|
+
hasInterruptions =
|
|
191
|
+
result.interruptions && result.interruptions.length > 0;
|
|
192
|
+
}
|
|
193
|
+
// Clear interrupted state
|
|
194
|
+
this._interruptedState = null;
|
|
195
|
+
this._history = result.history;
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
this._agentEvent.emit({
|
|
199
|
+
type: 'error',
|
|
200
|
+
data: { error: error }
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
this._controller = null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Approves a tool call by interruption ID.
|
|
209
|
+
* @param interruptionId The interruption ID to approve
|
|
210
|
+
*/
|
|
211
|
+
async approveToolCall(interruptionId) {
|
|
212
|
+
const pending = this._pendingApprovals.get(interruptionId);
|
|
213
|
+
if (!pending) {
|
|
214
|
+
console.warn(`No pending approval found for interruption ${interruptionId}`);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (this._interruptedState) {
|
|
218
|
+
this._interruptedState.state.approve(pending.interruption);
|
|
219
|
+
}
|
|
220
|
+
pending.approved = true;
|
|
221
|
+
this._pendingApprovals.delete(interruptionId);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Rejects a tool call by interruption ID.
|
|
225
|
+
* @param interruptionId The interruption ID to reject
|
|
226
|
+
*/
|
|
227
|
+
async rejectToolCall(interruptionId) {
|
|
228
|
+
const pending = this._pendingApprovals.get(interruptionId);
|
|
229
|
+
if (!pending) {
|
|
230
|
+
console.warn(`No pending approval found for interruption ${interruptionId}`);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (this._interruptedState) {
|
|
234
|
+
this._interruptedState.state.reject(pending.interruption);
|
|
235
|
+
}
|
|
236
|
+
pending.approved = false;
|
|
237
|
+
this._pendingApprovals.delete(interruptionId);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Approves all tools in a group by group ID.
|
|
241
|
+
* @param groupId The group ID containing the tool calls
|
|
242
|
+
* @param interruptionIds Array of interruption IDs to approve
|
|
243
|
+
*/
|
|
244
|
+
async approveGroupedToolCalls(groupId, interruptionIds) {
|
|
245
|
+
for (const interruptionId of interruptionIds) {
|
|
246
|
+
const pending = this._pendingApprovals.get(interruptionId);
|
|
247
|
+
if (pending && pending.groupId === groupId) {
|
|
248
|
+
if (this._interruptedState) {
|
|
249
|
+
this._interruptedState.state.approve(pending.interruption);
|
|
250
|
+
}
|
|
251
|
+
pending.approved = true;
|
|
252
|
+
this._pendingApprovals.delete(interruptionId);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Rejects all tools in a group by group ID.
|
|
258
|
+
* @param groupId The group ID containing the tool calls
|
|
259
|
+
* @param interruptionIds Array of interruption IDs to reject
|
|
260
|
+
*/
|
|
261
|
+
async rejectGroupedToolCalls(groupId, interruptionIds) {
|
|
262
|
+
for (const interruptionId of interruptionIds) {
|
|
263
|
+
const pending = this._pendingApprovals.get(interruptionId);
|
|
264
|
+
if (pending && pending.groupId === groupId) {
|
|
265
|
+
if (this._interruptedState) {
|
|
266
|
+
this._interruptedState.state.reject(pending.interruption);
|
|
267
|
+
}
|
|
268
|
+
pending.approved = false;
|
|
269
|
+
this._pendingApprovals.delete(interruptionId);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Handles settings changes and reinitializes the agent.
|
|
275
|
+
*/
|
|
276
|
+
_onSettingsChanged() {
|
|
277
|
+
this._initializeAgent().catch(error => console.warn('Failed to initialize agent on settings change:', error));
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Initializes the AI agent with current settings and tools.
|
|
281
|
+
* Sets up the agent with model configuration, tools, and MCP servers.
|
|
282
|
+
*/
|
|
283
|
+
async _initializeAgent() {
|
|
284
|
+
if (this._isInitializing) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
this._isInitializing = true;
|
|
288
|
+
try {
|
|
289
|
+
const config = this._settingsModel.config;
|
|
290
|
+
const model = this._createModel();
|
|
291
|
+
const shouldUseTools = config.toolsEnabled &&
|
|
292
|
+
this._selectedToolNames.length > 0 &&
|
|
293
|
+
this._toolRegistry &&
|
|
294
|
+
Object.keys(this._toolRegistry.tools).length > 0 &&
|
|
295
|
+
this._supportsToolCalling();
|
|
296
|
+
await this._initializeMCPServers();
|
|
297
|
+
const tools = shouldUseTools ? this.selectedAgentTools : [];
|
|
298
|
+
const mcpServers = this._mcpServers.filter(server => server !== null);
|
|
299
|
+
this._agent = new Agent({
|
|
300
|
+
name: 'Assistant',
|
|
301
|
+
instructions: shouldUseTools
|
|
302
|
+
? this._getEnhancedSystemPrompt(config.systemPrompt || '')
|
|
303
|
+
: config.systemPrompt || 'You are a helpful assistant.',
|
|
304
|
+
model: model,
|
|
305
|
+
mcpServers,
|
|
306
|
+
tools,
|
|
307
|
+
...(config.temperature && {
|
|
308
|
+
modelSettings: {
|
|
309
|
+
temperature: config.temperature,
|
|
310
|
+
...(config.maxTokens && { maxTokens: config.maxTokens })
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
console.warn('Failed to initialize agent:', error);
|
|
317
|
+
this._agent = null;
|
|
318
|
+
}
|
|
319
|
+
finally {
|
|
320
|
+
this._isInitializing = false;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Initializes MCP (Model Context Protocol) servers based on current settings.
|
|
325
|
+
* Closes existing servers and connects to enabled servers from configuration.
|
|
326
|
+
*/
|
|
327
|
+
async _initializeMCPServers() {
|
|
328
|
+
const config = this._settingsModel.config;
|
|
329
|
+
const enabledServers = config.mcpServers.filter(server => server.enabled);
|
|
330
|
+
let connectionChanged = false;
|
|
331
|
+
// Close existing servers
|
|
332
|
+
for (const server of this._mcpServers) {
|
|
333
|
+
try {
|
|
334
|
+
await server.close();
|
|
335
|
+
connectionChanged = true;
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
console.warn('Error closing MCP server:', error);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
this._mcpServers = [];
|
|
342
|
+
// Initialize new servers
|
|
343
|
+
for (const serverConfig of enabledServers) {
|
|
344
|
+
try {
|
|
345
|
+
const mcpServer = new BrowserMCPServerStreamableHttp({
|
|
346
|
+
url: serverConfig.url,
|
|
347
|
+
name: serverConfig.name
|
|
348
|
+
});
|
|
349
|
+
await mcpServer.connect();
|
|
350
|
+
this._mcpServers.push(mcpServer);
|
|
351
|
+
connectionChanged = true;
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
console.warn(`Failed to connect to MCP server "${serverConfig.name}" at ${serverConfig.url}:`, error);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// Emit connection change signal if there were any changes
|
|
358
|
+
if (connectionChanged) {
|
|
359
|
+
this._mcpConnectionChanged.emit(this._mcpServers.length > 0);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Processes the result stream from agent execution.
|
|
364
|
+
* Handles message streaming, tool calls, and emits appropriate events.
|
|
365
|
+
* @param result The async iterable result from agent execution
|
|
366
|
+
*/
|
|
367
|
+
async _processRunResult(result) {
|
|
368
|
+
let fullResponse = '';
|
|
369
|
+
let currentMessageId = null;
|
|
370
|
+
for await (const event of result) {
|
|
371
|
+
if (event.type === 'raw_model_stream_event') {
|
|
372
|
+
const data = event.data;
|
|
373
|
+
if (data.type === 'response_started') {
|
|
374
|
+
currentMessageId = `msg-${Date.now()}-${Math.random()}`;
|
|
375
|
+
fullResponse = '';
|
|
376
|
+
this._agentEvent.emit({
|
|
377
|
+
type: 'message_start',
|
|
378
|
+
data: { messageId: currentMessageId }
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
else if (data.type === 'output_text_delta') {
|
|
382
|
+
if (currentMessageId) {
|
|
383
|
+
const chunk = data.delta || '';
|
|
384
|
+
fullResponse += chunk;
|
|
385
|
+
this._agentEvent.emit({
|
|
386
|
+
type: 'message_chunk',
|
|
387
|
+
data: {
|
|
388
|
+
messageId: currentMessageId,
|
|
389
|
+
chunk,
|
|
390
|
+
fullContent: fullResponse
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
else if (data.type === 'response_done') {
|
|
396
|
+
if (currentMessageId) {
|
|
397
|
+
this._agentEvent.emit({
|
|
398
|
+
type: 'message_complete',
|
|
399
|
+
data: {
|
|
400
|
+
messageId: currentMessageId,
|
|
401
|
+
content: fullResponse
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
currentMessageId = null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
else if (data.type === 'model') {
|
|
408
|
+
const modelEvent = data.event;
|
|
409
|
+
if (modelEvent.type === 'tool-call') {
|
|
410
|
+
this._handleToolCallStart(modelEvent);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
else if (event.type === 'run_item_stream_event') {
|
|
415
|
+
if (event.name === 'tool_output') {
|
|
416
|
+
this._handleToolOutput(event);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Handles the start of a tool call from the model event.
|
|
423
|
+
* @param modelEvent The model event containing tool call information
|
|
424
|
+
*/
|
|
425
|
+
_handleToolCallStart(modelEvent) {
|
|
426
|
+
const toolCallId = modelEvent.toolCallId;
|
|
427
|
+
const toolName = modelEvent.toolName;
|
|
428
|
+
const toolInput = modelEvent.input;
|
|
429
|
+
let parsedToolInput;
|
|
430
|
+
try {
|
|
431
|
+
parsedToolInput = JSON.parse(toolInput);
|
|
432
|
+
}
|
|
433
|
+
catch (error) {
|
|
434
|
+
parsedToolInput = {};
|
|
435
|
+
}
|
|
436
|
+
this._agentEvent.emit({
|
|
437
|
+
type: 'tool_call_start',
|
|
438
|
+
data: {
|
|
439
|
+
callId: toolCallId,
|
|
440
|
+
toolName,
|
|
441
|
+
input: parsedToolInput
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Handles tool execution output and completion.
|
|
447
|
+
* @param event The tool output event containing result information
|
|
448
|
+
*/
|
|
449
|
+
_handleToolOutput(event) {
|
|
450
|
+
const toolEvent = event;
|
|
451
|
+
const toolCallOutput = toolEvent.item;
|
|
452
|
+
const callId = toolCallOutput.rawItem.callId;
|
|
453
|
+
const resultText = typeof toolCallOutput.output === 'string'
|
|
454
|
+
? toolCallOutput.output
|
|
455
|
+
: JSON.stringify(toolCallOutput.output, null, 2);
|
|
456
|
+
const isError = toolCallOutput.rawItem.type === 'function_call_result' &&
|
|
457
|
+
toolCallOutput.rawItem.error;
|
|
458
|
+
const toolName = toolCallOutput.rawItem.type === 'function_call_result'
|
|
459
|
+
? toolCallOutput.rawItem.name
|
|
460
|
+
: 'Unknown Tool';
|
|
461
|
+
this._agentEvent.emit({
|
|
462
|
+
type: 'tool_call_complete',
|
|
463
|
+
data: {
|
|
464
|
+
callId,
|
|
465
|
+
toolName,
|
|
466
|
+
output: resultText,
|
|
467
|
+
isError
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Handles approval request for a single tool call.
|
|
473
|
+
* @param interruption The tool approval interruption item
|
|
474
|
+
*/
|
|
475
|
+
async _handleSingleToolApproval(interruption) {
|
|
476
|
+
const toolName = interruption.rawItem?.name || 'Unknown Tool';
|
|
477
|
+
const toolInput = interruption.rawItem?.arguments || {};
|
|
478
|
+
const interruptionId = `int-${Date.now()}-${Math.random()}`;
|
|
479
|
+
const callId = interruption.rawItem?.callId;
|
|
480
|
+
this._pendingApprovals.set(interruptionId, { interruption });
|
|
481
|
+
this._agentEvent.emit({
|
|
482
|
+
type: 'tool_approval_required',
|
|
483
|
+
data: {
|
|
484
|
+
interruptionId,
|
|
485
|
+
toolName,
|
|
486
|
+
toolInput,
|
|
487
|
+
callId
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Handles approval requests for multiple grouped tool calls.
|
|
493
|
+
* @param interruptions Array of tool approval interruption items
|
|
494
|
+
*/
|
|
495
|
+
async _handleGroupedToolApprovals(interruptions) {
|
|
496
|
+
const groupId = `group-${Date.now()}-${Math.random()}`;
|
|
497
|
+
const approvals = interruptions.map(interruption => {
|
|
498
|
+
const toolName = interruption.rawItem?.name || 'Unknown Tool';
|
|
499
|
+
const toolInput = interruption.rawItem?.arguments || {};
|
|
500
|
+
const interruptionId = `int-${Date.now()}-${Math.random()}`;
|
|
501
|
+
this._pendingApprovals.set(interruptionId, { interruption, groupId });
|
|
502
|
+
return {
|
|
503
|
+
interruptionId,
|
|
504
|
+
toolName,
|
|
505
|
+
toolInput
|
|
506
|
+
};
|
|
507
|
+
});
|
|
508
|
+
this._agentEvent.emit({
|
|
509
|
+
type: 'grouped_approval_required',
|
|
510
|
+
data: {
|
|
511
|
+
groupId,
|
|
512
|
+
approvals
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Checks if the current provider supports tool calling.
|
|
518
|
+
* @returns True if the provider supports tool calling, false otherwise
|
|
519
|
+
*/
|
|
520
|
+
_supportsToolCalling() {
|
|
521
|
+
const activeProvider = this._settingsModel.getActiveProvider();
|
|
522
|
+
if (!activeProvider || !this._chatProviderRegistry) {
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
const providerInfo = this._chatProviderRegistry.getProviderInfo(activeProvider.provider);
|
|
526
|
+
// Default to true if supportsToolCalling is not specified
|
|
527
|
+
return providerInfo?.supportsToolCalling !== false;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Creates a model instance based on current settings.
|
|
531
|
+
* @returns The configured model instance for the agent
|
|
532
|
+
*/
|
|
533
|
+
_createModel() {
|
|
534
|
+
const activeProvider = this._settingsModel.getActiveProvider();
|
|
535
|
+
if (!activeProvider) {
|
|
536
|
+
throw new Error('No active provider configured');
|
|
537
|
+
}
|
|
538
|
+
const provider = activeProvider.provider;
|
|
539
|
+
const model = activeProvider.model;
|
|
540
|
+
const apiKey = this._settingsModel.getApiKey(activeProvider.id);
|
|
541
|
+
return createModel({
|
|
542
|
+
provider,
|
|
543
|
+
model,
|
|
544
|
+
apiKey
|
|
545
|
+
}, this._chatProviderRegistry);
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Enhances the base system prompt with tool usage guidelines.
|
|
549
|
+
* @param baseSystemPrompt The base system prompt from settings
|
|
550
|
+
* @returns The enhanced system prompt with tool usage instructions
|
|
551
|
+
*/
|
|
552
|
+
_getEnhancedSystemPrompt(baseSystemPrompt) {
|
|
553
|
+
const progressReportingPrompt = `
|
|
554
|
+
|
|
555
|
+
IMPORTANT: Follow this message flow pattern for better user experience:
|
|
556
|
+
|
|
557
|
+
1. FIRST: Explain what you're going to do and your approach
|
|
558
|
+
2. THEN: Execute tools (these will show automatically with step numbers)
|
|
559
|
+
3. FINALLY: Provide a concise summary of what was accomplished
|
|
560
|
+
|
|
561
|
+
Example flow:
|
|
562
|
+
- "I'll help you create a notebook with example cells. Let me first create the file structure, then add Python and Markdown cells."
|
|
563
|
+
- [Tool executions happen with automatic step display]
|
|
564
|
+
- "Successfully created your notebook with 3 cells: a title, code example, and visualization cell."
|
|
565
|
+
|
|
566
|
+
Guidelines:
|
|
567
|
+
- Start responses with your plan/approach before tool execution
|
|
568
|
+
- Let the system handle tool execution display (don't duplicate details)
|
|
569
|
+
- End with a brief summary of accomplishments
|
|
570
|
+
- Use natural, conversational tone throughout
|
|
571
|
+
|
|
572
|
+
COMMAND DISCOVERY: When you want to execute JupyterLab commands, ALWAYS use the 'discover_commands' tool first to find available commands and their metadata. This ensures you have complete information about command IDs, descriptions, and required arguments before attempting to execute them. Only after discovering the available commands should you use the 'execute_command' tool with the correct command ID and arguments.
|
|
573
|
+
|
|
574
|
+
TOOL SELECTION GUIDELINES:
|
|
575
|
+
- For file operations (create, read, write, modify files and directories): Use dedicated file manipulation tools
|
|
576
|
+
- For general JupyterLab UI interactions (opening panels, running commands, navigating interface): Use the general command tool (execute_command)
|
|
577
|
+
- Examples of file operations: Creating notebooks, editing code files, managing project structure
|
|
578
|
+
- Examples of UI interactions: Opening terminal, switching tabs, running notebook cells, accessing menus
|
|
579
|
+
|
|
580
|
+
NOTEBOOK CELL CONTENT MANAGEMENT:
|
|
581
|
+
- After setting the content of a cell using the 'set_cell_content' tool, ALWAYS follow up by showing the cell diff
|
|
582
|
+
- First, use the 'discover_commands' tool to check for available commands that can show cell diffs (look for commands containing 'diff', 'show-cell-diff', or similar)
|
|
583
|
+
- Then use the 'execute_command' tool with the appropriate command ID to display the changes made to the cell
|
|
584
|
+
- This helps users visualize exactly what changed in their notebook cells and provides transparency about modifications`;
|
|
585
|
+
return baseSystemPrompt + progressReportingPrompt;
|
|
586
|
+
}
|
|
587
|
+
// Private attributes
|
|
588
|
+
_settingsModel;
|
|
589
|
+
_toolRegistry;
|
|
590
|
+
_chatProviderRegistry;
|
|
591
|
+
_selectedToolNames;
|
|
592
|
+
_agent;
|
|
593
|
+
_runner;
|
|
594
|
+
_history;
|
|
595
|
+
_mcpServers;
|
|
596
|
+
_isInitializing;
|
|
597
|
+
_controller;
|
|
598
|
+
_pendingApprovals;
|
|
599
|
+
_interruptedState;
|
|
600
|
+
_agentEvent;
|
|
601
|
+
_mcpConnectionChanged;
|
|
602
|
+
_tokenUsage;
|
|
603
|
+
_tokenUsageChanged;
|
|
604
|
+
}
|