@jupyterlite/ai 0.11.1 → 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.
Files changed (48) hide show
  1. package/lib/agent.d.ts +37 -5
  2. package/lib/agent.js +142 -96
  3. package/lib/chat-commands/clear.d.ts +8 -0
  4. package/lib/chat-commands/clear.js +30 -0
  5. package/lib/chat-commands/index.d.ts +2 -0
  6. package/lib/chat-commands/index.js +2 -0
  7. package/lib/chat-commands/skills.d.ts +19 -0
  8. package/lib/chat-commands/skills.js +57 -0
  9. package/lib/chat-model.d.ts +8 -0
  10. package/lib/chat-model.js +35 -3
  11. package/lib/index.d.ts +3 -3
  12. package/lib/index.js +187 -10
  13. package/lib/models/settings-model.d.ts +1 -0
  14. package/lib/models/settings-model.js +61 -14
  15. package/lib/providers/built-in-providers.js +5 -7
  16. package/lib/skills/index.d.ts +4 -0
  17. package/lib/skills/index.js +7 -0
  18. package/lib/skills/parse-skill.d.ts +25 -0
  19. package/lib/skills/parse-skill.js +69 -0
  20. package/lib/skills/skill-loader.d.ts +25 -0
  21. package/lib/skills/skill-loader.js +133 -0
  22. package/lib/skills/skill-registry.d.ts +31 -0
  23. package/lib/skills/skill-registry.js +100 -0
  24. package/lib/skills/types.d.ts +29 -0
  25. package/lib/skills/types.js +5 -0
  26. package/lib/tokens.d.ts +33 -0
  27. package/lib/tokens.js +5 -0
  28. package/lib/tools/skills.d.ts +9 -0
  29. package/lib/tools/skills.js +73 -0
  30. package/lib/widgets/ai-settings.js +33 -1
  31. package/package.json +10 -9
  32. package/schema/settings-model.json +8 -1
  33. package/src/agent.ts +198 -102
  34. package/src/chat-commands/clear.ts +46 -0
  35. package/src/chat-commands/index.ts +2 -0
  36. package/src/chat-commands/skills.ts +87 -0
  37. package/src/chat-model.ts +48 -10
  38. package/src/index.ts +242 -5
  39. package/src/models/settings-model.ts +64 -15
  40. package/src/providers/built-in-providers.ts +5 -7
  41. package/src/skills/index.ts +14 -0
  42. package/src/skills/parse-skill.ts +91 -0
  43. package/src/skills/skill-loader.ts +175 -0
  44. package/src/skills/skill-registry.ts +137 -0
  45. package/src/skills/types.ts +37 -0
  46. package/src/tokens.ts +56 -0
  47. package/src/tools/skills.ts +84 -0
  48. package/src/widgets/ai-settings.tsx +75 -0
package/lib/agent.d.ts CHANGED
@@ -3,7 +3,7 @@ import { type Tool } from 'ai';
3
3
  import { ISecretsManager } from 'jupyter-secrets-manager';
4
4
  import { AISettingsModel } from './models/settings-model';
5
5
  import type { IProviderRegistry } from './tokens';
6
- import { ITool, IToolRegistry, ITokenUsage } from './tokens';
6
+ import { ISkillRegistry, ITool, IToolRegistry, ITokenUsage } from './tokens';
7
7
  type ToolMap = Record<string, Tool>;
