@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/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { JupyterFrontEndPlugin } from '@jupyterlab/application';
2
- import { IInputToolbarRegistryFactory } from '@jupyter/chat';
2
+ import { IChatCommandRegistry, IInputToolbarRegistryFactory } from '@jupyter/chat';
3
3
  import { AgentManagerFactory } from './agent';
4
- import { IProviderRegistry, IToolRegistry, IChatModelRegistry, IDiffManager } from './tokens';
4
+ import { IProviderRegistry, IToolRegistry, ISkillRegistry, IChatModelRegistry, IDiffManager } from './tokens';
5
5
  import { AISettingsModel } from './models/settings-model';
6
- declare const _default: (JupyterFrontEndPlugin<IProviderRegistry> | JupyterFrontEndPlugin<void> | JupyterFrontEndPlugin<IChatModelRegistry> | JupyterFrontEndPlugin<AgentManagerFactory> | JupyterFrontEndPlugin<AISettingsModel> | JupyterFrontEndPlugin<IDiffManager> | JupyterFrontEndPlugin<IToolRegistry> | JupyterFrontEndPlugin<IInputToolbarRegistryFactory>)[];
6
+ declare const _default: (JupyterFrontEndPlugin<IProviderRegistry> | JupyterFrontEndPlugin<void> | JupyterFrontEndPlugin<IChatCommandRegistry> | JupyterFrontEndPlugin<IChatModelRegistry> | JupyterFrontEndPlugin<AgentManagerFactory> | JupyterFrontEndPlugin<AISettingsModel> | JupyterFrontEndPlugin<IDiffManager> | JupyterFrontEndPlugin<ISkillRegistry> | JupyterFrontEndPlugin<IToolRegistry> | JupyterFrontEndPlugin<IInputToolbarRegistryFactory>)[];
7
7
  export default _default;
8
8
  export * from './tokens';
9
9
  export * from './icons';
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ILabShell, ILayoutRestorer } from '@jupyterlab/application';
2
- import { ActiveCellManager, AttachmentOpenerRegistry, chatIcon, ChatWidget, IInputToolbarRegistryFactory, InputToolbarRegistry, MultiChatPanel } from '@jupyter/chat';
2
+ import { ActiveCellManager, AttachmentOpenerRegistry, chatIcon, ChatCommandRegistry, ChatWidget, IChatCommandRegistry, IInputToolbarRegistryFactory, InputToolbarRegistry, MultiChatPanel } from '@jupyter/chat';
3
3
  import { ICommandPalette, IThemeManager, WidgetTracker } from '@jupyterlab/apputils';
4
4
  import { ICompletionProviderManager } from '@jupyterlab/completer';
5
5
  import { IDocumentManager } from '@jupyterlab/docmanager';
@@ -7,22 +7,28 @@ import { INotebookTracker } from '@jupyterlab/notebook';
7
7
  import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
8
8
  import { ISettingRegistry } from '@jupyterlab/settingregistry';
9
9
  import { IStatusBar } from '@jupyterlab/statusbar';
10
+ import { PathExt } from '@jupyterlab/coreutils';
10
11
  import { ITranslator, nullTranslator } from '@jupyterlab/translation';
11
12
  import { settingsIcon, Toolbar, ToolbarButton } from '@jupyterlab/ui-components';
12
13
  import { ISecretsManager, SecretsManager } from 'jupyter-secrets-manager';
13
14
  import { PromiseDelegate, UUID } from '@lumino/coreutils';
15
+ import { DisposableSet } from '@lumino/disposable';
14
16
  import { AgentManagerFactory } from './agent';
17
+ import { ClearCommandProvider } from './chat-commands/clear';
18
+ import { SkillsCommandProvider } from './chat-commands/skills';
15
19
  import { ProviderRegistry } from './providers/provider-registry';
16
20
  import { ApprovalButtons } from './approval-buttons';
17
21
  import { ChatModelRegistry } from './chat-model-registry';
