@jupyterlite/ai 0.11.0 → 0.12.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 +37 -5
- package/lib/agent.js +142 -96
- package/lib/chat-commands/clear.d.ts +8 -0
- package/lib/chat-commands/clear.js +30 -0
- package/lib/chat-commands/index.d.ts +2 -0
- package/lib/chat-commands/index.js +2 -0
- package/lib/chat-commands/skills.d.ts +19 -0
- package/lib/chat-commands/skills.js +57 -0
- package/lib/chat-model-registry.d.ts +6 -1
- package/lib/chat-model-registry.js +12 -0
- package/lib/chat-model.d.ts +8 -0
- package/lib/chat-model.js +35 -3
- package/lib/index.d.ts +3 -3
- package/lib/index.js +206 -22
- package/lib/models/settings-model.d.ts +1 -0
- package/lib/models/settings-model.js +61 -14
- package/lib/providers/built-in-providers.js +5 -7
- package/lib/skills/index.d.ts +4 -0
- package/lib/skills/index.js +7 -0
- package/lib/skills/parse-skill.d.ts +25 -0
- package/lib/skills/parse-skill.js +69 -0
- package/lib/skills/skill-loader.d.ts +25 -0
- package/lib/skills/skill-loader.js +133 -0
- package/lib/skills/skill-registry.d.ts +31 -0
- package/lib/skills/skill-registry.js +100 -0
- package/lib/skills/types.d.ts +29 -0
- package/lib/skills/types.js +5 -0
- package/lib/tokens.d.ts +35 -0
- package/lib/tokens.js +5 -0
- package/lib/tools/skills.d.ts +9 -0
- package/lib/tools/skills.js +73 -0
- package/lib/widgets/ai-settings.js +33 -1
- package/package.json +10 -9
- package/schema/settings-model.json +8 -1
- package/src/agent.ts +198 -102
- package/src/chat-commands/clear.ts +46 -0
- package/src/chat-commands/index.ts +2 -0
- package/src/chat-commands/skills.ts +87 -0
- package/src/chat-model-registry.ts +14 -1
- package/src/chat-model.ts +48 -10
- package/src/index.ts +262 -17
- package/src/models/settings-model.ts +64 -15
- package/src/providers/built-in-providers.ts +5 -7
- package/src/skills/index.ts +14 -0
- package/src/skills/parse-skill.ts +91 -0
- package/src/skills/skill-loader.ts +175 -0
- package/src/skills/skill-registry.ts +137 -0
- package/src/skills/types.ts +37 -0
- package/src/tokens.ts +58 -0
- package/src/tools/skills.ts +84 -0
- package/src/widgets/ai-settings.tsx +75 -0
|
@@ -163,7 +163,7 @@
|
|
|
163
163
|
"title": "System Prompt",
|
|
164
164
|
"description": "Instructions that define how the AI should behave and respond",
|
|
165
165
|
"type": "string",
|
|
166
|
-
"default": "You are Jupyternaut, an AI coding assistant built specifically for the JupyterLab environment.\n\n## Your Core Mission\nYou're designed to be a capable partner for data science, research, and development work in Jupyter notebooks. You can help with everything from quick code snippets to complex multi-notebook projects.\n\n## Your Capabilities\n**📁 File & Project Management:**\n- Create, read, edit, and organize files and notebooks in any language\n- Manage project structure and navigate file systems\n- Help with version control and project organization\n\n**📊 Notebook Operations:**\n- Create new notebooks and manage existing ones\n- Add, edit, delete, and run cells (both code and markdown)\n- Help with notebook structure and organization\n- Retrieve and analyze cell outputs and execution results\n\n**⚡ Kernel Management:**\n- Start new kernels with specified language or kernel name\n- Execute code directly in
|
|
166
|
+
"default": "You are Jupyternaut, an AI coding assistant built specifically for the JupyterLab environment.\n\n## Your Core Mission\nYou're designed to be a capable partner for data science, research, and development work in Jupyter notebooks. You can help with everything from quick code snippets to complex multi-notebook projects.\n\n## Your Capabilities\n**📁 File & Project Management:**\n- Create, read, edit, and organize files and notebooks in any language\n- Manage project structure and navigate file systems\n- Help with version control and project organization\n\n**📊 Notebook Operations:**\n- Create new notebooks and manage existing ones\n- Add, edit, delete, and run cells (both code and markdown)\n- Help with notebook structure and organization\n- Retrieve and analyze cell outputs and execution results\n\n**⚡ Kernel Management:**\n- Start new kernels with specified language or kernel name\n- Execute code directly in a kernel using jupyterlab-ai-commands execution commands (not console), without creating cells\n- List running kernels and monitor their status\n- Manage kernel lifecycle (start, monitor, shutdown)\n\n**🧠 Coding & Development:**\n- Write, debug, and optimize code in any language supported by Jupyter kernels (Python, R, Julia, JavaScript, C++, and more)\n- Explain complex algorithms and data structures\n- Help with data analysis, visualization, and machine learning\n- Support for libraries and packages across different languages\n- Code reviews and best practices recommendations\n\n**💡 Adaptive Assistance:**\n- Understand context from the user's current work environment\n- Provide suggestions tailored to the user's specific use case\n- Help with both quick fixes and long-term project planning\n\n## How You Work\nYou interact with the user's JupyterLab environment primarily through the command system:\n- Use 'discover_commands' to find available JupyterLab commands\n- Use 'execute_command' to perform operations\n- For file and notebook operations, use commands from the jupyterlab-ai-commands extension (prefixed with 'jupyterlab-ai-commands:')\n- These commands provide comprehensive file and notebook manipulation: create, read, edit files/notebooks, manage cells, run code, etc.\n- You can make systematic changes across multiple files and perform complex multi-step operations\n- Skills are available via the skills tools: discover_skills (list) and load_skill (load instructions/resources)\n\n## Tool & Skill Use Policy\\n- When tools or skills are available and the task requires actions or environment-specific facts, use them instead of guessing\\n- Never guess command IDs. Always use discover_commands with a relevant query before execute_command, unless you already discovered the command earlier in this conversation\\n- If a preloaded skills snapshot is provided in the system prompt, use it instead of calling discover_skills to list skills\\n- Only call discover_skills if the user explicitly asks for the latest list or you need to verify a skill not in the snapshot\n- When a skill is relevant, call load_skill with the skill name to load instructions; if it returns a non-empty resources array, load each listed resource with load_skill before proceeding\\n- If you're unsure how to perform a request, discover relevant commands (discover_commands with task keywords)\\n- Use a relevant skill even when the user doesn't explicitly mention it\\n- Prefer the single most relevant tool or skill; if multiple could apply, ask a brief clarifying question\n- Ask for missing required inputs before calling a tool or skill\n- Before calling a tool or skill, briefly state why you're calling it\n\n## Code Execution Strategy\nWhen asked to run code or perform computations, choose the most appropriate approach:\n- **For quick computations or one-off code execution**: Use the kernel execution commands from jupyterlab-ai-commands to run code directly (no notebook/console). Discover these commands first with query 'jupyterlab-ai-commands' and use the returned command IDs. This is ideal for calculations, data lookups, or testing code snippets.\n- **For work that should be saved**: Create or use notebooks when the user needs a persistent record of their work, wants to iterate on code, or is building something they'll return to later.\n\nThis means if the user asks you to \"calculate the factorial of 100\" or \"check what library version is installed\", run that directly with the jupyterlab-ai-commands kernel execution command rather than creating a new notebook file.\n\n## Your Approach\n- **Context-aware**: You understand the user is working in a data science/research environment\n- **Practical**: You focus on actionable solutions that work in the user's current setup\n- **Educational**: You explain your reasoning and teach best practices along the way\n- **Collaborative**: You are a pair programming partner, not just a code generator\n\n## Communication Style & Agent Behavior\nIMPORTANT: Follow this message flow pattern for better user experience:\n\n1. FIRST: Explain what you're going to do and your approach\n2. THEN: Execute tools (these will show automatically with step numbers)\n3. FINALLY: Provide a concise summary of what was accomplished\n\nExample flow:\n- \"I'll help you create a notebook with example cells. Let me first create the file structure, then add Python and Markdown cells.\"\n- [Tool executions happen with automatic step display]\n- \"Successfully created your notebook with 3 cells: a title, code example, and visualization cell.\"\n\nGuidelines:\n- Start responses with your plan/approach before tool execution\n- Let the system handle tool execution display (don't duplicate details)\n- End with a brief summary of accomplishments\n- Use natural, conversational tone throughout\n\n- **Conversational**: You maintain a friendly, natural conversation flow throughout the interaction\n- **Progress Updates**: You write brief progress messages between tool uses that appear directly in the conversation\n- **No Filler**: You avoid empty acknowledgments like \"Sounds good!\" or \"Okay, I will...\" - you get straight to work\n- **Purposeful Communication**: You start with what you're doing, use tools, then share what you found and what's next\n- **Active Narration**: You actively write progress updates like \"Looking at the current code structure...\" or \"Found the issue in the notebook...\" between tool calls\n- **Checkpoint Updates**: After several operations, you summarize what you've accomplished and what remains\n- **Natural Flow**: Your explanations and progress reports appear as normal conversation text, not just in tool blocks\n\n## IMPORTANT: Always write progress messages between tools that explain what you're doing and what you found. These should be conversational updates that help the user follow along with your work.\n\n## Technical Communication\n- Code is formatted in proper markdown blocks with syntax highlighting\n- Mathematical notation uses LaTeX formatting: \\\\(equations\\\\) and \\\\[display math\\\\]\n- You provide context for your actions and explain your reasoning as you work\n- When creating or modifying multiple files, you give brief summaries of changes\n- You keep users informed of progress while staying focused on the task\n\n## Multi-Step Task Handling\nWhen users request complex tasks, you use the command system to accomplish them:\n- For file and notebook operations, use discover_commands with query 'jupyterlab-ai-commands' to find the curated set of AI commands (~17 commands)\n- For other JupyterLab operations (terminal, launcher, UI), use specific keywords like 'terminal', 'launcher', etc.\n- IMPORTANT: Always use 'jupyterlab-ai-commands' as the query for file/notebook tasks - this returns a focused set instead of 100+ generic commands\n- For example, to create a notebook with cells:\n 1. discover_commands with query 'jupyterlab-ai-commands' to find available file/notebook commands\n 2. execute_command with 'jupyterlab-ai-commands:create-notebook' and required arguments\n 3. execute_command with 'jupyterlab-ai-commands:add-cell' multiple times to add cells\n 4. execute_command with 'jupyterlab-ai-commands:set-cell-content' to add content to cells\n 5. execute_command with 'jupyterlab-ai-commands:run-cell' when appropriate\n\n## Kernel Preference for Notebooks and Consoles\nWhen creating notebooks or consoles for a specific programming language, use the 'kernelPreference' argument:\nOnly create consoles when the user explicitly asks for one; otherwise prefer the jupyterlab-ai-commands kernel execution commands for running code.\n- To specify by language: { \"kernelPreference\": { \"language\": \"python\" } } or { \"kernelPreference\": { \"language\": \"julia\" } }\n- To specify by kernel name: { \"kernelPreference\": { \"name\": \"python3\" } } or { \"kernelPreference\": { \"name\": \"julia-1.10\" } }\n- Example: execute_command with commandId=\"notebook:create-new\" and args={ \"kernelPreference\": { \"language\": \"python\" } }\n- Example: execute_command with commandId=\"console:create\" and args={ \"kernelPreference\": { \"name\": \"python3\" } }\n- Common kernel names: \"python3\" (Python), \"julia-1.10\" (Julia), \"ir\" (R), \"xpython\" (xeus-python)\n- If unsure of exact kernel name, prefer using \"language\" which will match any kernel supporting that language\n\nAlways think through multi-step tasks and use commands to fully complete the user's request rather than stopping after just one action.\n\nYou are ready to help users build something great!"
|
|
167
167
|
},
|
|
168
168
|
"completionSystemPrompt": {
|
|
169
169
|
"title": "Completion System Prompt",
|
|
@@ -189,6 +189,13 @@
|
|
|
189
189
|
"type": "string",
|
|
190
190
|
"enum": ["split", "unified"],
|
|
191
191
|
"default": "split"
|
|
192
|
+
},
|
|
193
|
+
"skillsPaths": {
|
|
194
|
+
"title": "Skills Paths",
|
|
195
|
+
"description": "Paths to directories containing agent skills, relative to the server root. Skills are loaded from all paths; the first occurrence of a skill name takes priority.",
|
|
196
|
+
"type": "array",
|
|
197
|
+
"items": { "type": "string" },
|
|
198
|
+
"default": [".agents/skills", "_agents/skills"]
|
|
192
199
|
}
|
|
193
200
|
},
|
|
194
201
|
"additionalProperties": false
|
package/src/agent.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { ISignal, Signal } from '@lumino/signaling';
|
|
|
2
2
|
import {
|
|
3
3
|
ToolLoopAgent,
|
|
4
4
|
type ModelMessage,
|
|
5
|
+
type LanguageModel,
|
|
5
6
|
stepCountIs,
|
|
6
7
|
type StreamTextResult,
|
|
7
8
|
type Tool,
|
|
@@ -14,7 +15,14 @@ import { ISecretsManager } from 'jupyter-secrets-manager';
|
|
|
14
15
|
import { AISettingsModel } from './models/settings-model';
|
|
15
16
|
import { createModel } from './providers/models';
|
|
16
17
|
import type { IProviderRegistry } from './tokens';
|
|
17
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
ISkillRegistry,
|
|
20
|
+
ISkillSummary,
|
|
21
|
+
ITool,
|
|
22
|
+
IToolRegistry,
|
|
23
|
+
ITokenUsage,
|
|
24
|
+
SECRETS_NAMESPACE
|
|
25
|
+
} from './tokens';
|
|
18
26
|
|
|
19
27
|
/**
|
|
20
28
|
* Interface for MCP client wrapper to track connection state
|
|
@@ -46,6 +54,10 @@ export namespace AgentManagerFactory {
|
|
|
46
54
|
* The settings model.
|
|
47
55
|
*/
|
|
48
56
|
settingsModel: AISettingsModel;
|
|
57
|
+
/**
|
|
58
|
+
* The skill registry for discovering skills.
|
|
59
|
+
*/
|
|
60
|
+
skillRegistry?: ISkillRegistry;
|
|
49
61
|
/**
|
|
50
62
|
* The secrets manager.
|
|
51
63
|
*/
|
|
@@ -60,10 +72,17 @@ export class AgentManagerFactory {
|
|
|
60
72
|
constructor(options: AgentManagerFactory.IOptions) {
|
|
61
73
|
Private.setToken(options.token);
|
|
62
74
|
this._settingsModel = options.settingsModel;
|
|
75
|
+
this._skillRegistry = options.skillRegistry;
|
|
63
76
|
this._secretsManager = options.secretsManager;
|
|
64
77
|
this._mcpClients = [];
|
|
65
78
|
this._mcpConnectionChanged = new Signal<this, boolean>(this);
|
|
66
79
|
|
|
80
|
+
if (this._skillRegistry) {
|
|
81
|
+
this._skillRegistry.skillsChanged.connect(() => {
|
|
82
|
+
this.refreshSkillSnapshots();
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
67
86
|
// Initialize agent on construction
|
|
68
87
|
this._initializeAgents().catch(error =>
|
|
69
88
|
console.warn('Failed to initialize agent in constructor:', error)
|
|
@@ -76,6 +95,7 @@ export class AgentManagerFactory {
|
|
|
76
95
|
createAgent(options: IAgentManagerOptions): AgentManager {
|
|
77
96
|
const agentManager = new AgentManager({
|
|
78
97
|
...options,
|
|
98
|
+
skillRegistry: this._skillRegistry,
|
|
79
99
|
secretsManager: this._secretsManager
|
|
80
100
|
});
|
|
81
101
|
this._agentManagers.push(agentManager);
|
|
@@ -181,31 +201,39 @@ export class AgentManagerFactory {
|
|
|
181
201
|
* Sets up the agent with model configuration, tools, and MCP servers.
|
|
182
202
|
*/
|
|
183
203
|
private async _initializeAgents(): Promise<void> {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
204
|
+
this._initQueue = this._initQueue
|
|
205
|
+
.catch(() => undefined)
|
|
206
|
+
.then(async () => {
|
|
207
|
+
try {
|
|
208
|
+
await this._initializeMCPClients();
|
|
209
|
+
const mcpTools = await this.getMCPTools();
|
|
210
|
+
|
|
211
|
+
this._agentManagers.forEach(manager => {
|
|
212
|
+
manager.initializeAgent(mcpTools);
|
|
213
|
+
});
|
|
214
|
+
} catch (error) {
|
|
215
|
+
console.warn('Failed to initialize agents:', error);
|
|
216
|
+
}
|
|
195
217
|
});
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
218
|
+
return this._initQueue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Refresh skill snapshots across all agents.
|
|
223
|
+
*/
|
|
224
|
+
refreshSkillSnapshots(): void {
|
|
225
|
+
this._agentManagers.forEach(manager => {
|
|
226
|
+
manager.refreshSkills();
|
|
227
|
+
});
|
|
201
228
|
}
|
|
202
229
|
|
|
203
230
|
private _agentManagers: AgentManager[] = [];
|
|
204
231
|
private _settingsModel: AISettingsModel;
|
|
232
|
+
private _skillRegistry?: ISkillRegistry;
|
|
205
233
|
private _secretsManager?: ISecretsManager;
|
|
206
234
|
private _mcpClients: IMCPClientWrapper[];
|
|
207
235
|
private _mcpConnectionChanged: Signal<this, boolean>;
|
|
208
|
-
private
|
|
236
|
+
private _initQueue: Promise<void> = Promise.resolve();
|
|
209
237
|
}
|
|
210
238
|
|
|
211
239
|
/**
|
|
@@ -268,6 +296,19 @@ export type IAgentEvent<
|
|
|
268
296
|
}
|
|
269
297
|
: never;
|
|
270
298
|
|
|
299
|
+
/**
|
|
300
|
+
* Cached configuration used to (re)build the agent.
|
|
301
|
+
*/
|
|
302
|
+
interface IAgentConfig {
|
|
303
|
+
model: LanguageModel;
|
|
304
|
+
tools: ToolMap;
|
|
305
|
+
temperature: number;
|
|
306
|
+
maxOutputTokens?: number;
|
|
307
|
+
maxTurns: number;
|
|
308
|
+
baseSystemPrompt: string;
|
|
309
|
+
shouldUseTools: boolean;
|
|
310
|
+
}
|
|
311
|
+
|
|
271
312
|
/**
|
|
272
313
|
* Configuration options for the AgentManager
|
|
273
314
|
*/
|
|
@@ -287,6 +328,11 @@ export interface IAgentManagerOptions {
|
|
|
287
328
|
*/
|
|
288
329
|
providerRegistry?: IProviderRegistry;
|
|
289
330
|
|
|
331
|
+
/**
|
|
332
|
+
* The skill registry for discovering skills.
|
|
333
|
+
*/
|
|
334
|
+
skillRegistry?: ISkillRegistry;
|
|
335
|
+
|
|
290
336
|
/**
|
|
291
337
|
* The secrets manager.
|
|
292
338
|
*/
|
|
@@ -318,12 +364,12 @@ export class AgentManager {
|
|
|
318
364
|
this._settingsModel = options.settingsModel;
|
|
319
365
|
this._toolRegistry = options.toolRegistry;
|
|
320
366
|
this._providerRegistry = options.providerRegistry;
|
|
367
|
+
this._skillRegistry = options.skillRegistry;
|
|
321
368
|
this._secretsManager = options.secretsManager;
|
|
322
369
|
this._selectedToolNames = [];
|
|
323
370
|
this._agent = null;
|
|
324
371
|
this._history = [];
|
|
325
372
|
this._mcpTools = {};
|
|
326
|
-
this._isInitializing = false;
|
|
327
373
|
this._controller = null;
|
|
328
374
|
this._agentEvent = new Signal<this, IAgentEvent>(this);
|
|
329
375
|
this._tokenUsage = options.tokenUsage ?? {
|
|
@@ -331,6 +377,8 @@ export class AgentManager {
|
|
|
331
377
|
outputTokens: 0
|
|
332
378
|
};
|
|
333
379
|
this._tokenUsageChanged = new Signal<this, ITokenUsage>(this);
|
|
380
|
+
this._skills = [];
|
|
381
|
+
this._agentConfig = null;
|
|
334
382
|
|
|
335
383
|
this.activeProvider =
|
|
336
384
|
options.activeProvider ?? this._settingsModel.config.defaultProvider;
|
|
@@ -369,6 +417,21 @@ export class AgentManager {
|
|
|
369
417
|
return this._tokenUsageChanged;
|
|
370
418
|
}
|
|
371
419
|
|
|
420
|
+
/**
|
|
421
|
+
* Refresh the skills snapshot and rebuild the agent if resources are ready.
|
|
422
|
+
*/
|
|
423
|
+
refreshSkills(): void {
|
|
424
|
+
this._initQueue = this._initQueue
|
|
425
|
+
.catch(() => undefined)
|
|
426
|
+
.then(async () => {
|
|
427
|
+
this._refreshSkills();
|
|
428
|
+
if (!this._agentConfig) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
this._rebuildAgent();
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
|
|
372
435
|
/**
|
|
373
436
|
* The active provider for this agent.
|
|
374
437
|
*/
|
|
@@ -597,59 +660,108 @@ export class AgentManager {
|
|
|
597
660
|
* Sets up the agent with model configuration, tools, and MCP tools.
|
|
598
661
|
*/
|
|
599
662
|
initializeAgent = async (mcpTools?: ToolMap): Promise<void> => {
|
|
600
|
-
|
|
663
|
+
this._initQueue = this._initQueue
|
|
664
|
+
.catch(() => undefined)
|
|
665
|
+
.then(async () => {
|
|
666
|
+
try {
|
|
667
|
+
this._refreshSkills();
|
|
668
|
+
await this._prepareAgentConfig(mcpTools);
|
|
669
|
+
this._rebuildAgent();
|
|
670
|
+
} catch (error) {
|
|
671
|
+
console.warn('Failed to initialize agent:', error);
|
|
672
|
+
this._agent = null;
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
return this._initQueue;
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Refresh the in-memory skills snapshot from the skill registry.
|
|
680
|
+
*/
|
|
681
|
+
private _refreshSkills(): void {
|
|
682
|
+
if (!this._skillRegistry) {
|
|
683
|
+
this._skills = [];
|
|
601
684
|
return;
|
|
602
685
|
}
|
|
603
|
-
this.
|
|
604
|
-
|
|
605
|
-
try {
|
|
606
|
-
const config = this._settingsModel.config;
|
|
607
|
-
if (mcpTools !== undefined) {
|
|
608
|
-
this._mcpTools = mcpTools;
|
|
609
|
-
}
|
|
686
|
+
this._skills = this._skillRegistry.listSkills();
|
|
687
|
+
}
|
|
610
688
|
|
|
611
|
-
|
|
689
|
+
/**
|
|
690
|
+
* Prepare model, tools, and settings needed to (re)build the agent.
|
|
691
|
+
*/
|
|
692
|
+
private async _prepareAgentConfig(mcpTools?: ToolMap): Promise<void> {
|
|
693
|
+
const config = this._settingsModel.config;
|
|
694
|
+
if (mcpTools !== undefined) {
|
|
695
|
+
this._mcpTools = mcpTools;
|
|
696
|
+
}
|
|
612
697
|
|
|
613
|
-
|
|
614
|
-
config.toolsEnabled &&
|
|
615
|
-
this._selectedToolNames.length > 0 &&
|
|
616
|
-
this._toolRegistry &&
|
|
617
|
-
Object.keys(this._toolRegistry.tools).length > 0 &&
|
|
618
|
-
this._supportsToolCalling();
|
|
698
|
+
const model = await this._createModel();
|
|
619
699
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
700
|
+
const shouldUseTools = !!(
|
|
701
|
+
config.toolsEnabled &&
|
|
702
|
+
this._selectedToolNames.length > 0 &&
|
|
703
|
+
this._toolRegistry &&
|
|
704
|
+
Object.keys(this._toolRegistry.tools).length > 0 &&
|
|
705
|
+
this._supportsToolCalling()
|
|
706
|
+
);
|
|
623
707
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
708
|
+
const tools = shouldUseTools
|
|
709
|
+
? { ...this.selectedAgentTools, ...this._mcpTools }
|
|
710
|
+
: this._mcpTools;
|
|
627
711
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
const maxTurns =
|
|
632
|
-
activeProviderConfig?.parameters?.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
712
|
+
const activeProviderConfig = this._settingsModel.getProvider(
|
|
713
|
+
this._activeProvider
|
|
714
|
+
);
|
|
633
715
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
716
|
+
const temperature =
|
|
717
|
+
activeProviderConfig?.parameters?.temperature ?? DEFAULT_TEMPERATURE;
|
|
718
|
+
const maxTokens = activeProviderConfig?.parameters?.maxOutputTokens;
|
|
719
|
+
const maxTurns =
|
|
720
|
+
activeProviderConfig?.parameters?.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
721
|
+
|
|
722
|
+
this._agentConfig = {
|
|
723
|
+
model,
|
|
724
|
+
tools,
|
|
725
|
+
temperature,
|
|
726
|
+
maxOutputTokens: maxTokens,
|
|
727
|
+
maxTurns,
|
|
728
|
+
baseSystemPrompt: config.systemPrompt || '',
|
|
729
|
+
shouldUseTools
|
|
730
|
+
};
|
|
731
|
+
}
|
|
637
732
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
maxOutputTokens: maxTokens,
|
|
644
|
-
stopWhen: stepCountIs(maxTurns)
|
|
645
|
-
});
|
|
646
|
-
} catch (error) {
|
|
647
|
-
console.warn('Failed to initialize agent:', error);
|
|
733
|
+
/**
|
|
734
|
+
* Rebuild the agent using cached resources and the current skills snapshot.
|
|
735
|
+
*/
|
|
736
|
+
private _rebuildAgent(): void {
|
|
737
|
+
if (!this._agentConfig) {
|
|
648
738
|
this._agent = null;
|
|
649
|
-
|
|
650
|
-
this._isInitializing = false;
|
|
739
|
+
return;
|
|
651
740
|
}
|
|
652
|
-
|
|
741
|
+
|
|
742
|
+
const {
|
|
743
|
+
model,
|
|
744
|
+
tools,
|
|
745
|
+
temperature,
|
|
746
|
+
maxOutputTokens,
|
|
747
|
+
maxTurns,
|
|
748
|
+
baseSystemPrompt,
|
|
749
|
+
shouldUseTools
|
|
750
|
+
} = this._agentConfig;
|
|
751
|
+
|
|
752
|
+
const instructions = shouldUseTools
|
|
753
|
+
? this._getEnhancedSystemPrompt(baseSystemPrompt)
|
|
754
|
+
: baseSystemPrompt || 'You are a helpful assistant.';
|
|
755
|
+
|
|
756
|
+
this._agent = new ToolLoopAgent({
|
|
757
|
+
model,
|
|
758
|
+
instructions,
|
|
759
|
+
tools,
|
|
760
|
+
temperature,
|
|
761
|
+
maxOutputTokens,
|
|
762
|
+
stopWhen: stepCountIs(maxTurns)
|
|
763
|
+
});
|
|
764
|
+
}
|
|
653
765
|
|
|
654
766
|
/**
|
|
655
767
|
* Processes the stream result from agent execution.
|
|
@@ -892,69 +1004,53 @@ export class AgentManager {
|
|
|
892
1004
|
}
|
|
893
1005
|
|
|
894
1006
|
/**
|
|
895
|
-
* Enhances the base system prompt with
|
|
1007
|
+
* Enhances the base system prompt with dynamic context like skills.
|
|
896
1008
|
* @param baseSystemPrompt The base system prompt from settings
|
|
897
|
-
* @returns The enhanced system prompt with
|
|
1009
|
+
* @returns The enhanced system prompt with dynamic additions
|
|
898
1010
|
*/
|
|
899
1011
|
private _getEnhancedSystemPrompt(baseSystemPrompt: string): string {
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
PRIMARY TOOL USAGE - COMMAND-BASED OPERATIONS:
|
|
920
|
-
Most operations in JupyterLab should be performed using the command system:
|
|
921
|
-
1. Use 'discover_commands' to find available commands and their metadata
|
|
922
|
-
2. Use 'execute_command' to perform the actual operation
|
|
923
|
-
|
|
924
|
-
COMMAND DISCOVERY WORKFLOW:
|
|
925
|
-
- For file and notebook operations, use query 'jupyterlab-ai-commands' to discover the curated set of AI commands (~17 commands for file/notebook/directory operations)
|
|
926
|
-
- For other JupyterLab operations (terminal, launcher, UI), use specific keywords like 'terminal', 'launcher', etc.
|
|
927
|
-
- IMPORTANT: Always use 'jupyterlab-ai-commands' as the query for file/notebook tasks - this returns a focused set of commands instead of 100+ generic JupyterLab commands
|
|
928
|
-
|
|
929
|
-
KERNEL PREFERENCE FOR NOTEBOOKS AND CONSOLES:
|
|
930
|
-
When creating notebooks or consoles for a specific programming language, use the 'kernelPreference' argument to specify the kernel:
|
|
931
|
-
- To specify by language: { "kernelPreference": { "language": "python" } } or { "kernelPreference": { "language": "julia" } }
|
|
932
|
-
- To specify by kernel name: { "kernelPreference": { "name": "python3" } } or { "kernelPreference": { "name": "julia-1.10" } }
|
|
933
|
-
- Example: execute_command with commandId="notebook:create-new" and args={ "kernelPreference": { "language": "python" } }
|
|
934
|
-
- Example: execute_command with commandId="console:create" and args={ "kernelPreference": { "name": "python3" } }
|
|
935
|
-
- Common kernel names: "python3" (Python), "julia-1.10" (Julia), "ir" (R), "xpython" (xeus-python)
|
|
936
|
-
- If unsure of exact kernel name, prefer using "language" which will match any kernel supporting that language
|
|
1012
|
+
if (this._skills.length === 0) {
|
|
1013
|
+
return baseSystemPrompt;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
const lines = this._skills.map(
|
|
1017
|
+
skill => `- ${skill.name}: ${skill.description}`
|
|
1018
|
+
);
|
|
1019
|
+
const skillsPrompt = `
|
|
1020
|
+
|
|
1021
|
+
AGENT SKILLS:
|
|
1022
|
+
Skills are provided via the skills registry and accessed through tools (not commands).
|
|
1023
|
+
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.
|
|
1024
|
+
If the user explicitly asks for the latest list of skills, call discover_skills (optionally with a query).
|
|
1025
|
+
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.
|
|
1026
|
+
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.
|
|
1027
|
+
|
|
1028
|
+
AVAILABLE SKILLS (preloaded snapshot):
|
|
1029
|
+
${lines.join('\n')}
|
|
937
1030
|
`;
|
|
938
1031
|
|
|
939
|
-
return baseSystemPrompt +
|
|
1032
|
+
return baseSystemPrompt + skillsPrompt;
|
|
940
1033
|
}
|
|
941
1034
|
|
|
942
1035
|
// Private attributes
|
|
943
1036
|
private _settingsModel: AISettingsModel;
|
|
944
1037
|
private _toolRegistry?: IToolRegistry;
|
|
945
1038
|
private _providerRegistry?: IProviderRegistry;
|
|
1039
|
+
private _skillRegistry?: ISkillRegistry;
|
|
946
1040
|
private _secretsManager?: ISecretsManager;
|
|
947
1041
|
private _selectedToolNames: string[];
|
|
948
1042
|
private _agent: ToolLoopAgent<never, ToolMap> | null;
|
|
949
1043
|
private _history: ModelMessage[];
|
|
950
1044
|
private _mcpTools: ToolMap;
|
|
951
|
-
private _isInitializing: boolean;
|
|
952
1045
|
private _controller: AbortController | null;
|
|
953
1046
|
private _agentEvent: Signal<this, IAgentEvent>;
|
|
954
1047
|
private _tokenUsage: ITokenUsage;
|
|
955
1048
|
private _tokenUsageChanged: Signal<this, ITokenUsage>;
|
|
956
1049
|
private _activeProvider: string = '';
|
|
957
1050
|
private _activeProviderChanged = new Signal<this, string | undefined>(this);
|
|
1051
|
+
private _skills: ISkillSummary[];
|
|
1052
|
+
private _initQueue: Promise<void> = Promise.resolve();
|
|
1053
|
+
private _agentConfig: IAgentConfig | null;
|
|
958
1054
|
private _pendingApprovals: Map<
|
|
959
1055
|
string,
|
|
960
1056
|
{ resolve: (approved: boolean, reason?: string) => void }
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ChatCommand, IChatCommandProvider, IInputModel } from '@jupyter/chat';
|
|
2
|
+
|
|
3
|
+
import { AIChatModel } from '../chat-model';
|
|
4
|
+
|
|
5
|
+
export class ClearCommandProvider implements IChatCommandProvider {
|
|
6
|
+
public id: string = '@jupyterlite/ai:clear-command';
|
|
7
|
+
|
|
8
|
+
async listCommandCompletions(
|
|
9
|
+
inputModel: IInputModel
|
|
10
|
+
): Promise<ChatCommand[]> {
|
|
11
|
+
const match = inputModel.currentWord?.match(this._regex)?.[0];
|
|
12
|
+
if (!match) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (this._command.name.startsWith(match)) {
|
|
17
|
+
return [this._command];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async onSubmit(inputModel: IInputModel): Promise<void> {
|
|
24
|
+
const trimmed = inputModel.value.trim();
|
|
25
|
+
if (trimmed !== this._command.name) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const context = inputModel.chatContext as
|
|
30
|
+
| AIChatModel.IAIChatContext
|
|
31
|
+
| undefined;
|
|
32
|
+
context?.clearMessages?.();
|
|
33
|
+
|
|
34
|
+
inputModel.value = '';
|
|
35
|
+
inputModel.clearAttachments();
|
|
36
|
+
inputModel.clearMentions();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private _command: ChatCommand = {
|
|
40
|
+
name: '/clear',
|
|
41
|
+
providerId: this.id,
|
|
42
|
+
description: 'Clear the current chat history'
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
private _regex: RegExp = /^\/\w*$/;
|
|
46
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { ChatCommand, IChatCommandProvider, IInputModel } from '@jupyter/chat';
|
|
2
|
+
|
|
3
|
+
import { CommandRegistry } from '@lumino/commands';
|
|
4
|
+
|
|
5
|
+
import { AIChatModel } from '../chat-model';
|
|
6
|
+
import { CommandIds, ISkillRegistry } from '../tokens';
|
|
7
|
+
|
|
8
|
+
export class SkillsCommandProvider implements IChatCommandProvider {
|
|
9
|
+
constructor(options: SkillsCommandProvider.IOptions) {
|
|
10
|
+
this._skillRegistry = options.skillRegistry;
|
|
11
|
+
this._commands = options.commands;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public id: string = '@jupyterlite/ai:skills-command';
|
|
15
|
+
|
|
16
|
+
async listCommandCompletions(
|
|
17
|
+
inputModel: IInputModel
|
|
18
|
+
): Promise<ChatCommand[]> {
|
|
19
|
+
const match = inputModel.currentWord?.match(this._regex)?.[0];
|
|
20
|
+
if (!match) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (this._command.name.startsWith(match)) {
|
|
25
|
+
return [this._command];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async onSubmit(inputModel: IInputModel): Promise<void> {
|
|
32
|
+
const trimmed = inputModel.value.trim();
|
|
33
|
+
const match = trimmed.match(/^\/skills(?:\s+(.+))?$/);
|
|
34
|
+
if (!match) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Refresh skills from disk before listing
|
|
39
|
+
if (this._commands.hasCommand(CommandIds.refreshSkills)) {
|
|
40
|
+
await this._commands.execute(CommandIds.refreshSkills);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const query = match[1]?.trim();
|
|
44
|
+
const filtered = this._skillRegistry.listSkills(query);
|
|
45
|
+
|
|
46
|
+
let body = '';
|
|
47
|
+
if (filtered.length === 0) {
|
|
48
|
+
body = query
|
|
49
|
+
? `No skills found matching "${query}".`
|
|
50
|
+
: 'No skills are currently registered.';
|
|
51
|
+
} else {
|
|
52
|
+
const heading = query
|
|
53
|
+
? `Skills matching "${query}" (${filtered.length}):`
|
|
54
|
+
: `Available skills (${filtered.length}):`;
|
|
55
|
+
const lines = filtered.map(
|
|
56
|
+
skill => `- \`${skill.name}\` — ${skill.description}`
|
|
57
|
+
);
|
|
58
|
+
body = [heading, '', ...lines].join('\n');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const context = inputModel.chatContext as
|
|
62
|
+
| AIChatModel.IAIChatContext
|
|
63
|
+
| undefined;
|
|
64
|
+
context?.addSystemMessage?.(body);
|
|
65
|
+
|
|
66
|
+
inputModel.value = '';
|
|
67
|
+
inputModel.clearAttachments();
|
|
68
|
+
inputModel.clearMentions();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private _command: ChatCommand = {
|
|
72
|
+
name: '/skills',
|
|
73
|
+
providerId: this.id,
|
|
74
|
+
description: 'List available skills'
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
private _regex: RegExp = /^\/\w*$/;
|
|
78
|
+
private _commands: CommandRegistry;
|
|
79
|
+
private _skillRegistry: ISkillRegistry;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export namespace SkillsCommandProvider {
|
|
83
|
+
export interface IOptions {
|
|
84
|
+
skillRegistry: ISkillRegistry;
|
|
85
|
+
commands: CommandRegistry;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -75,6 +75,9 @@ export class ChatModelRegistry implements IChatModelRegistry {
|
|
|
75
75
|
add(model: AIChatModel): void {
|
|
76
76
|
if (!this._models.find(m => m.name === model.name)) {
|
|
77
77
|
this._models.push(model);
|
|
78
|
+
model.disposed.connect(() => {
|
|
79
|
+
this.remove(model.name);
|
|
80
|
+
});
|
|
78
81
|
}
|
|
79
82
|
}
|
|
80
83
|
|
|
@@ -93,6 +96,16 @@ export class ChatModelRegistry implements IChatModelRegistry {
|
|
|
93
96
|
}
|
|
94
97
|
}
|
|
95
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Getter/setter for the active cell manager.
|
|
101
|
+
*/
|
|
102
|
+
get activeCellManager(): ActiveCellManager | undefined {
|
|
103
|
+
return this._activeCellManager;
|
|
104
|
+
}
|
|
105
|
+
set activeCellManager(manager: ActiveCellManager | undefined) {
|
|
106
|
+
this._activeCellManager = manager;
|
|
107
|
+
}
|
|
108
|
+
|
|
96
109
|
private _models: AIChatModel[] = [];
|
|
97
110
|
private _docManager: IDocumentManager;
|
|
98
111
|
private _agentManagerFactory: AgentManagerFactory;
|
|
@@ -128,7 +141,7 @@ export namespace ChatModelRegistry {
|
|
|
128
141
|
/**
|
|
129
142
|
* The active cell manager.
|
|
130
143
|
*/
|
|
131
|
-
activeCellManager
|
|
144
|
+
activeCellManager?: ActiveCellManager | undefined;
|
|
132
145
|
/**
|
|
133
146
|
* The application language translation bundle.
|
|
134
147
|
*/
|