8
8
  export declare namespace AgentManagerFactory {
9
9
  interface IOptions {
@@ -11,6 +11,10 @@ export declare namespace AgentManagerFactory {
11
11
  * The settings model.
12
12
  */
13
13
  settingsModel: AISettingsModel;
14
+ /**
15
+ * The skill registry for discovering skills.
16
+ */
17
+ skillRegistry?: ISkillRegistry;
14
18
  /**
15
19
  * The secrets manager.
16
20
  */
@@ -52,12 +56,17 @@ export declare class AgentManagerFactory {
52
56
  * Sets up the agent with model configuration, tools, and MCP servers.
53
57
  */
54
58
  private _initializeAgents;
59
+ /**
60
+ * Refresh skill snapshots across all agents.
61
+ */
62
+ refreshSkillSnapshots(): void;
55
63
  private _agentManagers;
56
64
  private _settingsModel;
65
+ private _skillRegistry?;
57
66
  private _secretsManager?;
58
67
  private _mcpClients;
59
68
  private _mcpConnectionChanged;
60
- private _isInitializing;
69
+ private _initQueue;
61
70
  }
62
71
  /**
63
72
  * Event type mapping for type safety with inlined interface definitions
@@ -123,6 +132,10 @@ export interface IAgentManagerOptions {
123
132
  * Optional provider registry for model creation
124
133
  */
125
134
  providerRegistry?: IProviderRegistry;
135
+ /**
136
+ * The skill registry for discovering skills.
137
+ */
138
+ skillRegistry?: ISkillRegistry;
126
139
  /**
127
140
  * The secrets manager.
128
141
  */
@@ -164,6 +177,10 @@ export declare class AgentManager {
164
177
  * Signal emitted when token usage statistics change.
165
178
  */
166
179
  get tokenUsageChanged(): ISignal<this, ITokenUsage>;
180
+ /**
181
+ * Refresh the skills snapshot and rebuild the agent if resources are ready.
182
+ */
183
+ refreshSkills(): void;
167
184
  /**
168
185
  * The active provider for this agent.
169
186
  */
@@ -220,6 +237,18 @@ export declare class AgentManager {
220
237
  * Sets up the agent with model configuration, tools, and MCP tools.
221
238
  */
222
239
  initializeAgent: (mcpTools?: ToolMap) => Promise<void>;
240
+ /**
241
+ * Refresh the in-memory skills snapshot from the skill registry.
242
+ */
243
+ private _refreshSkills;
244
+ /**
245
+ * Prepare model, tools, and settings needed to (re)build the agent.
246
+ */
247
+ private _prepareAgentConfig;
248
+ /**
249
+ * Rebuild the agent using cached resources and the current skills snapshot.
250
+ */
251
+ private _rebuildAgent;
223
252
  /**
224
253
  * Processes the stream result from agent execution.
225
254
  * Handles message streaming, tool calls, and emits appropriate events.
@@ -262,26 +291,29 @@ export declare class AgentManager {
262
291
  */
263
292
  private _createModel;
264
293
  /**
265
- * Enhances the base system prompt with tool usage guidelines.
294
+ * Enhances the base system prompt with dynamic context like skills.
266
295
  * @param baseSystemPrompt The base system prompt from settings
267
- * @returns The enhanced system prompt with tool usage instructions
296
+ * @returns The enhanced system prompt with dynamic additions
268
297
  */
269
298
  private _getEnhancedSystemPrompt;
270
299
  private _settingsModel;
271
300
  private _toolRegistry?;
272
301
  private _providerRegistry?;
302
+ private _skillRegistry?;
273
303
  private _secretsManager?;
274
304
  private _selectedToolNames;
275
305
  private _agent;
276
306
  private _history;
277
307
  private _mcpTools;
278
- private _isInitializing;
279
308
  private _controller;
280
309
  private _agentEvent;
281
310
  private _tokenUsage;
282
311
  private _tokenUsageChanged;
283
312
  private _activeProvider;
284
313
  private _activeProviderChanged;
314
+ private _skills;
315
+ private _initQueue;
316
+ private _agentConfig;
285
317
  private _pendingApprovals;
286
318
  }
287
319
  export {};
package/lib/agent.js CHANGED
@@ -7,9 +7,15 @@ export class AgentManagerFactory {
7
7
  constructor(options) {
8
8
  Private.setToken(options.token);
9
9
  this._settingsModel = options.settingsModel;
10
+ this._skillRegistry = options.skillRegistry;
10
11
  this._secretsManager = options.secretsManager;
11
12
  this._mcpClients = [];
12
13
  this._mcpConnectionChanged = new Signal(this);
14
+ if (this._skillRegistry) {
15
+ this._skillRegistry.skillsChanged.connect(() => {
16
+ this.refreshSkillSnapshots();
17
+ });
18
+ }
13
19
  // Initialize agent on construction
14
20
  this._initializeAgents().catch(error => console.warn('Failed to initialize agent in constructor:', error));
15
21
  // Listen for settings changes
@@ -18,6 +24,7 @@ export class AgentManagerFactory {
18
24
  createAgent(options) {
19
25
  const agentManager = new AgentManager({
20
26
  ...options,
27
+ skillRegistry: this._skillRegistry,
21
28
  secretsManager: this._secretsManager
22
29
  });
23
30
  this._agentManagers.push(agentManager);
@@ -106,30 +113,37 @@ export class AgentManagerFactory {
106
113
  * Sets up the agent with model configuration, tools, and MCP servers.
107
114
  */
108
115
  async _initializeAgents() {
109
- if (this._isInitializing) {
110
- return;
111
- }
112
- this._isInitializing = true;
113
- try {
114
- await this._initializeMCPClients();
115
- const mcpTools = await this.getMCPTools();
116
- this._agentManagers.forEach(manager => {
117
- manager.initializeAgent(mcpTools);
118
- });
119
- }
120
- catch (error) {
121
- console.warn('Failed to initialize agents:', error);
122
- }
123
- finally {
124
- this._isInitializing = false;
125
- }
116
+ this._initQueue = this._initQueue
117
+ .catch(() => undefined)
118
+ .then(async () => {
119
+ try {
120
+ await this._initializeMCPClients();
121
+ const mcpTools = await this.getMCPTools();
122
+ this._agentManagers.forEach(manager => {
123
+ manager.initializeAgent(mcpTools);
124
+ });
125
+ }
126
+ catch (error) {
127
+ console.warn('Failed to initialize agents:', error);
128
+ }
129
+ });
130
+ return this._initQueue;
131
+ }
132
+ /**
133
+ * Refresh skill snapshots across all agents.
134
+ */
135
+ refreshSkillSnapshots() {
136
+ this._agentManagers.forEach(manager => {
137
+ manager.refreshSkills();
138
+ });
126
139
  }
127
140
  _agentManagers = [];
128
141
  _settingsModel;
142
+ _skillRegistry;
129
143
  _secretsManager;
130
144
  _mcpClients;
131
145
  _mcpConnectionChanged;
132
- _isInitializing = false;
146
+ _initQueue = Promise.resolve();
133
147
  }
134
148
  /**
135
149
  * Default parameter values for agent configuration
@@ -151,12 +165,12 @@ export class AgentManager {
151
165
  this._settingsModel = options.settingsModel;
152
166
  this._toolRegistry = options.toolRegistry;
153
167
  this._providerRegistry = options.providerRegistry;
168
+ this._skillRegistry = options.skillRegistry;
154
169
  this._secretsManager = options.secretsManager;
155
170
  this._selectedToolNames = [];
156
171
  this._agent = null;
157
172
  this._history = [];
158
173
  this._mcpTools = {};
159
- this._isInitializing = false;
160
174
  this._controller = null;
161
175
  this._agentEvent = new Signal(this);
162
176
  this._tokenUsage = options.tokenUsage ?? {
@@ -164,6 +178,8 @@ export class AgentManager {
164
178
  outputTokens: 0
165
179
  };
166
180
  this._tokenUsageChanged = new Signal(this);
181
+ this._skills = [];
182
+ this._agentConfig = null;
167
183
  this.activeProvider =
168
184
  options.activeProvider ?? this._settingsModel.config.defaultProvider;
169
185
  // Initialize selected tools to all available tools by default
@@ -195,6 +211,20 @@ export class AgentManager {
195
211
  get tokenUsageChanged() {
196
212
  return this._tokenUsageChanged;
197
213
  }
214
+ /**
215
+ * Refresh the skills snapshot and rebuild the agent if resources are ready.
216
+ */
217
+ refreshSkills() {
218
+ this._initQueue = this._initQueue
219
+ .catch(() => undefined)
220
+ .then(async () => {
221
+ this._refreshSkills();
222
+ if (!this._agentConfig) {
223
+ return;
224
+ }
225
+ this._rebuildAgent();
226
+ });
227
+ }
198
228
  /**
199
229
  * The active provider for this agent.
200
230
  */
@@ -390,48 +420,83 @@ export class AgentManager {
390
420
  * Sets up the agent with model configuration, tools, and MCP tools.
391
421
  */
392
422
  initializeAgent = async (mcpTools) => {
393
- if (this._isInitializing) {
394
- return;
395
- }
396
- this._isInitializing = true;
397
- try {
398
- const config = this._settingsModel.config;
399
- if (mcpTools !== undefined) {
400
- this._mcpTools = mcpTools;
423
+ this._initQueue = this._initQueue
424
+ .catch(() => undefined)
425
+ .then(async () => {
426
+ try {
427
+ this._refreshSkills();
428
+ await this._prepareAgentConfig(mcpTools);
429
+ this._rebuildAgent();
401
430
  }
402
- const model = await this._createModel();
403
- const shouldUseTools = config.toolsEnabled &&
404
- this._selectedToolNames.length > 0 &&
405
- this._toolRegistry &&
406
- Object.keys(this._toolRegistry.tools).length > 0 &&
407
- this._supportsToolCalling();
408
- const tools = shouldUseTools
409
- ? { ...this.selectedAgentTools, ...this._mcpTools }
410
- : this._mcpTools;
411
- const activeProviderConfig = this._settingsModel.getProvider(this._activeProvider);
412
- const temperature = activeProviderConfig?.parameters?.temperature ?? DEFAULT_TEMPERATURE;
413
- const maxTokens = activeProviderConfig?.parameters?.maxOutputTokens;
414
- const maxTurns = activeProviderConfig?.parameters?.maxTurns ?? DEFAULT_MAX_TURNS;
415
- const instructions = shouldUseTools
416
- ? this._getEnhancedSystemPrompt(config.systemPrompt || '')
417
- : config.systemPrompt || 'You are a helpful assistant.';
418
- this._agent = new ToolLoopAgent({
419
- model,
420
- instructions,
421
- tools,
422
- temperature,
423
- maxOutputTokens: maxTokens,
424
- stopWhen: stepCountIs(maxTurns)
425
- });
431
+ catch (error) {
432
+ console.warn('Failed to initialize agent:', error);
433
+ this._agent = null;
434
+ }
435
+ });
436
+ return this._initQueue;
437
+ };
438
+ /**
439
+ * Refresh the in-memory skills snapshot from the skill registry.
440
+ */
441
+ _refreshSkills() {
442
+ if (!this._skillRegistry) {
443
+ this._skills = [];
444
+ return;
426
445
  }
427
- catch (error) {
428
- console.warn('Failed to initialize agent:', error);
446
+ this._skills = this._skillRegistry.listSkills();
447
+ }
448
+ /**
449
+ * Prepare model, tools, and settings needed to (re)build the agent.
450
+ */
451
+ async _prepareAgentConfig(mcpTools) {
452
+ const config = this._settingsModel.config;
453
+ if (mcpTools !== undefined) {
454
+ this._mcpTools = mcpTools;
455
+ }
456
+ const model = await this._createModel();
457
+ const shouldUseTools = !!(config.toolsEnabled &&
458
+ this._selectedToolNames.length > 0 &&
459
+ this._toolRegistry &&
460
+ Object.keys(this._toolRegistry.tools).length > 0 &&
461
+ this._supportsToolCalling());
462
+ const tools = shouldUseTools
463
+ ? { ...this.selectedAgentTools, ...this._mcpTools }
464
+ : this._mcpTools;
465
+ const activeProviderConfig = this._settingsModel.getProvider(this._activeProvider);
466
+ const temperature = activeProviderConfig?.parameters?.temperature ?? DEFAULT_TEMPERATURE;
467
+ const maxTokens = activeProviderConfig?.parameters?.maxOutputTokens;
468
+ const maxTurns = activeProviderConfig?.parameters?.maxTurns ?? DEFAULT_MAX_TURNS;
469
+ this._agentConfig = {
470
+ model,
471
+ tools,
472
+ temperature,
473
+ maxOutputTokens: maxTokens,
474
+ maxTurns,
475
+ baseSystemPrompt: config.systemPrompt || '',
476
+ shouldUseTools
477
+ };
478
+ }
479
+ /**
480
+ * Rebuild the agent using cached resources and the current skills snapshot.
481
+ */
482
+ _rebuildAgent() {
483
+ if (!this._agentConfig) {
429
484
  this._agent = null;
485
+ return;
430
486
  }
431
- finally {
432
- this._isInitializing = false;
433
- }
434
- };
487
+ const { model, tools, temperature, maxOutputTokens, maxTurns, baseSystemPrompt, shouldUseTools } = this._agentConfig;
488
+ const instructions = shouldUseTools
489
+ ? this._getEnhancedSystemPrompt(baseSystemPrompt)
490
+ : baseSystemPrompt || 'You are a helpful assistant.';
491
+ this._agent = new ToolLoopAgent({
492
+ model,
493
+ instructions,
494
+ tools,
495
+ temperature,
496
+ maxOutputTokens,
497
+ stopWhen: stepCountIs(maxTurns)
498
+ });
499
+ }
435
500
  /**
436
501
  * Processes the stream result from agent execution.
437
502
  * Handles message streaming, tool calls, and emits appropriate events.
@@ -630,67 +695,48 @@ export class AgentManager {
630
695
  }, this._providerRegistry);
631
696
  }
632
697
  /**
633
- * Enhances the base system prompt with tool usage guidelines.
698
+ * Enhances the base system prompt with dynamic context like skills.
634
699
  * @param baseSystemPrompt The base system prompt from settings
635
- * @returns The enhanced system prompt with tool usage instructions
700
+ * @returns The enhanced system prompt with dynamic additions
636
701
  */
637
702
  _getEnhancedSystemPrompt(baseSystemPrompt) {
638
- const progressReportingPrompt = `
639
-
640
- IMPORTANT: Follow this message flow pattern for better user experience:
641
-
642
- 1. FIRST: Explain what you're going to do and your approach
643
- 2. THEN: Execute tools (these will show automatically with step numbers)
644
- 3. FINALLY: Provide a concise summary of what was accomplished
645
-
646
- Example flow:
647
- - "I'll help you create a notebook with example cells. Let me first create the file structure, then add Python and Markdown cells."
648
- - [Tool executions happen with automatic step display]
649
- - "Successfully created your notebook with 3 cells: a title, code example, and visualization cell."
650
-
651
- Guidelines:
652
- - Start responses with your plan/approach before tool execution
653
- - Let the system handle tool execution display (don't duplicate details)
654
- - End with a brief summary of accomplishments
655
- - Use natural, conversational tone throughout
656
-
657
- PRIMARY TOOL USAGE - COMMAND-BASED OPERATIONS:
658
- Most operations in JupyterLab should be performed using the command system:
659
- 1. Use 'discover_commands' to find available commands and their metadata
660
- 2. Use 'execute_command' to perform the actual operation
703
+ if (this._skills.length === 0) {
704
+ return baseSystemPrompt;
705
+ }
706
+ const lines = this._skills.map(skill => `- ${skill.name}: ${skill.description}`);
707
+ const skillsPrompt = `
661
708
 
662
- COMMAND DISCOVERY WORKFLOW:
663
- - 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)
664
- - For other JupyterLab operations (terminal, launcher, UI), use specific keywords like 'terminal', 'launcher', etc.
665
- - 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
709
+ AGENT SKILLS:
710
+ Skills are provided via the skills registry and accessed through tools (not commands).
711
+ 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.
712
+ If the user explicitly asks for the latest list of skills, call discover_skills (optionally with a query).
713
+ 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.
714
+ 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.
666
715
 
667
- KERNEL PREFERENCE FOR NOTEBOOKS AND CONSOLES:
668
- When creating notebooks or consoles for a specific programming language, use the 'kernelPreference' argument to specify the kernel:
669
- - To specify by language: { "kernelPreference": { "language": "python" } } or { "kernelPreference": { "language": "julia" } }
670
- - To specify by kernel name: { "kernelPreference": { "name": "python3" } } or { "kernelPreference": { "name": "julia-1.10" } }
671
- - Example: execute_command with commandId="notebook:create-new" and args={ "kernelPreference": { "language": "python" } }
672
- - Example: execute_command with commandId="console:create" and args={ "kernelPreference": { "name": "python3" } }
673
- - Common kernel names: "python3" (Python), "julia-1.10" (Julia), "ir" (R), "xpython" (xeus-python)
674
- - If unsure of exact kernel name, prefer using "language" which will match any kernel supporting that language
716
+ AVAILABLE SKILLS (preloaded snapshot):
717
+ ${lines.join('\n')}
675
718
  `;
676
- return baseSystemPrompt + progressReportingPrompt;
719
+ return baseSystemPrompt + skillsPrompt;
677
720
  }
678
721
  // Private attributes
679
722
  _settingsModel;
680
723
  _toolRegistry;
681
724
  _providerRegistry;
725
+ _skillRegistry;
682
726
  _secretsManager;
683
727
  _selectedToolNames;
684
728
  _agent;
685
729
  _history;
686
730
  _mcpTools;
687
- _isInitializing;
688
731
  _controller;
689
732
  _agentEvent;
690
733
  _tokenUsage;
691
734
  _tokenUsageChanged;
692
735
  _activeProvider = '';
693
736
  _activeProviderChanged = new Signal(this);
737
+ _skills;
738
+ _initQueue = Promise.resolve();
739
+ _agentConfig;
694
740
  _pendingApprovals = new Map();
695
741
  }
696
742
  var Private;
@@ -0,0 +1,8 @@
1
+ import { ChatCommand, IChatCommandProvider, IInputModel } from '@jupyter/chat';
2
+ export declare class ClearCommandProvider implements IChatCommandProvider {
3
+ id: string;
4
+ listCommandCompletions(inputModel: IInputModel): Promise<ChatCommand[]>;
5
+ onSubmit(inputModel: IInputModel): Promise<void>;
6
+ private _command;
7
+ private _regex;
8
+ }
@@ -0,0 +1,30 @@
1
+ export class ClearCommandProvider {
2
+ id = '@jupyterlite/ai:clear-command';
3
+ async listCommandCompletions(inputModel) {
4
+ const match = inputModel.currentWord?.match(this._regex)?.[0];
5
+ if (!match) {
6
+ return [];
7
+ }
8
+ if (this._command.name.startsWith(match)) {
9
+ return [this._command];
10
+ }
11
+ return [];
12
+ }
13
+ async onSubmit(inputModel) {
14
+ const trimmed = inputModel.value.trim();
15
+ if (trimmed !== this._command.name) {
16
+ return;
17
+ }
18
+ const context = inputModel.chatContext;
19
+ context?.clearMessages?.();
20
+ inputModel.value = '';
21
+ inputModel.clearAttachments();
22
+ inputModel.clearMentions();
23
+ }
24
+ _command = {
25
+ name: '/clear',
26
+ providerId: this.id,
27
+ description: 'Clear the current chat history'
28
+ };
29
+ _regex = /^\/\w*$/;
30
+ }
@@ -0,0 +1,2 @@
1
+ export * from './clear';
2
+ export * from './skills';
@@ -0,0 +1,2 @@
1
+ export * from './clear';
2
+ export * from './skills';
@@ -0,0 +1,19 @@
1
+ import { ChatCommand, IChatCommandProvider, IInputModel } from '@jupyter/chat';
2
+ import { CommandRegistry } from '@lumino/commands';
3
+ import { ISkillRegistry } from '../tokens';
4
+ export declare class SkillsCommandProvider implements IChatCommandProvider {
5
+ constructor(options: SkillsCommandProvider.IOptions);
6
+ id: string;
7
+ listCommandCompletions(inputModel: IInputModel): Promise<ChatCommand[]>;
8
+ onSubmit(inputModel: IInputModel): Promise<void>;
9
+ private _command;
10
+ private _regex;
11
+ private _commands;
12
+ private _skillRegistry;
13
+ }
14
+ export declare namespace SkillsCommandProvider {
15
+ interface IOptions {
16
+ skillRegistry: ISkillRegistry;
17
+ commands: CommandRegistry;
18
+ }
19
+ }
@@ -0,0 +1,57 @@
1
+ import { CommandIds } from '../tokens';
2
+ export class SkillsCommandProvider {
3
+ constructor(options) {
4
+ this._skillRegistry = options.skillRegistry;
5
+ this._commands = options.commands;
6
+ }
7
+ id = '@jupyterlite/ai:skills-command';
8
+ async listCommandCompletions(inputModel) {
9
+ const match = inputModel.currentWord?.match(this._regex)?.[0];
10
+ if (!match) {
11
+ return [];
12
+ }
13
+ if (this._command.name.startsWith(match)) {
14
+ return [this._command];
15
+ }
16
+ return [];
17
+ }
18
+ async onSubmit(inputModel) {
19
+ const trimmed = inputModel.value.trim();
20
+ const match = trimmed.match(/^\/skills(?:\s+(.+))?$/);
21
+ if (!match) {
22
+ return;
23
+ }
24
+ // Refresh skills from disk before listing
25
+ if (this._commands.hasCommand(CommandIds.refreshSkills)) {
26
+ await this._commands.execute(CommandIds.refreshSkills);
27
+ }
28
+ const query = match[1]?.trim();
29
+ const filtered = this._skillRegistry.listSkills(query);
30
+ let body = '';
31
+ if (filtered.length === 0) {
32
+ body = query
33
+ ? `No skills found matching "${query}".`
34
+ : 'No skills are currently registered.';
35
+ }
36
+ else {
37
+ const heading = query
38
+ ? `Skills matching "${query}" (${filtered.length}):`
39
+ : `Available skills (${filtered.length}):`;
40
+ const lines = filtered.map(skill => `- \`${skill.name}\` — ${skill.description}`);
41
+ body = [heading, '', ...lines].join('\n');
42
+ }
43
+ const context = inputModel.chatContext;
44
+ context?.addSystemMessage?.(body);
45
+ inputModel.value = '';
46
+ inputModel.clearAttachments();
47
+ inputModel.clearMentions();
48
+ }
49
+ _command = {
50
+ name: '/skills',
51
+ providerId: this.id,
52
+ description: 'List available skills'
53
+ };
54
+ _regex = /^\/\w*$/;
55
+ _commands;
56
+ _skillRegistry;
57
+ }
@@ -48,6 +48,10 @@ export declare class AIChatModel extends AbstractChatModel {
48
48
  * Clears all messages from the chat and resets conversation state.
49
49
  */
50
50
  clearMessages: () => void;
51
+ /**
52
+ * Adds a non-user message to the chat (used by chat commands).
53
+ */
54
+ addSystemMessage(body: string): void;
51
55
  /**
52
56
  * Sends a message to the AI and generates a response.
53
57
  * @param message The user message to send
@@ -184,6 +188,10 @@ export declare namespace AIChatModel {
184
188
  * The clear messages callback.
185
189
  */
186
190
  clearMessages: () => void;
191
+ /**
192
+ * Adds an assistant/system message to the chat.
193
+ */
194
+ addSystemMessage: (body: string) => void;
187
195
  /**
188
196
  * The agent manager of the chat.
189
197
  */
package/lib/chat-model.js CHANGED
@@ -76,7 +76,8 @@ export class AIChatModel extends AbstractChatModel {
76
76
  messages: this.messages,
77
77
  stopStreaming: () => this.stopStreaming(),
78
78
  clearMessages: () => this.clearMessages(),
79
- agentManager: this._agentManager
79
+ agentManager: this._agentManager,
80
+ addSystemMessage: (body) => this.addSystemMessage(body)
80
81
  };
81
82
  }
82
83
  /**
@@ -93,11 +94,30 @@ export class AIChatModel extends AbstractChatModel {
93
94
  this._toolContexts.clear();
94
95
  this._agentManager.clearHistory();
95
96
  };
97
+ /**
98
+ * Adds a non-user message to the chat (used by chat commands).
99
+ */
100
+ addSystemMessage(body) {
101
+ const message = {
102
+ body,
103
+ sender: this._getAIUser(),
104
+ id: UUID.uuid4(),
105
+ time: Date.now() / 1000,
106
+ type: 'msg',
107
+ raw_time: false
108
+ };
109
+ this.messageAdded(message);
110
+ }
96
111
  /**
97
112
  * Sends a message to the AI and generates a response.
98
113
  * @param message The user message to send
99
114
  */
100
115
  async sendMessage(message) {
116
+ const hasBody = message.body.trim().length > 0;
117
+ const hasAttachments = this.input.attachments.length > 0;
118
+ if (!hasBody && !hasAttachments) {
119
+ return;
120
+ }
101
121
  // Add user message to chat
102
122
  const userMessage = {
103
123
  body: message.body,
@@ -262,6 +282,19 @@ export class AIChatModel extends AbstractChatModel {
262
282
  return `query: "${parsedInput.query}"`;
263
283
  }
264
284
  break;
285
+ case 'discover_skills':
286
+ if (parsedInput.query) {
287
+ return `query: "${parsedInput.query}"`;
288
+ }
289
+ break;
290
+ case 'load_skill':
291
+ if (parsedInput.name) {
292
+ if (parsedInput.resource) {
293
+ return `${parsedInput.name} (${parsedInput.resource})`;
294
+ }
295
+ return parsedInput.name;
296
+ }
297
+ break;
265
298
  }
266
299
  }
267
300
  catch {
@@ -361,8 +394,7 @@ export class AIChatModel extends AbstractChatModel {
361
394
  return;
362
395
  }
363
396
  context.status = status;
364
- this.messageAdded({
365
- ...existingMessage,
397
+ existingMessage.update({
366
398
  body: Private.buildToolCallHtml({
367
399
  toolName: context.toolName,
368
400
  input: context.input,