18
- import { CommandIds, IAgentManagerFactory, IProviderRegistry, IToolRegistry, SECRETS_NAMESPACE, IAISettingsModel, IChatModelRegistry, IDiffManager } from './tokens';
22
+ import { CommandIds, IAgentManagerFactory, IProviderRegistry, IToolRegistry, ISkillRegistry, SECRETS_NAMESPACE, IAISettingsModel, IChatModelRegistry, IDiffManager } from './tokens';
19
23
  import { anthropicProvider, googleProvider, mistralProvider, openaiProvider, genericProvider } from './providers/built-in-providers';
20
24
  import { AICompletionProvider } from './completion';
21
25
  import { clearItem, createModelSelectItem, createToolSelectItem, stopItem, CompletionStatusWidget, TokenUsageWidget } from './components';
22
26
  import { AISettingsModel } from './models/settings-model';
27
+ import { loadSkillsFromPaths, SkillRegistry } from './skills';
23
28
  import { DiffManager } from './diff-manager';
24
29
  import { ToolRegistry } from './tools/tool-registry';
25
30
  import { createDiscoverCommandsTool, createExecuteCommandTool } from './tools/commands';
31
+ import { createDiscoverSkillsTool, createLoadSkillTool } from './tools/skills';
26
32
  import { AISettingsWidget } from './widgets/ai-settings';
27
33
  import { MainAreaChat } from './widgets/main-area-chat';
28
34
  /**
@@ -97,6 +103,45 @@ const genericProviderPlugin = {
97
103
  providerRegistry.registerProvider(genericProvider);
98
104
  }
99
105
  };
106
+ /**
107
+ * Chat command registry plugin.
108
+ */
109
+ const chatCommandRegistryPlugin = {
110
+ id: '@jupyterlite/ai:chat-command-registry',
111
+ description: 'Provide the chat command registry for JupyterLite AI.',
112
+ autoStart: true,
113
+ provides: IChatCommandRegistry,
114
+ activate: () => {
115
+ return new ChatCommandRegistry();
116
+ }
117
+ };
118
+ /**
119
+ * Clear chat command plugin.
120
+ */
121
+ const clearCommandPlugin = {
122
+ id: '@jupyterlite/ai:clear-command',
123
+ description: 'Register the /clear chat command.',
124
+ autoStart: true,
125
+ requires: [IChatCommandRegistry],
126
+ activate: (app, registry) => {
127
+ registry.addProvider(new ClearCommandProvider());
128
+ }
129
+ };
130
+ /**
131
+ * Skills chat command plugin.
132
+ */
133
+ const skillsCommandPlugin = {
134
+ id: '@jupyterlite/ai:skills-command',
135
+ description: 'Register the /skills chat command.',
136
+ autoStart: true,
137
+ requires: [IChatCommandRegistry, ISkillRegistry],
138
+ activate: (app, registry, skillRegistry) => {
139
+ registry.addProvider(new SkillsCommandProvider({
140
+ skillRegistry,
141
+ commands: app.commands
142
+ }));
143
+ }
144
+ };
100
145
  /**
101
146
  * The chat model registry.
102
147
  */
@@ -130,7 +175,8 @@ const plugin = {
130
175
  IRenderMimeRegistry,
131
176
  IInputToolbarRegistryFactory,
132
177
  IChatModelRegistry,
133
- IAISettingsModel
178
+ IAISettingsModel,
179
+ IChatCommandRegistry
134
180
  ],
