@jupyterlite/ai 0.18.0 → 0.19.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/chat-commands/clear.d.ts +1 -0
- package/lib/chat-commands/index.d.ts +1 -0
- package/lib/chat-commands/skills.d.ts +2 -1
- package/lib/chat-model-handler.d.ts +3 -1
- package/lib/chat-model.d.ts +46 -8
- package/lib/chat-model.js +51 -21
- package/lib/completion/completion-provider.d.ts +3 -1
- package/lib/completion/completion-provider.js +1 -2
- package/lib/completion/index.d.ts +1 -0
- package/lib/components/clear-button.d.ts +1 -0
- package/lib/components/clear-button.js +3 -4
- package/lib/components/completion-status.d.ts +1 -0
- package/lib/components/completion-status.js +5 -4
- package/lib/components/index.d.ts +1 -0
- package/lib/components/model-select.d.ts +1 -0
- package/lib/components/model-select.js +62 -67
- package/lib/components/save-button.d.ts +1 -0
- package/lib/components/save-button.js +4 -5
- package/lib/components/stop-button.d.ts +1 -0
- package/lib/components/stop-button.js +3 -4
- package/lib/components/tool-select.d.ts +3 -1
- package/lib/components/tool-select.js +47 -60
- package/lib/components/usage-display.d.ts +4 -2
- package/lib/components/usage-display.js +50 -61
- package/lib/diff-manager.d.ts +3 -1
- package/lib/index.d.ts +3 -2
- package/lib/index.js +28 -17
- package/lib/models/settings-model.d.ts +3 -1
- package/lib/rendered-message-outputarea.d.ts +1 -0
- package/lib/tokens.d.ts +18 -640
- package/lib/tokens.js +2 -31
- package/lib/widgets/ai-settings.d.ts +3 -1
- package/lib/widgets/ai-settings.js +185 -349
- package/lib/widgets/main-area-chat.d.ts +1 -0
- package/lib/widgets/provider-config-dialog.d.ts +2 -1
- package/lib/widgets/provider-config-dialog.js +102 -167
- package/package.json +111 -258
- package/src/chat-commands/skills.ts +2 -2
- package/src/chat-model-handler.ts +6 -4
- package/src/chat-model.ts +66 -19
- package/src/completion/completion-provider.ts +6 -6
- package/src/components/clear-button.tsx +0 -2
- package/src/components/completion-status.tsx +2 -2
- package/src/components/model-select.tsx +1 -1
- package/src/components/stop-button.tsx +0 -2
- package/src/components/tool-select.tsx +10 -9
- package/src/components/usage-display.tsx +4 -2
- package/src/diff-manager.ts +4 -3
- package/src/index.ts +62 -44
- package/src/models/settings-model.ts +6 -6
- package/src/tokens.ts +23 -788
- package/src/widgets/ai-settings.tsx +14 -11
- package/src/widgets/provider-config-dialog.tsx +8 -8
- package/LICENSE +0 -30
- package/README.md +0 -49
- package/lib/agent.d.ts +0 -280
- package/lib/agent.js +0 -1103
- package/lib/icons.d.ts +0 -3
- package/lib/icons.js +0 -8
- package/lib/providers/built-in-providers.d.ts +0 -21
- package/lib/providers/built-in-providers.js +0 -233
- package/lib/providers/generated-model-info.d.ts +0 -8
- package/lib/providers/generated-model-info.js +0 -502
- package/lib/providers/model-info.d.ts +0 -6
- package/lib/providers/model-info.js +0 -91
- package/lib/providers/models.d.ts +0 -37
- package/lib/providers/models.js +0 -28
- package/lib/providers/provider-registry.d.ts +0 -49
- package/lib/providers/provider-registry.js +0 -72
- package/lib/providers/provider-tools.d.ts +0 -36
- package/lib/providers/provider-tools.js +0 -93
- package/lib/skills/index.d.ts +0 -4
- package/lib/skills/index.js +0 -7
- package/lib/skills/parse-skill.d.ts +0 -25
- package/lib/skills/parse-skill.js +0 -69
- package/lib/skills/skill-loader.d.ts +0 -25
- package/lib/skills/skill-loader.js +0 -133
- package/lib/skills/skill-registry.d.ts +0 -31
- package/lib/skills/skill-registry.js +0 -100
- package/lib/skills/types.d.ts +0 -29
- package/lib/skills/types.js +0 -5
- package/lib/tools/commands.d.ts +0 -11
- package/lib/tools/commands.js +0 -154
- package/lib/tools/skills.d.ts +0 -9
- package/lib/tools/skills.js +0 -73
- package/lib/tools/tool-registry.d.ts +0 -35
- package/lib/tools/tool-registry.js +0 -55
- package/lib/tools/web.d.ts +0 -8
- package/lib/tools/web.js +0 -196
- package/src/agent.ts +0 -1431
- package/src/icons.ts +0 -11
- package/src/providers/built-in-providers.ts +0 -241
- package/src/providers/generated-model-info.ts +0 -508
- package/src/providers/model-info.ts +0 -145
- package/src/providers/models.ts +0 -76
- package/src/providers/provider-registry.ts +0 -88
- package/src/providers/provider-tools.ts +0 -179
- package/src/skills/index.ts +0 -14
- package/src/skills/parse-skill.ts +0 -91
- package/src/skills/skill-loader.ts +0 -175
- package/src/skills/skill-registry.ts +0 -137
- package/src/skills/types.ts +0 -37
- package/src/tools/commands.ts +0 -210
- package/src/tools/skills.ts +0 -84
- package/src/tools/tool-registry.ts +0 -63
- package/src/tools/web.ts +0 -238
- package/src/types.d.ts +0 -4
- package/style/icons/jupyternaut-lite.svg +0 -7
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { ISignal, Signal } from '@lumino/signaling';
|
|
2
|
-
import type { LanguageModel } from 'ai';
|
|
3
|
-
import type { IModelOptions } from './models';
|
|
4
|
-
import { IProviderInfo, IProviderRegistry } from '../tokens';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Implementation of the provider registry
|
|
8
|
-
*/
|
|
9
|
-
export class ProviderRegistry implements IProviderRegistry {
|
|
10
|
-
/**
|
|
11
|
-
* Get a copy of all registered providers
|
|
12
|
-
*/
|
|
13
|
-
get providers(): Record<string, IProviderInfo> {
|
|
14
|
-
return { ...this._providers };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Signal emitted when providers are added or removed
|
|
19
|
-
*/
|
|
20
|
-
get providersChanged(): ISignal<IProviderRegistry, void> {
|
|
21
|
-
return this._providersChanged;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Register a new provider
|
|
26
|
-
* @param info Provider information with factories for chat and completion
|
|
27
|
-
*/
|
|
28
|
-
registerProvider(info: IProviderInfo): void {
|
|
29
|
-
if (info.id in this._providers) {
|
|
30
|
-
throw new Error(`Provider with id "${info.id}" is already registered`);
|
|
31
|
-
}
|
|
32
|
-
this._providers[info.id] = { ...info };
|
|
33
|
-
this._providersChanged.emit();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Get provider information by ID
|
|
38
|
-
* @param id Provider ID
|
|
39
|
-
* @returns Provider info or null if not found
|
|
40
|
-
*/
|
|
41
|
-
getProviderInfo(id: string): IProviderInfo | null {
|
|
42
|
-
return this._providers[id] || null;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Create a chat model instance using the specified provider
|
|
47
|
-
* @param id Provider ID
|
|
48
|
-
* @param options Model configuration options
|
|
49
|
-
* @returns Chat model instance or null if creation fails
|
|
50
|
-
*/
|
|
51
|
-
createChatModel(id: string, options: IModelOptions): LanguageModel | null {
|
|
52
|
-
const provider = this._providers[id];
|
|
53
|
-
if (!provider) {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return provider.factory(options);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Create a completion model instance using the specified provider
|
|
62
|
-
* @param id Provider ID
|
|
63
|
-
* @param options Model configuration options
|
|
64
|
-
* @returns Language model instance or null if creation fails
|
|
65
|
-
*/
|
|
66
|
-
createCompletionModel(
|
|
67
|
-
id: string,
|
|
68
|
-
options: IModelOptions
|
|
69
|
-
): LanguageModel | null {
|
|
70
|
-
const provider = this._providers[id];
|
|
71
|
-
if (!provider) {
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return provider.factory(options);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Get list of all available provider IDs
|
|
80
|
-
* @returns Array of provider IDs
|
|
81
|
-
*/
|
|
82
|
-
getAvailableProviders(): string[] {
|
|
83
|
-
return Object.keys(this._providers);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
private _providers: Record<string, IProviderInfo> = {};
|
|
87
|
-
private _providersChanged = new Signal<IProviderRegistry, void>(this);
|
|
88
|
-
}
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
import { anthropic } from '@ai-sdk/anthropic';
|
|
2
|
-
import { openai } from '@ai-sdk/openai';
|
|
3
|
-
import type { Tool } from 'ai';
|
|
4
|
-
|
|
5
|
-
import type {
|
|
6
|
-
IProviderInfo,
|
|
7
|
-
IProviderWebFetchImplementation,
|
|
8
|
-
IProviderWebSearchImplementation
|
|
9
|
-
} from '../tokens';
|
|
10
|
-
|
|
11
|
-
type ToolMap = Record<string, Tool>;
|
|
12
|
-
|
|
13
|
-
interface IWebSearchSettings {
|
|
14
|
-
enabled?: boolean;
|
|
15
|
-
externalWebAccess?: boolean;
|
|
16
|
-
searchContextSize?: 'low' | 'medium' | 'high';
|
|
17
|
-
allowedDomains?: string[];
|
|
18
|
-
blockedDomains?: string[];
|
|
19
|
-
maxUses?: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface IWebFetchSettings {
|
|
23
|
-
enabled?: boolean;
|
|
24
|
-
maxUses?: number;
|
|
25
|
-
maxContentTokens?: number;
|
|
26
|
-
allowedDomains?: string[];
|
|
27
|
-
blockedDomains?: string[];
|
|
28
|
-
citationsEnabled?: boolean;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Provider-level custom settings that control built-in web tools.
|
|
33
|
-
*/
|
|
34
|
-
export interface IProviderCustomSettings {
|
|
35
|
-
webSearch?: IWebSearchSettings;
|
|
36
|
-
webFetch?: IWebFetchSettings;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
interface IProviderToolContext {
|
|
40
|
-
providerInfo?: IProviderInfo | null;
|
|
41
|
-
customSettings?: IProviderCustomSettings;
|
|
42
|
-
hasFunctionTools: boolean;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const DEFAULT_ANTHROPIC_WEB_FETCH_MAX_USES = 2;
|
|
46
|
-
const DEFAULT_ANTHROPIC_WEB_FETCH_MAX_CONTENT_TOKENS = 12000;
|
|
47
|
-
|
|
48
|
-
function normalizeDomain(value: string): string {
|
|
49
|
-
const normalized = (value || '').trim().toLowerCase();
|
|
50
|
-
const withoutProtocol = normalized.replace(/^https?:\/\//, '');
|
|
51
|
-
const hostname = withoutProtocol.split('/')[0].trim();
|
|
52
|
-
// Treat "*.example.com" as "example.com" for provider domain filters.
|
|
53
|
-
return hostname.startsWith('*.') ? hostname.slice(2) : hostname;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function collectDomains(value?: string[]): string[] {
|
|
57
|
-
value = value || [];
|
|
58
|
-
const values = Array.from(
|
|
59
|
-
new Set(value.map(normalizeDomain).filter(domain => domain.length > 0))
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
return values;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function createOpenAIWebSearchTool(
|
|
66
|
-
webSearchSettings: IWebSearchSettings
|
|
67
|
-
): Tool {
|
|
68
|
-
const allowedDomains = collectDomains(webSearchSettings.allowedDomains);
|
|
69
|
-
return openai.tools.webSearch({
|
|
70
|
-
externalWebAccess: webSearchSettings.externalWebAccess,
|
|
71
|
-
searchContextSize: webSearchSettings.searchContextSize,
|
|
72
|
-
filters: allowedDomains.length > 0 ? { allowedDomains } : undefined
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function createAnthropicWebSearchTool(
|
|
77
|
-
webSearchSettings: IWebSearchSettings
|
|
78
|
-
): Tool {
|
|
79
|
-
const allowedDomains = collectDomains(webSearchSettings.allowedDomains);
|
|
80
|
-
const blockedDomains = collectDomains(webSearchSettings.blockedDomains);
|
|
81
|
-
return anthropic.tools.webSearch_20250305({
|
|
82
|
-
maxUses: webSearchSettings.maxUses,
|
|
83
|
-
allowedDomains: allowedDomains.length > 0 ? allowedDomains : undefined,
|
|
84
|
-
blockedDomains: blockedDomains.length > 0 ? blockedDomains : undefined
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function createAnthropicWebFetchTool(
|
|
89
|
-
webFetchSettings: IWebFetchSettings
|
|
90
|
-
): Tool {
|
|
91
|
-
const maxUses =
|
|
92
|
-
webFetchSettings.maxUses ?? DEFAULT_ANTHROPIC_WEB_FETCH_MAX_USES;
|
|
93
|
-
const maxContentTokens =
|
|
94
|
-
webFetchSettings.maxContentTokens ??
|
|
95
|
-
DEFAULT_ANTHROPIC_WEB_FETCH_MAX_CONTENT_TOKENS;
|
|
96
|
-
const allowedDomains = collectDomains(webFetchSettings.allowedDomains);
|
|
97
|
-
const blockedDomains = collectDomains(webFetchSettings.blockedDomains);
|
|
98
|
-
const citationsEnabled = webFetchSettings.citationsEnabled;
|
|
99
|
-
return anthropic.tools.webFetch_20250910({
|
|
100
|
-
maxUses,
|
|
101
|
-
maxContentTokens,
|
|
102
|
-
allowedDomains: allowedDomains.length > 0 ? allowedDomains : undefined,
|
|
103
|
-
blockedDomains: blockedDomains.length > 0 ? blockedDomains : undefined,
|
|
104
|
-
citations:
|
|
105
|
-
citationsEnabled !== undefined ? { enabled: citationsEnabled } : undefined
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function createWebSearchTool(
|
|
110
|
-
implementation: IProviderWebSearchImplementation,
|
|
111
|
-
webSearchSettings: IWebSearchSettings
|
|
112
|
-
): Tool {
|
|
113
|
-
switch (implementation) {
|
|
114
|
-
case 'openai':
|
|
115
|
-
return createOpenAIWebSearchTool(webSearchSettings);
|
|
116
|
-
case 'anthropic':
|
|
117
|
-
return createAnthropicWebSearchTool(webSearchSettings);
|
|
118
|
-
default:
|
|
119
|
-
throw new Error(
|
|
120
|
-
`Unsupported web search implementation: ${implementation}`
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function createWebFetchTool(
|
|
126
|
-
implementation: IProviderWebFetchImplementation,
|
|
127
|
-
webFetchSettings: IWebFetchSettings
|
|
128
|
-
): Tool {
|
|
129
|
-
switch (implementation) {
|
|
130
|
-
case 'anthropic':
|
|
131
|
-
return createAnthropicWebFetchTool(webFetchSettings);
|
|
132
|
-
default:
|
|
133
|
-
throw new Error(
|
|
134
|
-
`Unsupported web fetch implementation: ${implementation}`
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Create provider-defined tools from custom settings and provider capabilities.
|
|
141
|
-
*/
|
|
142
|
-
export function createProviderTools(options: IProviderToolContext): ToolMap {
|
|
143
|
-
const tools: ToolMap = {};
|
|
144
|
-
if (
|
|
145
|
-
!options.customSettings ||
|
|
146
|
-
!options.providerInfo?.providerToolCapabilities
|
|
147
|
-
) {
|
|
148
|
-
return tools;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const capabilities = options.providerInfo.providerToolCapabilities;
|
|
152
|
-
const webSearchSettings = options.customSettings.webSearch;
|
|
153
|
-
const webFetchSettings = options.customSettings.webFetch;
|
|
154
|
-
|
|
155
|
-
const webSearchEnabled = webSearchSettings?.enabled === true;
|
|
156
|
-
const webFetchEnabled = webFetchSettings?.enabled === true;
|
|
157
|
-
|
|
158
|
-
const webSearchCapability = capabilities.webSearch;
|
|
159
|
-
if (webSearchEnabled && webSearchSettings && webSearchCapability) {
|
|
160
|
-
const requiresNoFunctionTools =
|
|
161
|
-
webSearchCapability.requiresNoFunctionTools === true;
|
|
162
|
-
if (!requiresNoFunctionTools || !options.hasFunctionTools) {
|
|
163
|
-
tools.web_search = createWebSearchTool(
|
|
164
|
-
webSearchCapability.implementation,
|
|
165
|
-
webSearchSettings
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const webFetchCapability = capabilities.webFetch;
|
|
171
|
-
if (webFetchEnabled && webFetchSettings && webFetchCapability) {
|
|
172
|
-
tools.web_fetch = createWebFetchTool(
|
|
173
|
-
webFetchCapability.implementation,
|
|
174
|
-
webFetchSettings
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return tools;
|
|
179
|
-
}
|
package/src/skills/index.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Jupyter Development Team.
|
|
3
|
-
* Distributed under the terms of the Modified BSD License.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export { parseSkillMd, type IParsedSkill } from './parse-skill';
|
|
7
|
-
export { loadSkillsFromPaths, type ISkillFileDefinition } from './skill-loader';
|
|
8
|
-
export type {
|
|
9
|
-
ISkillDefinition,
|
|
10
|
-
ISkillRegistration,
|
|
11
|
-
ISkillResourceResult,
|
|
12
|
-
ISkillSummary
|
|
13
|
-
} from './types';
|
|
14
|
-
export { SkillRegistry } from './skill-registry';
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Jupyter Development Team.
|
|
3
|
-
* Distributed under the terms of the Modified BSD License.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { parse as parseYaml } from 'yaml';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Parsed skill definition from a SKILL.md file.
|
|
10
|
-
*/
|
|
11
|
-
export interface IParsedSkill {
|
|
12
|
-
name: string;
|
|
13
|
-
description: string;
|
|
14
|
-
instructions: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Parse a SKILL.md file content into a structured skill definition.
|
|
19
|
-
*
|
|
20
|
-
* Expected format:
|
|
21
|
-
* ```
|
|
22
|
-
* ---
|
|
23
|
-
* name: my-skill
|
|
24
|
-
* description: A brief description of the skill
|
|
25
|
-
* ---
|
|
26
|
-
* Full instructions body here...
|
|
27
|
-
* ```
|
|
28
|
-
*
|
|
29
|
-
* @param content - The raw content of a SKILL.md file
|
|
30
|
-
* @returns Parsed skill with name, description, and instructions
|
|
31
|
-
* @throws Error if the frontmatter is missing or invalid
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
export function parseSkillMd(content: string): IParsedSkill {
|
|
35
|
-
const normalizedContent = content
|
|
36
|
-
.replace(/^\uFEFF/, '')
|
|
37
|
-
.replace(/\r\n/g, '\n');
|
|
38
|
-
const lines = normalizedContent.split('\n');
|
|
39
|
-
|
|
40
|
-
if (lines[0]?.trim() !== '---') {
|
|
41
|
-
throw new Error('Invalid SKILL.md: missing frontmatter delimiters (---)');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const frontmatterLines: string[] = [];
|
|
45
|
-
let i = 1;
|
|
46
|
-
for (; i < lines.length; i++) {
|
|
47
|
-
const line = lines[i];
|
|
48
|
-
const trimmed = line.trim();
|
|
49
|
-
if (trimmed === '---') {
|
|
50
|
-
break;
|
|
51
|
-
}
|
|
52
|
-
frontmatterLines.push(line);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (i >= lines.length) {
|
|
56
|
-
throw new Error('Invalid SKILL.md: missing frontmatter delimiters (---)');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const frontmatter = frontmatterLines.join('\n');
|
|
60
|
-
const instructions = lines
|
|
61
|
-
.slice(i + 1)
|
|
62
|
-
.join('\n')
|
|
63
|
-
.trim();
|
|
64
|
-
|
|
65
|
-
let metadata: unknown;
|
|
66
|
-
try {
|
|
67
|
-
metadata = parseYaml(frontmatter);
|
|
68
|
-
} catch (error) {
|
|
69
|
-
throw new Error(
|
|
70
|
-
`Invalid SKILL.md: YAML frontmatter parse failed: ${error}`
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const data = metadata as Record<string, unknown> | null;
|
|
75
|
-
const name = data?.name;
|
|
76
|
-
const description = data?.description;
|
|
77
|
-
|
|
78
|
-
if (typeof name !== 'string' || name.trim().length === 0) {
|
|
79
|
-
throw new Error('Invalid SKILL.md: missing "name" in frontmatter');
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (typeof description !== 'string' || description.trim().length === 0) {
|
|
83
|
-
throw new Error('Invalid SKILL.md: missing "description" in frontmatter');
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return {
|
|
87
|
-
name: name.trim(),
|
|
88
|
-
description: description.trim(),
|
|
89
|
-
instructions
|
|
90
|
-
};
|
|
91
|
-
}
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Jupyter Development Team.
|
|
3
|
-
* Distributed under the terms of the Modified BSD License.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Contents } from '@jupyterlab/services';
|
|
7
|
-
import { PathExt } from '@jupyterlab/coreutils';
|
|
8
|
-
|
|
9
|
-
import { parseSkillMd, IParsedSkill } from './parse-skill';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* A skill definition loaded from the filesystem.
|
|
13
|
-
*/
|
|
14
|
-
export interface ISkillFileDefinition extends IParsedSkill {
|
|
15
|
-
/**
|
|
16
|
-
* Path to the skill directory (e.g. ".agents/skills/my-skill").
|
|
17
|
-
*/
|
|
18
|
-
path: string;
|
|
19
|
-
/**
|
|
20
|
-
* Paths to resource files relative to the skill directory.
|
|
21
|
-
*/
|
|
22
|
-
resources: string[];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Load skills from multiple directories. Each path is scanned in order;
|
|
27
|
-
* when the same skill name appears in more than one path, the first
|
|
28
|
-
* occurrence wins.
|
|
29
|
-
*
|
|
30
|
-
* @param contentsManager - The Jupyter contents manager
|
|
31
|
-
* @param skillsPaths - Ordered list of directories to scan
|
|
32
|
-
* @returns Merged array of loaded skill definitions
|
|
33
|
-
*/
|
|
34
|
-
export async function loadSkillsFromPaths(
|
|
35
|
-
contentsManager: Contents.IManager,
|
|
36
|
-
skillsPaths: string[]
|
|
37
|
-
): Promise<ISkillFileDefinition[]> {
|
|
38
|
-
const seen = new Set<string>();
|
|
39
|
-
const merged: ISkillFileDefinition[] = [];
|
|
40
|
-
|
|
41
|
-
for (const skillsPath of skillsPaths) {
|
|
42
|
-
const skills = await loadSkills(contentsManager, skillsPath);
|
|
43
|
-
for (const skill of skills) {
|
|
44
|
-
if (seen.has(skill.name)) {
|
|
45
|
-
console.debug(
|
|
46
|
-
`Skipping duplicate skill "${skill.name}" from "${skillsPath}" (already loaded from an earlier path).`
|
|
47
|
-
);
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
seen.add(skill.name);
|
|
51
|
-
merged.push(skill);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return merged;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Load skills from the filesystem by scanning a directory for subdirectories
|
|
60
|
-
* containing SKILL.md files.
|
|
61
|
-
*
|
|
62
|
-
* @param contentsManager - The Jupyter contents manager
|
|
63
|
-
* @param skillsPath - Path to the skills directory (e.g. ".agents/skills")
|
|
64
|
-
* @returns Array of loaded skill definitions
|
|
65
|
-
*/
|
|
66
|
-
async function loadSkills(
|
|
67
|
-
contentsManager: Contents.IManager,
|
|
68
|
-
skillsPath: string
|
|
69
|
-
): Promise<ISkillFileDefinition[]> {
|
|
70
|
-
const skills: ISkillFileDefinition[] = [];
|
|
71
|
-
|
|
72
|
-
// Walk each path segment from root to verify the directory exists before fetching it.
|
|
73
|
-
const segments = skillsPath.split('/').filter(s => s.length > 0);
|
|
74
|
-
let currentPath = '';
|
|
75
|
-
for (const segment of segments) {
|
|
76
|
-
let listing: Contents.IModel;
|
|
77
|
-
try {
|
|
78
|
-
listing = await contentsManager.get(currentPath, { content: true });
|
|
79
|
-
} catch (error) {
|
|
80
|
-
console.debug(
|
|
81
|
-
`Skills path segment not found at "${currentPath}":`,
|
|
82
|
-
error
|
|
83
|
-
);
|
|
84
|
-
return skills;
|
|
85
|
-
}
|
|
86
|
-
const children = (listing.content ?? []) as Contents.IModel[];
|
|
87
|
-
if (!children.some(c => c.type === 'directory' && c.name === segment)) {
|
|
88
|
-
return skills;
|
|
89
|
-
}
|
|
90
|
-
currentPath = PathExt.join(currentPath, segment);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const dirModel = await contentsManager.get(skillsPath, { content: true });
|
|
94
|
-
if (dirModel.type !== 'directory' || !dirModel.content) {
|
|
95
|
-
return skills;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
for (const child of dirModel.content as Contents.IModel[]) {
|
|
99
|
-
if (child.type !== 'directory') {
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// List the subdirectory to check if SKILL.md exists before requesting it
|
|
104
|
-
const subDir = await contentsManager.get(child.path, { content: true });
|
|
105
|
-
const subChildren = (subDir.content ?? []) as Contents.IModel[];
|
|
106
|
-
if (!subChildren.some(f => f.type === 'file' && f.name === 'SKILL.md')) {
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const skillMdPath = `${child.path}/SKILL.md`;
|
|
111
|
-
const fileModel = await contentsManager.get(skillMdPath, {
|
|
112
|
-
content: true
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
if (typeof fileModel.content !== 'string') {
|
|
116
|
-
console.warn(`Skipping ${skillMdPath}: content is not a string`);
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
const parsed = parseSkillMd(fileModel.content);
|
|
122
|
-
const resources = await collectResourcePaths(contentsManager, child.path);
|
|
123
|
-
skills.push({
|
|
124
|
-
...parsed,
|
|
125
|
-
path: child.path,
|
|
126
|
-
resources
|
|
127
|
-
});
|
|
128
|
-
} catch (error) {
|
|
129
|
-
console.warn(`Skipping skill at ${child.path}:`, error);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return skills;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Recursively collect paths to all resource files in a skill directory,
|
|
138
|
-
* excluding `SKILL.md`. Content is loaded on-demand when the agent
|
|
139
|
-
* requests a specific resource.
|
|
140
|
-
*/
|
|
141
|
-
async function collectResourcePaths(
|
|
142
|
-
contentsManager: Contents.IManager,
|
|
143
|
-
basePath: string
|
|
144
|
-
): Promise<string[]> {
|
|
145
|
-
const resourcePaths: string[] = [];
|
|
146
|
-
|
|
147
|
-
async function walk(dirPath: string): Promise<void> {
|
|
148
|
-
let dirModel: Contents.IModel;
|
|
149
|
-
try {
|
|
150
|
-
dirModel = await contentsManager.get(dirPath, { content: true });
|
|
151
|
-
} catch (error) {
|
|
152
|
-
console.warn(`Failed to list directory ${dirPath}:`, error);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
if (dirModel.type !== 'directory' || !dirModel.content) {
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
for (const item of dirModel.content as Contents.IModel[]) {
|
|
159
|
-
// Skip checkpoint directories
|
|
160
|
-
if (item.name === '.ipynb_checkpoints') {
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
if (item.type === 'directory') {
|
|
164
|
-
await walk(item.path);
|
|
165
|
-
} else if (item.type === 'file' && item.name !== 'SKILL.md') {
|
|
166
|
-
// Store path relative to the skill directory
|
|
167
|
-
const relativePath = PathExt.relative(basePath, item.path);
|
|
168
|
-
resourcePaths.push(relativePath);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
await walk(basePath);
|
|
174
|
-
return resourcePaths;
|
|
175
|
-
}
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Jupyter Development Team.
|
|
3
|
-
* Distributed under the terms of the Modified BSD License.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { DisposableDelegate } from '@lumino/disposable';
|
|
7
|
-
import { ISignal, Signal } from '@lumino/signaling';
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
ISkillDefinition,
|
|
11
|
-
ISkillRegistration,
|
|
12
|
-
ISkillResourceResult,
|
|
13
|
-
ISkillSummary
|
|
14
|
-
} from './types';
|
|
15
|
-
import { ISkillRegistry } from '../tokens';
|
|
16
|
-
|
|
17
|
-
interface ISkillEntry {
|
|
18
|
-
definition: ISkillDefinition;
|
|
19
|
-
loadResource?: (resource: string) => Promise<ISkillResourceResult>;
|
|
20
|
-
registrationId: number;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export class SkillRegistry implements ISkillRegistry {
|
|
24
|
-
/**
|
|
25
|
-
* Signal emitted when skills change.
|
|
26
|
-
*/
|
|
27
|
-
get skillsChanged(): ISignal<ISkillRegistry, void> {
|
|
28
|
-
return this._skillsChanged;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Register a single skill.
|
|
33
|
-
*/
|
|
34
|
-
registerSkill(skill: ISkillRegistration): DisposableDelegate {
|
|
35
|
-
const entry = this._registerSkillInternal(skill);
|
|
36
|
-
if (!entry) {
|
|
37
|
-
return new DisposableDelegate(() => undefined);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
this._skillsChanged.emit(void 0);
|
|
41
|
-
|
|
42
|
-
const registrationId = entry.registrationId;
|
|
43
|
-
const name = skill.name;
|
|
44
|
-
return new DisposableDelegate(() => {
|
|
45
|
-
const current = this._skills.get(name);
|
|
46
|
-
if (!current || current.registrationId !== registrationId) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
this._skills.delete(name);
|
|
51
|
-
this._skillsChanged.emit(void 0);
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* List all registered skills with summary info, optionally filtered by a
|
|
57
|
-
* search query (matches name or description, case-insensitive).
|
|
58
|
-
*/
|
|
59
|
-
listSkills(query?: string): ISkillSummary[] {
|
|
60
|
-
const summaries: ISkillSummary[] = [];
|
|
61
|
-
for (const entry of this._skills.values()) {
|
|
62
|
-
summaries.push({
|
|
63
|
-
name: entry.definition.name,
|
|
64
|
-
description: entry.definition.description
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
summaries.sort((a, b) => a.name.localeCompare(b.name));
|
|
68
|
-
|
|
69
|
-
if (!query) {
|
|
70
|
-
return summaries;
|
|
71
|
-
}
|
|
72
|
-
const term = query.toLowerCase();
|
|
73
|
-
return summaries.filter(
|
|
74
|
-
skill =>
|
|
75
|
-
skill.name.toLowerCase().includes(term) ||
|
|
76
|
-
skill.description.toLowerCase().includes(term)
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Get the full definition for a skill.
|
|
82
|
-
*/
|
|
83
|
-
getSkill(name: string): ISkillDefinition | null {
|
|
84
|
-
const entry = this._skills.get(name);
|
|
85
|
-
return entry ? entry.definition : null;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Load a resource for a skill.
|
|
90
|
-
*/
|
|
91
|
-
async getSkillResource(
|
|
92
|
-
name: string,
|
|
93
|
-
resource: string
|
|
94
|
-
): Promise<ISkillResourceResult> {
|
|
95
|
-
const entry = this._skills.get(name);
|
|
96
|
-
if (!entry) {
|
|
97
|
-
return {
|
|
98
|
-
name,
|
|
99
|
-
resource,
|
|
100
|
-
error: `Skill not found: ${name}`
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (!entry.loadResource) {
|
|
105
|
-
return {
|
|
106
|
-
name,
|
|
107
|
-
resource,
|
|
108
|
-
error: `Skill does not provide resources: ${name}`
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return entry.loadResource(resource);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
private _registerSkillInternal(
|
|
116
|
-
skill: ISkillRegistration
|
|
117
|
-
): ISkillEntry | null {
|
|
118
|
-
const existing = this._skills.get(skill.name);
|
|
119
|
-
if (existing) {
|
|
120
|
-
console.warn(`Skipping duplicate skill name "${skill.name}".`);
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const { loadResource, ...definition } = skill;
|
|
125
|
-
const entry: ISkillEntry = {
|
|
126
|
-
definition,
|
|
127
|
-
loadResource,
|
|
128
|
-
registrationId: this._nextRegistrationId++
|
|
129
|
-
};
|
|
130
|
-
this._skills.set(skill.name, entry);
|
|
131
|
-
return entry;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
private _skills = new Map<string, ISkillEntry>();
|
|
135
|
-
private _skillsChanged = new Signal<ISkillRegistry, void>(this);
|
|
136
|
-
private _nextRegistrationId = 0;
|
|
137
|
-
}
|