@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/src/chat-model.ts
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
IActiveCellManager,
|
|
4
4
|
IAttachment,
|
|
5
5
|
IChatContext,
|
|
6
|
-
|
|
6
|
+
IMessageContent,
|
|
7
7
|
INewMessage,
|
|
8
8
|
IUser
|
|
9
9
|
} from '@jupyter/chat';
|
|
@@ -160,7 +160,8 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
160
160
|
messages: this.messages,
|
|
161
161
|
stopStreaming: () => this.stopStreaming(),
|
|
162
162
|
clearMessages: () => this.clearMessages(),
|
|
163
|
-
agentManager: this._agentManager
|
|
163
|
+
agentManager: this._agentManager,
|
|
164
|
+
addSystemMessage: (body: string) => this.addSystemMessage(body)
|
|
164
165
|
};
|
|
165
166
|
}
|
|
166
167
|
|
|
@@ -180,13 +181,34 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
180
181
|
this._agentManager.clearHistory();
|
|
181
182
|
};
|
|
182
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Adds a non-user message to the chat (used by chat commands).
|
|
186
|
+
*/
|
|
187
|
+
addSystemMessage(body: string): void {
|
|
188
|
+
const message: IMessageContent = {
|
|
189
|
+
body,
|
|
190
|
+
sender: this._getAIUser(),
|
|
191
|
+
id: UUID.uuid4(),
|
|
192
|
+
time: Date.now() / 1000,
|
|
193
|
+
type: 'msg',
|
|
194
|
+
raw_time: false
|
|
195
|
+
};
|
|
196
|
+
this.messageAdded(message);
|
|
197
|
+
}
|
|
198
|
+
|
|
183
199
|
/**
|
|
184
200
|
* Sends a message to the AI and generates a response.
|
|
185
201
|
* @param message The user message to send
|
|
186
202
|
*/
|
|
187
203
|
async sendMessage(message: INewMessage): Promise<void> {
|
|
204
|
+
const hasBody = message.body.trim().length > 0;
|
|
205
|
+
const hasAttachments = this.input.attachments.length > 0;
|
|
206
|
+
if (!hasBody && !hasAttachments) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
188
210
|
// Add user message to chat
|
|
189
|
-
const userMessage:
|
|
211
|
+
const userMessage: IMessageContent = {
|
|
190
212
|
body: message.body,
|
|
191
213
|
sender: this.user || { username: 'user', display_name: 'User' },
|
|
192
214
|
id: UUID.uuid4(),
|
|
@@ -199,7 +221,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
199
221
|
|
|
200
222
|
// Check if we have valid configuration
|
|
201
223
|
if (!this._agentManager.hasValidConfig()) {
|
|
202
|
-
const errorMessage:
|
|
224
|
+
const errorMessage: IMessageContent = {
|
|
203
225
|
body: 'Please configure your AI settings first. Open the AI Settings to set your API key and model.',
|
|
204
226
|
sender: this._getAIUser(),
|
|
205
227
|
id: UUID.uuid4(),
|
|
@@ -230,7 +252,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
230
252
|
|
|
231
253
|
await this._agentManager.generateResponse(enhancedMessage);
|
|
232
254
|
} catch (error) {
|
|
233
|
-
const errorMessage:
|
|
255
|
+
const errorMessage: IMessageContent = {
|
|
234
256
|
body: `Error generating AI response: ${(error as Error).message}`,
|
|
235
257
|
sender: this._getAIUser(),
|
|
236
258
|
id: UUID.uuid4(),
|
|
@@ -304,7 +326,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
304
326
|
* @param event Event containing the message start data
|
|
305
327
|
*/
|
|
306
328
|
private _handleMessageStart(event: IAgentEvent<'message_start'>): void {
|
|
307
|
-
const aiMessage:
|
|
329
|
+
const aiMessage: IMessageContent = {
|
|
308
330
|
body: '',
|
|
309
331
|
sender: this._getAIUser(),
|
|
310
332
|
id: event.data.messageId,
|
|
@@ -366,6 +388,19 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
366
388
|
return `query: "${parsedInput.query}"`;
|
|
367
389
|
}
|
|
368
390
|
break;
|
|
391
|
+
case 'discover_skills':
|
|
392
|
+
if (parsedInput.query) {
|
|
393
|
+
return `query: "${parsedInput.query}"`;
|
|
394
|
+
}
|
|
395
|
+
break;
|
|
396
|
+
case 'load_skill':
|
|
397
|
+
if (parsedInput.name) {
|
|
398
|
+
if (parsedInput.resource) {
|
|
399
|
+
return `${parsedInput.name} (${parsedInput.resource})`;
|
|
400
|
+
}
|
|
401
|
+
return parsedInput.name;
|
|
402
|
+
}
|
|
403
|
+
break;
|
|
369
404
|
}
|
|
370
405
|
} catch {
|
|
371
406
|
// If parsing fails, return empty string
|
|
@@ -396,7 +431,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
396
431
|
|
|
397
432
|
this._toolContexts.set(event.data.callId, context);
|
|
398
433
|
|
|
399
|
-
const toolCallMessage:
|
|
434
|
+
const toolCallMessage: IMessageContent = {
|
|
400
435
|
body: Private.buildToolCallHtml({
|
|
401
436
|
toolName: context.toolName,
|
|
402
437
|
input: context.input,
|
|
@@ -496,8 +531,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
496
531
|
}
|
|
497
532
|
|
|
498
533
|
context.status = status;
|
|
499
|
-
|
|
500
|
-
...existingMessage,
|
|
534
|
+
existingMessage.update({
|
|
501
535
|
body: Private.buildToolCallHtml({
|
|
502
536
|
toolName: context.toolName,
|
|
503
537
|
input: context.input,
|
|
@@ -793,7 +827,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
793
827
|
private _user: IUser;
|
|
794
828
|
private _toolContexts: Map<string, IToolExecutionContext> = new Map();
|
|
795
829
|
private _agentManager: AgentManager;
|
|
796
|
-
private _currentStreamingMessage:
|
|
830
|
+
private _currentStreamingMessage: IMessageContent | null = null;
|
|
797
831
|
private _nameChanged = new Signal<AIChatModel, string>(this);
|
|
798
832
|
private _trans: TranslationBundle;
|
|
799
833
|
}
|
|
@@ -989,6 +1023,10 @@ export namespace AIChatModel {
|
|
|
989
1023
|
* The clear messages callback.
|
|
990
1024
|
*/
|
|
991
1025
|
clearMessages: () => void;
|
|
1026
|
+
/**
|
|
1027
|
+
* Adds an assistant/system message to the chat.
|
|
1028
|
+
*/
|
|
1029
|
+
addSystemMessage: (body: string) => void;
|
|
992
1030
|
/**
|
|
993
1031
|
* The agent manager of the chat.
|
|
994
1032
|
*/
|
package/src/index.ts
CHANGED
|
@@ -9,8 +9,10 @@ import {
|
|
|
9
9
|
ActiveCellManager,
|
|
10
10
|
AttachmentOpenerRegistry,
|
|
11
11
|
chatIcon,
|
|
12
|
+
ChatCommandRegistry,
|
|
12
13
|
ChatWidget,
|
|
13
14
|
IAttachmentOpenerRegistry,
|
|
15
|
+
IChatCommandRegistry,
|
|
14
16
|
IInputToolbarRegistryFactory,
|
|
15
17
|
InputToolbarRegistry,
|
|
16
18
|
MultiChatPanel
|
|
@@ -34,6 +36,8 @@ import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
|
34
36
|
|
|
35
37
|
import { IStatusBar } from '@jupyterlab/statusbar';
|
|
36
38
|
|
|
39
|
+
import { PathExt } from '@jupyterlab/coreutils';
|
|
40
|
+
|
|
37
41
|
import {
|
|
38
42
|
ITranslator,
|
|
39
43
|
nullTranslator,
|
|
@@ -49,11 +53,15 @@ import {
|
|
|
49
53
|
import { ISecretsManager, SecretsManager } from 'jupyter-secrets-manager';
|
|
50
54
|
|
|
51
55
|
import { PromiseDelegate, UUID } from '@lumino/coreutils';
|
|
56
|
+
import { DisposableSet } from '@lumino/disposable';
|
|
52
57
|
|
|
53
58
|
import { AgentManagerFactory } from './agent';
|
|
54
59
|
|
|
55
60
|
import { AIChatModel } from './chat-model';
|
|
56
61
|
|
|
62
|
+
import { ClearCommandProvider } from './chat-commands/clear';
|
|
63
|
+
import { SkillsCommandProvider } from './chat-commands/skills';
|
|
64
|
+
|
|
57
65
|
import { ProviderRegistry } from './providers/provider-registry';
|
|
58
66
|
|
|
59
67
|
import { ApprovalButtons } from './approval-buttons';
|
|
@@ -65,6 +73,7 @@ import {
|
|
|
65
73
|
IAgentManagerFactory,
|
|
66
74
|
IProviderRegistry,
|
|
67
75
|
IToolRegistry,
|
|
76
|
+
ISkillRegistry,
|
|
68
77
|
SECRETS_NAMESPACE,
|
|
69
78
|
IAISettingsModel,
|
|
70
79
|
IChatModelRegistry,
|
|
@@ -92,6 +101,8 @@ import {
|
|
|
92
101
|
|
|
93
102
|
import { AISettingsModel } from './models/settings-model';
|
|
94
103
|
|
|
104
|
+
import { loadSkillsFromPaths, SkillRegistry } from './skills';
|
|
105
|
+
|
|
95
106
|
import { DiffManager } from './diff-manager';
|
|
96
107
|
|
|
97
108
|
import { ToolRegistry } from './tools/tool-registry';
|
|
@@ -100,6 +111,7 @@ import {
|
|
|
100
111
|
createDiscoverCommandsTool,
|
|
101
112
|
createExecuteCommandTool
|
|
102
113
|
} from './tools/commands';
|
|
114
|
+
import { createDiscoverSkillsTool, createLoadSkillTool } from './tools/skills';
|
|
103
115
|
|
|
104
116
|
import { AISettingsWidget } from './widgets/ai-settings';
|
|
105
117
|
|
|
@@ -183,6 +195,54 @@ const genericProviderPlugin: JupyterFrontEndPlugin<void> = {
|
|
|
183
195
|
}
|
|
184
196
|
};
|
|
185
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Chat command registry plugin.
|
|
200
|
+
*/
|
|
201
|
+
const chatCommandRegistryPlugin: JupyterFrontEndPlugin<IChatCommandRegistry> = {
|
|
202
|
+
id: '@jupyterlite/ai:chat-command-registry',
|
|
203
|
+
description: 'Provide the chat command registry for JupyterLite AI.',
|
|
204
|
+
autoStart: true,
|
|
205
|
+
provides: IChatCommandRegistry,
|
|
206
|
+
activate: () => {
|
|
207
|
+
return new ChatCommandRegistry();
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Clear chat command plugin.
|
|
213
|
+
*/
|
|
214
|
+
const clearCommandPlugin: JupyterFrontEndPlugin<void> = {
|
|
215
|
+
id: '@jupyterlite/ai:clear-command',
|
|
216
|
+
description: 'Register the /clear chat command.',
|
|
217
|
+
autoStart: true,
|
|
218
|
+
requires: [IChatCommandRegistry],
|
|
219
|
+
activate: (app, registry: IChatCommandRegistry) => {
|
|
220
|
+
registry.addProvider(new ClearCommandProvider());
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Skills chat command plugin.
|
|
226
|
+
*/
|
|
227
|
+
const skillsCommandPlugin: JupyterFrontEndPlugin<void> = {
|
|
228
|
+
id: '@jupyterlite/ai:skills-command',
|
|
229
|
+
description: 'Register the /skills chat command.',
|
|
230
|
+
autoStart: true,
|
|
231
|
+
requires: [IChatCommandRegistry, ISkillRegistry],
|
|
232
|
+
activate: (
|
|
233
|
+
app,
|
|
234
|
+
registry: IChatCommandRegistry,
|
|
235
|
+
skillRegistry: ISkillRegistry
|
|
236
|
+
) => {
|
|
237
|
+
registry.addProvider(
|
|
238
|
+
new SkillsCommandProvider({
|
|
239
|
+
skillRegistry,
|
|
240
|
+
commands: app.commands
|
|
241
|
+
})
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
186
246
|
/**
|
|
187
247
|
* The chat model registry.
|
|
188
248
|
*/
|
|
@@ -226,7 +286,8 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
226
286
|
IRenderMimeRegistry,
|
|
227
287
|
IInputToolbarRegistryFactory,
|
|
228
288
|
IChatModelRegistry,
|
|
229
|
-
IAISettingsModel
|
|
289
|
+
IAISettingsModel,
|
|
290
|
+
IChatCommandRegistry
|
|
230
291
|
],
|
|
231
292
|
optional: [
|
|
232
293
|
IThemeManager,
|
|
@@ -241,6 +302,7 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
241
302
|
inputToolbarFactory: IInputToolbarRegistryFactory,
|
|
242
303
|
modelRegistry: IChatModelRegistry,
|
|
243
304
|
settingsModel: AISettingsModel,
|
|
305
|
+
chatCommandRegistry: IChatCommandRegistry,
|
|
244
306
|
themeManager?: IThemeManager,
|
|
245
307
|
restorer?: ILayoutRestorer,
|
|
246
308
|
labShell?: ILabShell,
|
|
@@ -275,6 +337,7 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
275
337
|
themeManager: themeManager ?? null,
|
|
276
338
|
inputToolbarFactory,
|
|
277
339
|
attachmentOpenerRegistry,
|
|
340
|
+
chatCommandRegistry,
|
|
278
341
|
createModel: async (name?: string) => {
|
|
279
342
|
const model = modelRegistry.createModel(name);
|
|
280
343
|
return { model };
|
|
@@ -400,6 +463,7 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
400
463
|
attachmentOpenerRegistry,
|
|
401
464
|
inputToolbarFactory,
|
|
402
465
|
settingsModel,
|
|
466
|
+
chatCommandRegistry,
|
|
403
467
|
tracker,
|
|
404
468
|
modelRegistry,
|
|
405
469
|
trans,
|
|
@@ -416,6 +480,7 @@ function registerCommands(
|
|
|
416
480
|
attachmentOpenerRegistry: IAttachmentOpenerRegistry,
|
|
417
481
|
inputToolbarFactory: IInputToolbarRegistryFactory,
|
|
418
482
|
settingsModel: AISettingsModel,
|
|
483
|
+
chatCommandRegistry: IChatCommandRegistry,
|
|
419
484
|
tracker: WidgetTracker<MainAreaChat | ChatWidget>,
|
|
420
485
|
modelRegistry: IChatModelRegistry,
|
|
421
486
|
trans: TranslationBundle,
|
|
@@ -481,7 +546,8 @@ function registerCommands(
|
|
|
481
546
|
rmRegistry,
|
|
482
547
|
themeManager: themeManager ?? null,
|
|
483
548
|
inputToolbarRegistry: inputToolbarFactory.create(),
|
|
484
|
-
attachmentOpenerRegistry
|
|
549
|
+
attachmentOpenerRegistry,
|
|
550
|
+
chatCommandRegistry
|
|
485
551
|
});
|
|
486
552
|
const widget = new MainAreaChat({
|
|
487
553
|
content,
|
|
@@ -666,6 +732,7 @@ const agentManagerFactory: JupyterFrontEndPlugin<AgentManagerFactory> =
|
|
|
666
732
|
provides: IAgentManagerFactory,
|
|
667
733
|
requires: [IAISettingsModel, IProviderRegistry],
|
|
668
734
|
optional: [
|
|
735
|
+
ISkillRegistry,
|
|
669
736
|
ICommandPalette,
|
|
670
737
|
ICompletionProviderManager,
|
|
671
738
|
ILayoutRestorer,
|
|
@@ -677,7 +744,8 @@ const agentManagerFactory: JupyterFrontEndPlugin<AgentManagerFactory> =
|
|
|
677
744
|
app: JupyterFrontEnd,
|
|
678
745
|
settingsModel: AISettingsModel,
|
|
679
746
|
providerRegistry: IProviderRegistry,
|
|
680
|
-
|
|
747
|
+
skillRegistry?: ISkillRegistry,
|
|
748
|
+
palette?: ICommandPalette,
|
|
681
749
|
completionManager?: ICompletionProviderManager,
|
|
682
750
|
restorer?: ILayoutRestorer,
|
|
683
751
|
secretsManager?: ISecretsManager,
|
|
@@ -687,6 +755,7 @@ const agentManagerFactory: JupyterFrontEndPlugin<AgentManagerFactory> =
|
|
|
687
755
|
const trans = (translator ?? nullTranslator).load('jupyterlite_ai');
|
|
688
756
|
const agentManagerFactory = new AgentManagerFactory({
|
|
689
757
|
settingsModel,
|
|
758
|
+
skillRegistry,
|
|
690
759
|
secretsManager,
|
|
691
760
|
token
|
|
692
761
|
});
|
|
@@ -797,13 +866,31 @@ const diffManager: JupyterFrontEndPlugin<IDiffManager> = {
|
|
|
797
866
|
}
|
|
798
867
|
};
|
|
799
868
|
|
|
869
|
+
/**
|
|
870
|
+
* Skill registry plugin
|
|
871
|
+
*/
|
|
872
|
+
const skillRegistryPlugin: JupyterFrontEndPlugin<ISkillRegistry> = {
|
|
873
|
+
id: '@jupyterlite/ai:skill-registry',
|
|
874
|
+
description: 'Provide the skill registry',
|
|
875
|
+
autoStart: true,
|
|
876
|
+
provides: ISkillRegistry,
|
|
877
|
+
activate: () => {
|
|
878
|
+
return new SkillRegistry();
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
|
|
800
882
|
const toolRegistry: JupyterFrontEndPlugin<IToolRegistry> = {
|
|
801
883
|
id: '@jupyterlite/ai:tool-registry',
|
|
802
884
|
description: 'Provide the AI tool registry',
|
|
803
885
|
autoStart: true,
|
|
804
886
|
requires: [IAISettingsModel],
|
|
887
|
+
optional: [ISkillRegistry],
|
|
805
888
|
provides: IToolRegistry,
|
|
806
|
-
activate: (
|
|
889
|
+
activate: (
|
|
890
|
+
app: JupyterFrontEnd,
|
|
891
|
+
settingsModel: AISettingsModel,
|
|
892
|
+
skillRegistry?: ISkillRegistry
|
|
893
|
+
) => {
|
|
807
894
|
const toolRegistry = new ToolRegistry();
|
|
808
895
|
|
|
809
896
|
// Add command operation tools
|
|
@@ -815,6 +902,13 @@ const toolRegistry: JupyterFrontEndPlugin<IToolRegistry> = {
|
|
|
815
902
|
|
|
816
903
|
toolRegistry.add('discover_commands', discoverCommandsTool);
|
|
817
904
|
toolRegistry.add('execute_command', executeCommandTool);
|
|
905
|
+
if (skillRegistry) {
|
|
906
|
+
toolRegistry.add(
|
|
907
|
+
'discover_skills',
|
|
908
|
+
createDiscoverSkillsTool(skillRegistry)
|
|
909
|
+
);
|
|
910
|
+
toolRegistry.add('load_skill', createLoadSkillTool(skillRegistry));
|
|
911
|
+
}
|
|
818
912
|
|
|
819
913
|
return toolRegistry;
|
|
820
914
|
}
|
|
@@ -900,6 +994,144 @@ const completionStatus: JupyterFrontEndPlugin<void> = {
|
|
|
900
994
|
}
|
|
901
995
|
};
|
|
902
996
|
|
|
997
|
+
/**
|
|
998
|
+
* Skills plugin: discovers and registers agent skills from the filesystem.
|
|
999
|
+
*/
|
|
1000
|
+
const skillsPlugin: JupyterFrontEndPlugin<void> = {
|
|
1001
|
+
id: '@jupyterlite/ai:skills',
|
|
1002
|
+
description: 'Discover and register agent skills',
|
|
1003
|
+
autoStart: true,
|
|
1004
|
+
requires: [IAISettingsModel, IDocumentManager, ISkillRegistry],
|
|
1005
|
+
optional: [ICommandPalette, ITranslator],
|
|
1006
|
+
activate: async (
|
|
1007
|
+
app: JupyterFrontEnd,
|
|
1008
|
+
settingsModel: AISettingsModel,
|
|
1009
|
+
docManager: IDocumentManager,
|
|
1010
|
+
skillRegistry: ISkillRegistry,
|
|
1011
|
+
palette?: ICommandPalette,
|
|
1012
|
+
translator?: ITranslator
|
|
1013
|
+
) => {
|
|
1014
|
+
const trans = (translator ?? nullTranslator).load('jupyterlite_ai');
|
|
1015
|
+
const validateResourcePath = (resourcePath: string): string | null => {
|
|
1016
|
+
if (resourcePath.startsWith('/')) {
|
|
1017
|
+
return null;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
const normalized = PathExt.normalize(resourcePath);
|
|
1021
|
+
if (normalized.startsWith('..') || normalized === '') {
|
|
1022
|
+
return null;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
return normalized;
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
let currentSkillsPaths = settingsModel.config.skillsPaths;
|
|
1029
|
+
let currentSkillDisposables = new DisposableSet();
|
|
1030
|
+
|
|
1031
|
+
const loadAndRegister = async () => {
|
|
1032
|
+
const skillsPaths = settingsModel.config.skillsPaths;
|
|
1033
|
+
const skills = await loadSkillsFromPaths(
|
|
1034
|
+
docManager.services.contents,
|
|
1035
|
+
skillsPaths
|
|
1036
|
+
);
|
|
1037
|
+
|
|
1038
|
+
const registrations = skills.map(skill => ({
|
|
1039
|
+
name: skill.name,
|
|
1040
|
+
description: skill.description,
|
|
1041
|
+
instructions: skill.instructions,
|
|
1042
|
+
resources: skill.resources,
|
|
1043
|
+
loadResource: async (resource: string) => {
|
|
1044
|
+
const validatedPath = validateResourcePath(resource);
|
|
1045
|
+
if (validatedPath === null) {
|
|
1046
|
+
return {
|
|
1047
|
+
name: skill.name,
|
|
1048
|
+
resource,
|
|
1049
|
+
error: 'Invalid resource path: path traversal not allowed'
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
if (!skill.resources.includes(validatedPath)) {
|
|
1054
|
+
return {
|
|
1055
|
+
name: skill.name,
|
|
1056
|
+
resource,
|
|
1057
|
+
error: `Resource not found: ${resource}`
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
const resourcePath = `${skill.path}/${validatedPath}`;
|
|
1062
|
+
try {
|
|
1063
|
+
const fileModel = await docManager.services.contents.get(
|
|
1064
|
+
resourcePath,
|
|
1065
|
+
{
|
|
1066
|
+
content: true
|
|
1067
|
+
}
|
|
1068
|
+
);
|
|
1069
|
+
if (typeof fileModel.content !== 'string') {
|
|
1070
|
+
return {
|
|
1071
|
+
name: skill.name,
|
|
1072
|
+
resource,
|
|
1073
|
+
error: 'Resource content is not a string'
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
return {
|
|
1077
|
+
name: skill.name,
|
|
1078
|
+
resource,
|
|
1079
|
+
content: fileModel.content
|
|
1080
|
+
};
|
|
1081
|
+
} catch (error) {
|
|
1082
|
+
return {
|
|
1083
|
+
name: skill.name,
|
|
1084
|
+
resource,
|
|
1085
|
+
error: `Failed to read resource: ${error}`
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}));
|
|
1090
|
+
|
|
1091
|
+
currentSkillDisposables.dispose();
|
|
1092
|
+
currentSkillDisposables = new DisposableSet();
|
|
1093
|
+
for (const registration of registrations) {
|
|
1094
|
+
currentSkillDisposables.add(skillRegistry.registerSkill(registration));
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
|
|
1098
|
+
app.commands.addCommand(CommandIds.refreshSkills, {
|
|
1099
|
+
label: trans.__('Refresh Agents Skills'),
|
|
1100
|
+
caption: trans.__(
|
|
1101
|
+
'Re-scan the agents skills directory and update the registry'
|
|
1102
|
+
),
|
|
1103
|
+
execute: async () => {
|
|
1104
|
+
await loadAndRegister();
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
|
|
1108
|
+
if (palette) {
|
|
1109
|
+
palette.addItem({
|
|
1110
|
+
command: CommandIds.refreshSkills,
|
|
1111
|
+
category: trans.__('AI Assistant')
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
loadAndRegister().catch(error =>
|
|
1116
|
+
console.warn('Failed to load skills on activation:', error)
|
|
1117
|
+
);
|
|
1118
|
+
|
|
1119
|
+
settingsModel.stateChanged.connect(() => {
|
|
1120
|
+
const newPaths = settingsModel.config.skillsPaths;
|
|
1121
|
+
if (
|
|
1122
|
+
newPaths.length === currentSkillsPaths.length &&
|
|
1123
|
+
newPaths.every((p, i) => p === currentSkillsPaths[i])
|
|
1124
|
+
) {
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
currentSkillsPaths = newPaths;
|
|
1128
|
+
loadAndRegister().catch(error =>
|
|
1129
|
+
console.warn('Failed to reload skills:', error)
|
|
1130
|
+
);
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
};
|
|
1134
|
+
|
|
903
1135
|
export default [
|
|
904
1136
|
providerRegistryPlugin,
|
|
905
1137
|
anthropicProviderPlugin,
|
|
@@ -909,12 +1141,17 @@ export default [
|
|
|
909
1141
|
genericProviderPlugin,
|
|
910
1142
|
settingsModel,
|
|
911
1143
|
diffManager,
|
|
1144
|
+
chatCommandRegistryPlugin,
|
|
1145
|
+
clearCommandPlugin,
|
|
1146
|
+
skillRegistryPlugin,
|
|
1147
|
+
skillsCommandPlugin,
|
|
912
1148
|
chatModelRegistry,
|
|
913
1149
|
plugin,
|
|
914
1150
|
toolRegistry,
|
|
915
1151
|
agentManagerFactory,
|
|
916
1152
|
inputToolbarFactory,
|
|
917
|
-
completionStatus
|
|
1153
|
+
completionStatus,
|
|
1154
|
+
skillsPlugin
|
|
918
1155
|
];
|
|
919
1156
|
|
|
920
1157
|
// Export extension points for other extensions to use
|
|
@@ -60,6 +60,8 @@ export interface IAIConfig {
|
|
|
60
60
|
showCellDiff: boolean;
|
|
61
61
|
showFileDiff: boolean;
|
|
62
62
|
diffDisplayMode: 'split' | 'unified';
|
|
63
|
+
// Paths to directories containing agent skills
|
|
64
|
+
skillsPaths: string[];
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
export class AISettingsModel extends VDomModel {
|
|
@@ -78,6 +80,7 @@ export class AISettingsModel extends VDomModel {
|
|
|
78
80
|
showCellDiff: true,
|
|
79
81
|
showFileDiff: true,
|
|
80
82
|
diffDisplayMode: 'split',
|
|
83
|
+
skillsPaths: ['.agents/skills', '_agents/skills'],
|
|
81
84
|
commandsRequiringApproval: [
|
|
82
85
|
'notebook:restart-run-all',
|
|
83
86
|
'notebook:run-cell',
|
|
@@ -113,7 +116,7 @@ You're designed to be a capable partner for data science, research, and developm
|
|
|
113
116
|
|
|
114
117
|
**⚡ Kernel Management:**
|
|
115
118
|
- Start new kernels with specified language or kernel name
|
|
116
|
-
- Execute code directly in
|
|
119
|
+
- Execute code directly in a kernel using jupyterlab-ai-commands execution commands (not console), without creating cells
|
|
117
120
|
- List running kernels and monitor their status
|
|
118
121
|
- Manage kernel lifecycle (start, monitor, shutdown)
|
|
119
122
|
|
|
@@ -130,18 +133,32 @@ You're designed to be a capable partner for data science, research, and developm
|
|
|
130
133
|
- Help with both quick fixes and long-term project planning
|
|
131
134
|
|
|
132
135
|
## How You Work
|
|
133
|
-
You
|
|
134
|
-
-
|
|
135
|
-
-
|
|
136
|
-
-
|
|
137
|
-
-
|
|
136
|
+
You interact with the user's JupyterLab environment primarily through the command system:
|
|
137
|
+
- Use 'discover_commands' to find available JupyterLab commands
|
|
138
|
+
- Use 'execute_command' to perform operations
|
|
139
|
+
- For file and notebook operations, use commands from the jupyterlab-ai-commands extension (prefixed with 'jupyterlab-ai-commands:')
|
|
140
|
+
- These commands provide comprehensive file and notebook manipulation: create, read, edit files/notebooks, manage cells, run code, etc.
|
|
141
|
+
- You can make systematic changes across multiple files and perform complex multi-step operations
|
|
142
|
+
- Skills are available via the skills tools: discover_skills (list) and load_skill (load instructions/resources)
|
|
143
|
+
|
|
144
|
+
## Tool & Skill Use Policy
|
|
145
|
+
- When tools or skills are available and the task requires actions or environment-specific facts, use them instead of guessing
|
|
146
|
+
- 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
|
|
147
|
+
- If a preloaded skills snapshot is provided in the system prompt, use it instead of calling discover_skills to list skills
|
|
148
|
+
- Only call discover_skills if the user explicitly asks for the latest list or you need to verify a skill not in the snapshot
|
|
149
|
+
- 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
|
|
150
|
+
- If you're unsure how to perform a request, discover relevant commands (discover_commands with task keywords)
|
|
151
|
+
- Use a relevant skill even when the user doesn't explicitly mention it
|
|
152
|
+
- Prefer the single most relevant tool or skill; if multiple could apply, ask a brief clarifying question
|
|
153
|
+
- Ask for missing required inputs before calling a tool or skill
|
|
154
|
+
- Before calling a tool or skill, briefly state why you're calling it
|
|
138
155
|
|
|
139
156
|
## Code Execution Strategy
|
|
140
157
|
When asked to run code or perform computations, choose the most appropriate approach:
|
|
141
|
-
- **For quick computations or one-off code execution**: Use kernel commands to
|
|
158
|
+
- **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.
|
|
142
159
|
- **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.
|
|
143
160
|
|
|
144
|
-
This means if the user asks you to "calculate the factorial of 100" or "check what library version is installed", run that directly
|
|
161
|
+
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.
|
|
145
162
|
|
|
146
163
|
## Your Approach
|
|
147
164
|
- **Context-aware**: You understand the user is working in a data science/research environment
|
|
@@ -150,6 +167,23 @@ This means if the user asks you to "calculate the factorial of 100" or "check wh
|
|
|
150
167
|
- **Collaborative**: You are a pair programming partner, not just a code generator
|
|
151
168
|
|
|
152
169
|
## Communication Style & Agent Behavior
|
|
170
|
+
IMPORTANT: Follow this message flow pattern for better user experience:
|
|
171
|
+
|
|
172
|
+
1. FIRST: Explain what you're going to do and your approach
|
|
173
|
+
2. THEN: Execute tools (these will show automatically with step numbers)
|
|
174
|
+
3. FINALLY: Provide a concise summary of what was accomplished
|
|
175
|
+
|
|
176
|
+
Example flow:
|
|
177
|
+
- "I'll help you create a notebook with example cells. Let me first create the file structure, then add Python and Markdown cells."
|
|
178
|
+
- [Tool executions happen with automatic step display]
|
|
179
|
+
- "Successfully created your notebook with 3 cells: a title, code example, and visualization cell."
|
|
180
|
+
|
|
181
|
+
Guidelines:
|
|
182
|
+
- Start responses with your plan/approach before tool execution
|
|
183
|
+
- Let the system handle tool execution display (don't duplicate details)
|
|
184
|
+
- End with a brief summary of accomplishments
|
|
185
|
+
- Use natural, conversational tone throughout
|
|
186
|
+
|
|
153
187
|
- **Conversational**: You maintain a friendly, natural conversation flow throughout the interaction
|
|
154
188
|
- **Progress Updates**: You write brief progress messages between tool uses that appear directly in the conversation
|
|
155
189
|
- **No Filler**: You avoid empty acknowledgments like "Sounds good!" or "Okay, I will..." - you get straight to work
|
|
@@ -168,13 +202,28 @@ This means if the user asks you to "calculate the factorial of 100" or "check wh
|
|
|
168
202
|
- You keep users informed of progress while staying focused on the task
|
|
169
203
|
|
|
170
204
|
## Multi-Step Task Handling
|
|
171
|
-
When users request complex tasks
|
|
172
|
-
-
|
|
173
|
-
-
|
|
174
|
-
-
|
|
175
|
-
-
|
|
176
|
-
|
|
177
|
-
|
|
205
|
+
When users request complex tasks, you use the command system to accomplish them:
|
|
206
|
+
- For file and notebook operations, use discover_commands with query 'jupyterlab-ai-commands' to find the curated set of AI commands (~17 commands)
|
|
207
|
+
- For other JupyterLab operations (terminal, launcher, UI), use specific keywords like 'terminal', 'launcher', etc.
|
|
208
|
+
- IMPORTANT: Always use 'jupyterlab-ai-commands' as the query for file/notebook tasks - this returns a focused set instead of 100+ generic commands
|
|
209
|
+
- For example, to create a notebook with cells:
|
|
210
|
+
1. discover_commands with query 'jupyterlab-ai-commands' to find available file/notebook commands
|
|
211
|
+
2. execute_command with 'jupyterlab-ai-commands:create-notebook' and required arguments
|
|
212
|
+
3. execute_command with 'jupyterlab-ai-commands:add-cell' multiple times to add cells
|
|
213
|
+
4. execute_command with 'jupyterlab-ai-commands:set-cell-content' to add content to cells
|
|
214
|
+
5. execute_command with 'jupyterlab-ai-commands:run-cell' when appropriate
|
|
215
|
+
|
|
216
|
+
## Kernel Preference for Notebooks and Consoles
|
|
217
|
+
When creating notebooks or consoles for a specific programming language, use the 'kernelPreference' argument:
|
|
218
|
+
Only create consoles when the user explicitly asks for one; otherwise prefer the jupyterlab-ai-commands kernel execution commands for running code.
|
|
219
|
+
- To specify by language: { "kernelPreference": { "language": "python" } } or { "kernelPreference": { "language": "julia" } }
|
|
220
|
+
- To specify by kernel name: { "kernelPreference": { "name": "python3" } } or { "kernelPreference": { "name": "julia-1.10" } }
|
|
221
|
+
- Example: execute_command with commandId="notebook:create-new" and args={ "kernelPreference": { "language": "python" } }
|
|
222
|
+
- Example: execute_command with commandId="console:create" and args={ "kernelPreference": { "name": "python3" } }
|
|
223
|
+
- Common kernel names: "python3" (Python), "julia-1.10" (Julia), "ir" (R), "xpython" (xeus-python)
|
|
224
|
+
- If unsure of exact kernel name, prefer using "language" which will match any kernel supporting that language
|
|
225
|
+
|
|
226
|
+
Always think through multi-step tasks and use commands to fully complete the user's request rather than stopping after just one action.
|
|
178
227
|
|
|
179
228
|
You are ready to help users build something great!`,
|
|
180
229
|
// Completion system prompt - also defined in schema/settings-model.json
|