135
181
  optional: [
136
182
  IThemeManager,
@@ -139,7 +185,7 @@ const plugin = {
139
185
  INotebookTracker,
140
186
  ITranslator
141
187
  ],
142
- activate: (app, rmRegistry, inputToolbarFactory, modelRegistry, settingsModel, themeManager, restorer, labShell, notebookTracker, translator) => {
188
+ activate: (app, rmRegistry, inputToolbarFactory, modelRegistry, settingsModel, chatCommandRegistry, themeManager, restorer, labShell, notebookTracker, translator) => {
143
189
  const trans = (translator ?? nullTranslator).load('jupyterlite_ai');
144
190
  // Create attachment opener registry to handle file attachments
145
191
  const attachmentOpenerRegistry = new AttachmentOpenerRegistry();
@@ -165,6 +211,7 @@ const plugin = {
165
211
  themeManager: themeManager ?? null,
166
212
  inputToolbarFactory,
167
213
  attachmentOpenerRegistry,
214
+ chatCommandRegistry,
168
215
  createModel: async (name) => {
169
216
  const model = modelRegistry.createModel(name);
170
217
  return { model };
@@ -260,10 +307,10 @@ const plugin = {
260
307
  app.commands.execute(CommandIds.openChat);
261
308
  }
262
309
  });
263
- registerCommands(app, rmRegistry, chatPanel, attachmentOpenerRegistry, inputToolbarFactory, settingsModel, tracker, modelRegistry, trans, themeManager, labShell);
310
+ registerCommands(app, rmRegistry, chatPanel, attachmentOpenerRegistry, inputToolbarFactory, settingsModel, chatCommandRegistry, tracker, modelRegistry, trans, themeManager, labShell);
264
311
  }
265
312
  };
266
- function registerCommands(app, rmRegistry, chatPanel, attachmentOpenerRegistry, inputToolbarFactory, settingsModel, tracker, modelRegistry, trans, themeManager, labShell) {
313
+ function registerCommands(app, rmRegistry, chatPanel, attachmentOpenerRegistry, inputToolbarFactory, settingsModel, chatCommandRegistry, tracker, modelRegistry, trans, themeManager, labShell) {
267
314
  const { commands } = app;
268
315
  if (labShell) {
269
316
  commands.addCommand(CommandIds.reposition, {
@@ -314,7 +361,8 @@ function registerCommands(app, rmRegistry, chatPanel, attachmentOpenerRegistry,
314
361
  rmRegistry,
315
362
  themeManager: themeManager ?? null,
316
363
  inputToolbarRegistry: inputToolbarFactory.create(),
317
- attachmentOpenerRegistry
364
+ attachmentOpenerRegistry,
365
+ chatCommandRegistry
318
366
  });
319
367
  const widget = new MainAreaChat({
320
368
  content,
@@ -464,6 +512,7 @@ const agentManagerFactory = SecretsManager.sign(SECRETS_NAMESPACE, token => ({
464
512
  provides: IAgentManagerFactory,
465
513
  requires: [IAISettingsModel, IProviderRegistry],
466
514
  optional: [
515
+ ISkillRegistry,
467
516
  ICommandPalette,
468
517
  ICompletionProviderManager,
469
518
  ILayoutRestorer,
@@ -471,10 +520,11 @@ const agentManagerFactory = SecretsManager.sign(SECRETS_NAMESPACE, token => ({
471
520
  IThemeManager,
472
521
  ITranslator
473
522
  ],
474
- activate: (app, settingsModel, providerRegistry, palette, completionManager, restorer, secretsManager, themeManager, translator) => {
523
+ activate: (app, settingsModel, providerRegistry, skillRegistry, palette, completionManager, restorer, secretsManager, themeManager, translator) => {
475
524
  const trans = (translator ?? nullTranslator).load('jupyterlite_ai');
476
525
  const agentManagerFactory = new AgentManagerFactory({
477
526
  settingsModel,
527
+ skillRegistry,
478
528
  secretsManager,
479
529
  token
480
530
  });
@@ -567,19 +617,36 @@ const diffManager = {
567
617
  });
568
618
  }
569
619
  };
620
+ /**
621
+ * Skill registry plugin
622
+ */
623
+ const skillRegistryPlugin = {
624
+ id: '@jupyterlite/ai:skill-registry',
625
+ description: 'Provide the skill registry',
626
+ autoStart: true,
627
+ provides: ISkillRegistry,
628
+ activate: () => {
629
+ return new SkillRegistry();
630
+ }
631
+ };
570
632
  const toolRegistry = {
571
633
  id: '@jupyterlite/ai:tool-registry',
572
634
  description: 'Provide the AI tool registry',
573
635
  autoStart: true,
574
636
  requires: [IAISettingsModel],
637
+ optional: [ISkillRegistry],
575
638
  provides: IToolRegistry,
576
- activate: (app, settingsModel) => {
639
+ activate: (app, settingsModel, skillRegistry) => {
577
640
  const toolRegistry = new ToolRegistry();
578
641
  // Add command operation tools
579
642
  const discoverCommandsTool = createDiscoverCommandsTool(app.commands);
580
643
  const executeCommandTool = createExecuteCommandTool(app.commands, settingsModel);
581
644
  toolRegistry.add('discover_commands', discoverCommandsTool);
582
645
  toolRegistry.add('execute_command', executeCommandTool);
646
+ if (skillRegistry) {
647
+ toolRegistry.add('discover_skills', createDiscoverSkillsTool(skillRegistry));
648
+ toolRegistry.add('load_skill', createLoadSkillTool(skillRegistry));
649
+ }
583
650
  return toolRegistry;
584
651
  }
585
652
  };
@@ -643,6 +710,111 @@ const completionStatus = {
643
710
  });
644
711
  }
645
712
  };
713
+ /**
714
+ * Skills plugin: discovers and registers agent skills from the filesystem.
715
+ */
716
+ const skillsPlugin = {
717
+ id: '@jupyterlite/ai:skills',
718
+ description: 'Discover and register agent skills',
719
+ autoStart: true,
720
+ requires: [IAISettingsModel, IDocumentManager, ISkillRegistry],
721
+ optional: [ICommandPalette, ITranslator],
722
+ activate: async (app, settingsModel, docManager, skillRegistry, palette, translator) => {
723
+ const trans = (translator ?? nullTranslator).load('jupyterlite_ai');
724
+ const validateResourcePath = (resourcePath) => {
725
+ if (resourcePath.startsWith('/')) {
726
+ return null;
727
+ }
728
+ const normalized = PathExt.normalize(resourcePath);
729
+ if (normalized.startsWith('..') || normalized === '') {
730
+ return null;
731
+ }
732
+ return normalized;
733
+ };
734
+ let currentSkillsPaths = settingsModel.config.skillsPaths;
735
+ let currentSkillDisposables = new DisposableSet();
736
+ const loadAndRegister = async () => {
737
+ const skillsPaths = settingsModel.config.skillsPaths;
738
+ const skills = await loadSkillsFromPaths(docManager.services.contents, skillsPaths);
739
+ const registrations = skills.map(skill => ({
740
+ name: skill.name,
741
+ description: skill.description,
742
+ instructions: skill.instructions,
743
+ resources: skill.resources,
744
+ loadResource: async (resource) => {
745
+ const validatedPath = validateResourcePath(resource);
746
+ if (validatedPath === null) {
747
+ return {
748
+ name: skill.name,
749
+ resource,
750
+ error: 'Invalid resource path: path traversal not allowed'
751
+ };
752
+ }
753
+ if (!skill.resources.includes(validatedPath)) {
754
+ return {
755
+ name: skill.name,
756
+ resource,
757
+ error: `Resource not found: ${resource}`
758
+ };
759
+ }
760
+ const resourcePath = `${skill.path}/${validatedPath}`;
761
+ try {
762
+ const fileModel = await docManager.services.contents.get(resourcePath, {
763
+ content: true
764
+ });
765
+ if (typeof fileModel.content !== 'string') {
766
+ return {
767
+ name: skill.name,
768
+ resource,
769
+ error: 'Resource content is not a string'
770
+ };
771
+ }
772
+ return {
773
+ name: skill.name,
774
+ resource,
775
+ content: fileModel.content
776
+ };
777
+ }
778
+ catch (error) {
779
+ return {
780
+ name: skill.name,
781
+ resource,
782
+ error: `Failed to read resource: ${error}`
783
+ };
784
+ }
785
+ }
786
+ }));
787
+ currentSkillDisposables.dispose();
788
+ currentSkillDisposables = new DisposableSet();
789
+ for (const registration of registrations) {
790
+ currentSkillDisposables.add(skillRegistry.registerSkill(registration));
791
+ }
792
+ };
793
+ app.commands.addCommand(CommandIds.refreshSkills, {
794
+ label: trans.__('Refresh Agents Skills'),
795
+ caption: trans.__('Re-scan the agents skills directory and update the registry'),
796
+ execute: async () => {
797
+ await loadAndRegister();
798
+ }
799
+ });
800
+ if (palette) {
801
+ palette.addItem({
802
+ command: CommandIds.refreshSkills,
803
+ category: trans.__('AI Assistant')
804
+ });
805
+ }
806
+ loadAndRegister().catch(error => console.warn('Failed to load skills on activation:', error));
807
+ settingsModel.stateChanged.connect(() => {
808
+ const newPaths = settingsModel.config.skillsPaths;
809
+ if (newPaths.length === currentSkillsPaths.length &&
810
+ newPaths.every((p, i) => p === currentSkillsPaths[i])) {
811
+ return;
812
+ }
813
+ currentSkillsPaths = newPaths;
814
+ loadAndRegister().catch(error => console.warn('Failed to reload skills:', error));
815
+ });
816
+ }
817
+ };
646
818
  export default [
647
819
  providerRegistryPlugin,
648
820
  anthropicProviderPlugin,
@@ -652,12 +824,17 @@ export default [
652
824
  genericProviderPlugin,
653
825
  settingsModel,
654
826
  diffManager,
827
+ chatCommandRegistryPlugin,
828
+ clearCommandPlugin,
829
+ skillRegistryPlugin,
830
+ skillsCommandPlugin,
655
831
  chatModelRegistry,
656
832
  plugin,
657
833
  toolRegistry,
658
834
  agentManagerFactory,
659
835
  inputToolbarFactory,
660
- completionStatus
836
+ completionStatus,
837
+ skillsPlugin
661
838
  ];
662
839
  // Export extension points for other extensions to use
663
840
  export * from './tokens';
@@ -44,6 +44,7 @@ export interface IAIConfig {
44
44
  showCellDiff: boolean;
45
45
  showFileDiff: boolean;
46
46
  diffDisplayMode: 'split' | 'unified';
47
+ skillsPaths: string[];
47
48
  }
48
49
  export declare class AISettingsModel extends VDomModel {
49
50
  private _config;
@@ -16,6 +16,7 @@ export class AISettingsModel extends VDomModel {
16
16
  showCellDiff: true,
17
17
  showFileDiff: true,
18
18
  diffDisplayMode: 'split',
19
+ skillsPaths: ['.agents/skills', '_agents/skills'],
19
20
  commandsRequiringApproval: [
20
21
  'notebook:restart-run-all',
21
22
  'notebook:run-cell',
@@ -51,7 +52,7 @@ You're designed to be a capable partner for data science, research, and developm
51
52
 
52
53
  **⚡ Kernel Management:**
53
54
  - Start new kernels with specified language or kernel name
54
- - Execute code directly in running kernels without creating cells
55
+ - Execute code directly in a kernel using jupyterlab-ai-commands execution commands (not console), without creating cells
55
56
  - List running kernels and monitor their status
56
57
  - Manage kernel lifecycle (start, monitor, shutdown)
57
58
 
@@ -68,18 +69,32 @@ You're designed to be a capable partner for data science, research, and developm
68
69
  - Help with both quick fixes and long-term project planning
69
70
 
70
71
  ## How You Work
71
- You can actively interact with the user's JupyterLab environment using specialized tools. When asked to perform actions, you can:
72
- - Execute operations directly in notebooks
73
- - Create and modify files as needed
74
- - Run code and analyze results
75
- - Make systematic changes across multiple files
72
+ You interact with the user's JupyterLab environment primarily through the command system:
73
+ - Use 'discover_commands' to find available JupyterLab commands
74
+ - Use 'execute_command' to perform operations
75
+ - For file and notebook operations, use commands from the jupyterlab-ai-commands extension (prefixed with 'jupyterlab-ai-commands:')
76
+ - These commands provide comprehensive file and notebook manipulation: create, read, edit files/notebooks, manage cells, run code, etc.
77
+ - You can make systematic changes across multiple files and perform complex multi-step operations
78
+ - Skills are available via the skills tools: discover_skills (list) and load_skill (load instructions/resources)
79
+
80
+ ## Tool & Skill Use Policy
81
+ - When tools or skills are available and the task requires actions or environment-specific facts, use them instead of guessing
82
+ - 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
83
+ - If a preloaded skills snapshot is provided in the system prompt, use it instead of calling discover_skills to list skills
84
+ - Only call discover_skills if the user explicitly asks for the latest list or you need to verify a skill not in the snapshot
85
+ - 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
86
+ - If you're unsure how to perform a request, discover relevant commands (discover_commands with task keywords)
87
+ - Use a relevant skill even when the user doesn't explicitly mention it
88
+ - Prefer the single most relevant tool or skill; if multiple could apply, ask a brief clarifying question
89
+ - Ask for missing required inputs before calling a tool or skill
90
+ - Before calling a tool or skill, briefly state why you're calling it
76
91
 
77
92
  ## Code Execution Strategy
78
93
  When asked to run code or perform computations, choose the most appropriate approach:
79
- - **For quick computations or one-off code execution**: Use kernel commands to start a kernel and execute code directly, without creating notebook files. This is ideal for calculations, data lookups, or testing code snippets.
94
+ - **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.
80
95
  - **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.
81
96
 
82
- This means if the user asks you to "calculate the factorial of 100" or "check what library version is installed", run that directly in a kernel rather than creating a new notebook file.
97
+ This 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.
83
98
 
84
99
  ## Your Approach
85
100
  - **Context-aware**: You understand the user is working in a data science/research environment
@@ -88,6 +103,23 @@ This means if the user asks you to "calculate the factorial of 100" or "check wh
88
103
  - **Collaborative**: You are a pair programming partner, not just a code generator
89
104
 
90
105
  ## Communication Style & Agent Behavior
106
+ IMPORTANT: Follow this message flow pattern for better user experience:
107
+
108
+ 1. FIRST: Explain what you're going to do and your approach
109
+ 2. THEN: Execute tools (these will show automatically with step numbers)
110
+ 3. FINALLY: Provide a concise summary of what was accomplished
111
+
112
+ Example flow:
113
+ - "I'll help you create a notebook with example cells. Let me first create the file structure, then add Python and Markdown cells."
114
+ - [Tool executions happen with automatic step display]
115
+ - "Successfully created your notebook with 3 cells: a title, code example, and visualization cell."
116
+
117
+ Guidelines:
118
+ - Start responses with your plan/approach before tool execution
119
+ - Let the system handle tool execution display (don't duplicate details)
120
+ - End with a brief summary of accomplishments
121
+ - Use natural, conversational tone throughout
122
+
91
123
  - **Conversational**: You maintain a friendly, natural conversation flow throughout the interaction
92
124
  - **Progress Updates**: You write brief progress messages between tool uses that appear directly in the conversation
93
125
  - **No Filler**: You avoid empty acknowledgments like "Sounds good!" or "Okay, I will..." - you get straight to work
@@ -106,13 +138,28 @@ This means if the user asks you to "calculate the factorial of 100" or "check wh
106
138
  - You keep users informed of progress while staying focused on the task
107
139
 
108
140
  ## Multi-Step Task Handling
109
- When users request complex tasks that require multiple steps (like "create a notebook with example cells"), you use tools in sequence to accomplish the complete task. For example:
110
- - First use create_notebook to create the notebook
111
- - Then use add_code_cell or add_markdown_cell to add cells
112
- - Use set_cell_content to add content to cells as needed
113
- - Use run_cell to execute code when appropriate
141
+ When users request complex tasks, you use the command system to accomplish them:
142
+ - For file and notebook operations, use discover_commands with query 'jupyterlab-ai-commands' to find the curated set of AI commands (~17 commands)
143
+ - For other JupyterLab operations (terminal, launcher, UI), use specific keywords like 'terminal', 'launcher', etc.
144
+ - IMPORTANT: Always use 'jupyterlab-ai-commands' as the query for file/notebook tasks - this returns a focused set instead of 100+ generic commands
145
+ - For example, to create a notebook with cells:
146
+ 1. discover_commands with query 'jupyterlab-ai-commands' to find available file/notebook commands
147
+ 2. execute_command with 'jupyterlab-ai-commands:create-notebook' and required arguments
148
+ 3. execute_command with 'jupyterlab-ai-commands:add-cell' multiple times to add cells
149
+ 4. execute_command with 'jupyterlab-ai-commands:set-cell-content' to add content to cells
150
+ 5. execute_command with 'jupyterlab-ai-commands:run-cell' when appropriate
151
+
152
+ ## Kernel Preference for Notebooks and Consoles
153
+ When creating notebooks or consoles for a specific programming language, use the 'kernelPreference' argument:
154
+ Only create consoles when the user explicitly asks for one; otherwise prefer the jupyterlab-ai-commands kernel execution commands for running code.
155
+ - To specify by language: { "kernelPreference": { "language": "python" } } or { "kernelPreference": { "language": "julia" } }
156
+ - To specify by kernel name: { "kernelPreference": { "name": "python3" } } or { "kernelPreference": { "name": "julia-1.10" } }
157
+ - Example: execute_command with commandId="notebook:create-new" and args={ "kernelPreference": { "language": "python" } }
158
+ - Example: execute_command with commandId="console:create" and args={ "kernelPreference": { "name": "python3" } }
159
+ - Common kernel names: "python3" (Python), "julia-1.10" (Julia), "ir" (R), "xpython" (xeus-python)
160
+ - If unsure of exact kernel name, prefer using "language" which will match any kernel supporting that language
114
161
 
115
- Always think through multi-step tasks and use tools to fully complete the user's request rather than stopping after just one action.
162
+ Always think through multi-step tasks and use commands to fully complete the user's request rather than stopping after just one action.
116
163
 
117
164
  You are ready to help users build something great!`,
118
165
  // Completion system prompt - also defined in schema/settings-model.json
@@ -11,6 +11,7 @@ export const anthropicProvider = {
11
11
  name: 'Anthropic Claude',
12
12
  apiKeyRequirement: 'required',
13
13
  defaultModels: [
14
+ 'claude-opus-4-6',
14
15
  'claude-opus-4-5',
15
16
  'claude-opus-4-5-20251101',
16
17
  'claude-sonnet-4-5',
@@ -60,7 +61,7 @@ export const googleProvider = {
60
61
  'gemini-3-flash-preview',
61
62
  'gemini-2.5-pro',
62
63
  'gemini-2.5-flash',
63
- 'gemini-2.5-flash-image-preview',
64
+ 'gemini-2.5-flash-image',
64
65
  'gemini-2.5-flash-lite',
65
66
  'gemini-2.5-flash-lite-preview-09-2025',
66
67
  'gemini-2.5-flash-preview-04-17',
@@ -154,21 +155,18 @@ export const openaiProvider = {
154
155
  'gpt-5.2',
155
156
  'gpt-5.2-chat-latest',
156
157
  'gpt-5.2-pro',
158
+ 'gpt-5.2-codex',
157
159
  'gpt-5.1',
158
160
  'gpt-5.1-chat-latest',
159
- 'gpt-5.1-codex',
160
- 'gpt-5.1-codex-mini',
161
- 'gpt-5.1-codex-max',
162
161
  'gpt-5',
163
162
  'gpt-5-2025-08-07',
164
163
  'gpt-5-chat-latest',
165
- 'gpt-5-codex',
166
- 'gpt-5-pro',
167
- 'gpt-5-pro-2025-10-06',
168
164
  'gpt-5-mini',
169
165
  'gpt-5-mini-2025-08-07',
170
166
  'gpt-5-nano',
171
167
  'gpt-5-nano-2025-08-07',
168
+ 'o4-mini',
169
+ 'o4-mini-2025-04-16',
172
170
  'o3',
173
171
  'o3-2025-04-16',
174
172
  'o3-mini',
@@ -0,0 +1,4 @@
1
+ export { parseSkillMd, type IParsedSkill } from './parse-skill';
2
+ export { loadSkillsFromPaths, type ISkillFileDefinition } from './skill-loader';
3
+ export type { ISkillDefinition, ISkillRegistration, ISkillResourceResult, ISkillSummary } from './types';
4
+ export { SkillRegistry } from './skill-registry';
@@ -0,0 +1,7 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ export { parseSkillMd } from './parse-skill';
6
+ export { loadSkillsFromPaths } from './skill-loader';
7
+ export { SkillRegistry } from './skill-registry';
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Parsed skill definition from a SKILL.md file.
3
+ */
4
+ export interface IParsedSkill {
5
+ name: string;
6
+ description: string;
7
+ instructions: string;
8
+ }
9
+ /**
10
+ * Parse a SKILL.md file content into a structured skill definition.
11
+ *
12
+ * Expected format:
13
+ * ```
14
+ * ---
15
+ * name: my-skill
16
+ * description: A brief description of the skill
17
+ * ---
18
+ * Full instructions body here...
19
+ * ```
20
+ *
21
+ * @param content - The raw content of a SKILL.md file
22
+ * @returns Parsed skill with name, description, and instructions
23
+ * @throws Error if the frontmatter is missing or invalid
24
+ */
25
+ export declare function parseSkillMd(content: string): IParsedSkill;
@@ -0,0 +1,69 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import { parse as parseYaml } from 'yaml';
6
+ /**
7
+ * Parse a SKILL.md file content into a structured skill definition.
8
+ *
9
+ * Expected format:
10
+ * ```
11
+ * ---
12
+ * name: my-skill
13
+ * description: A brief description of the skill
14
+ * ---
15
+ * Full instructions body here...
16
+ * ```
17
+ *
18
+ * @param content - The raw content of a SKILL.md file
19
+ * @returns Parsed skill with name, description, and instructions
20
+ * @throws Error if the frontmatter is missing or invalid
21
+ */
22
+ export function parseSkillMd(content) {
23
+ const normalizedContent = content
24
+ .replace(/^\uFEFF/, '')
25
+ .replace(/\r\n/g, '\n');
26
+ const lines = normalizedContent.split('\n');
27
+ if (lines[0]?.trim() !== '---') {
28
+ throw new Error('Invalid SKILL.md: missing frontmatter delimiters (---)');
29
+ }
30
+ const frontmatterLines = [];
31
+ let i = 1;
32
+ for (; i < lines.length; i++) {
33
+ const line = lines[i];
34
+ const trimmed = line.trim();
35
+ if (trimmed === '---') {
36
+ break;
37
+ }
38
+ frontmatterLines.push(line);
39
+ }
40
+ if (i >= lines.length) {
41
+ throw new Error('Invalid SKILL.md: missing frontmatter delimiters (---)');
42
+ }
43
+ const frontmatter = frontmatterLines.join('\n');
44
+ const instructions = lines
45
+ .slice(i + 1)
46
+ .join('\n')
47
+ .trim();
48
+ let metadata;
49
+ try {
50
+ metadata = parseYaml(frontmatter);
51
+ }
52
+ catch (error) {
53
+ throw new Error(`Invalid SKILL.md: YAML frontmatter parse failed: ${error}`);
54
+ }
55
+ const data = metadata;
56
+ const name = data?.name;
57
+ const description = data?.description;
58
+ if (typeof name !== 'string' || name.trim().length === 0) {
59
+ throw new Error('Invalid SKILL.md: missing "name" in frontmatter');
60
+ }
61
+ if (typeof description !== 'string' || description.trim().length === 0) {
62
+ throw new Error('Invalid SKILL.md: missing "description" in frontmatter');
63
+ }
64
+ return {
65
+ name: name.trim(),
66
+ description: description.trim(),
67
+ instructions
68
+ };
69
+ }
@@ -0,0 +1,25 @@
1
+ import { Contents } from '@jupyterlab/services';
2
+ import { IParsedSkill } from './parse-skill';
3
+ /**
4
+ * A skill definition loaded from the filesystem.
5
+ */
6
+ export interface ISkillFileDefinition extends IParsedSkill {
7
+ /**
8
+ * Path to the skill directory (e.g. ".agents/skills/my-skill").
9
+ */
10
+ path: string;
11
+ /**
12
+ * Paths to resource files relative to the skill directory.
13
+ */
14
+ resources: string[];
15
+ }
16
+ /**
17
+ * Load skills from multiple directories. Each path is scanned in order;
18
+ * when the same skill name appears in more than one path, the first
19
+ * occurrence wins.
20
+ *
21
+ * @param contentsManager - The Jupyter contents manager
22
+ * @param skillsPaths - Ordered list of directories to scan
23
+ * @returns Merged array of loaded skill definitions
24
+ */
25
+ export declare function loadSkillsFromPaths(contentsManager: Contents.IManager, skillsPaths: string[]): Promise<ISkillFileDefinition[]>;