@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.
- 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.d.ts +8 -0
- package/lib/chat-model.js +35 -3
- package/lib/index.d.ts +3 -3
- package/lib/index.js +187 -10
- 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 +33 -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.ts +48 -10
- package/src/index.ts +242 -5
- 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 +56 -0
- package/src/tools/skills.ts +84 -0
- 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';
|
|
@@ -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
|
|
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
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
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
|
|
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
|
|
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
|
|
110
|
-
-
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
-
|
|
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
|
|
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
|
|
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[]>;
|