@tanstack/ai-code-mode-skills 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +199 -0
  2. package/dist/esm/code-mode-with-skills.d.ts +58 -0
  3. package/dist/esm/code-mode-with-skills.js +124 -0
  4. package/dist/esm/code-mode-with-skills.js.map +1 -0
  5. package/dist/esm/create-skill-management-tools.d.ts +40 -0
  6. package/dist/esm/create-skill-management-tools.js +198 -0
  7. package/dist/esm/create-skill-management-tools.js.map +1 -0
  8. package/dist/esm/create-skills-system-prompt.d.ts +22 -0
  9. package/dist/esm/create-skills-system-prompt.js +236 -0
  10. package/dist/esm/create-skills-system-prompt.js.map +1 -0
  11. package/dist/esm/generate-skill-types.d.ts +7 -0
  12. package/dist/esm/generate-skill-types.js +87 -0
  13. package/dist/esm/generate-skill-types.js.map +1 -0
  14. package/dist/esm/index.d.ts +13 -0
  15. package/dist/esm/index.js +29 -0
  16. package/dist/esm/index.js.map +1 -0
  17. package/dist/esm/select-relevant-skills.d.ts +29 -0
  18. package/dist/esm/select-relevant-skills.js +79 -0
  19. package/dist/esm/select-relevant-skills.js.map +1 -0
  20. package/dist/esm/skills-to-bindings.d.ts +34 -0
  21. package/dist/esm/skills-to-bindings.js +77 -0
  22. package/dist/esm/skills-to-bindings.js.map +1 -0
  23. package/dist/esm/skills-to-tools.d.ts +74 -0
  24. package/dist/esm/skills-to-tools.js +189 -0
  25. package/dist/esm/skills-to-tools.js.map +1 -0
  26. package/dist/esm/storage/file-storage.d.ts +27 -0
  27. package/dist/esm/storage/file-storage.js +149 -0
  28. package/dist/esm/storage/file-storage.js.map +1 -0
  29. package/dist/esm/storage/index.d.ts +3 -0
  30. package/dist/esm/storage/index.js +7 -0
  31. package/dist/esm/storage/index.js.map +1 -0
  32. package/dist/esm/storage/memory-storage.d.ts +17 -0
  33. package/dist/esm/storage/memory-storage.js +99 -0
  34. package/dist/esm/storage/memory-storage.js.map +1 -0
  35. package/dist/esm/trust-strategies.d.ts +50 -0
  36. package/dist/esm/trust-strategies.js +63 -0
  37. package/dist/esm/trust-strategies.js.map +1 -0
  38. package/dist/esm/types.d.ts +216 -0
  39. package/package.json +82 -0
  40. package/src/code-mode-with-skills.ts +204 -0
  41. package/src/create-skill-management-tools.ts +296 -0
  42. package/src/create-skills-system-prompt.ts +289 -0
  43. package/src/generate-skill-types.ts +162 -0
  44. package/src/index.ts +51 -0
  45. package/src/select-relevant-skills.ts +136 -0
  46. package/src/skills-to-bindings.ts +134 -0
  47. package/src/skills-to-tools.ts +319 -0
  48. package/src/storage/file-storage.ts +243 -0
  49. package/src/storage/index.ts +6 -0
  50. package/src/storage/memory-storage.ts +163 -0
  51. package/src/trust-strategies.ts +142 -0
  52. package/src/types.ts +289 -0
@@ -0,0 +1,22 @@
1
+ import { Skill } from './types.js';
2
+ interface CreateSkillsSystemPromptOptions {
3
+ /**
4
+ * Skills that were selected for this request
5
+ */
6
+ selectedSkills: Array<Skill>;
7
+ /**
8
+ * Total number of skills in the library
9
+ */
10
+ totalSkillCount: number;
11
+ /**
12
+ * Whether skills are exposed as direct tools (not just sandbox bindings)
13
+ * @default true
14
+ */
15
+ skillsAsTools?: boolean;
16
+ }
17
+ /**
18
+ * Create system prompt documentation for the skill library.
19
+ * This is appended to the Code Mode system prompt.
20
+ */
21
+ export declare function createSkillsSystemPrompt({ selectedSkills, totalSkillCount, skillsAsTools, }: CreateSkillsSystemPromptOptions): string;
22
+ export {};
@@ -0,0 +1,236 @@
1
+ import { generateSkillTypes } from "./generate-skill-types.js";
2
+ function generateExampleFromSchema(schema) {
3
+ if (schema.type === "object" && schema.properties) {
4
+ const props = schema.properties;
5
+ const example = {};
6
+ for (const [key, value] of Object.entries(props)) {
7
+ if (value.type === "string") example[key] = `'example_${key}'`;
8
+ else if (value.type === "number") example[key] = 0;
9
+ else if (value.type === "boolean") example[key] = true;
10
+ else if (value.type === "array") example[key] = [];
11
+ else example[key] = null;
12
+ }
13
+ return JSON.stringify(example).replace(/"/g, "");
14
+ }
15
+ return "{}";
16
+ }
17
+ function createSkillsSystemPrompt({
18
+ selectedSkills,
19
+ totalSkillCount,
20
+ skillsAsTools = true
21
+ }) {
22
+ if (totalSkillCount === 0) {
23
+ return `## Skill Library
24
+
25
+ You have access to a skill library for storing reusable code. The library is currently empty.
26
+
27
+ ### Skill Management Tools
28
+
29
+ - \`search_skills(query, limit?)\` - Search for skills (currently empty)
30
+ - \`get_skill(name)\` - Get full skill details including code
31
+ - \`register_skill(...)\` - Save working code as a reusable skill
32
+
33
+ When you write useful, reusable code, consider registering it as a skill for future use.
34
+
35
+ **Important**: Newly registered skills become available as tools on the **next message**, not immediately in the current conversation turn.
36
+ `;
37
+ }
38
+ if (selectedSkills.length === 0) {
39
+ return `## Skill Library
40
+
41
+ You have access to a persistent skill library with ${totalSkillCount} skill${totalSkillCount === 1 ? "" : "s"}. No skills were pre-loaded for this conversation based on context.
42
+
43
+ ### Skill Management Tools
44
+
45
+ - \`search_skills(query, limit?)\` - Search for relevant skills
46
+ - \`get_skill(name)\` - Get full skill details including code
47
+ - \`register_skill(...)\` - Save working code as a reusable skill
48
+
49
+ When you write useful, reusable code, consider registering it as a skill for future use.
50
+
51
+ **Important**: Newly registered skills become available as tools on the **next message**, not immediately in the current conversation turn.
52
+ `;
53
+ }
54
+ if (skillsAsTools) {
55
+ const skillToolDocs = selectedSkills.map((skill) => {
56
+ const inputExample = generateExampleFromSchema(skill.inputSchema);
57
+ const trustBadge = skill.trustLevel === "trusted" ? "✓ trusted" : skill.trustLevel === "provisional" ? "◐ provisional" : "○ untrusted";
58
+ return `
59
+ ### ${skill.name} [${trustBadge}]
60
+
61
+ ${skill.description}
62
+
63
+ ${skill.usageHints.map((h) => `- ${h}`).join("\n")}
64
+
65
+ **Input Schema:**
66
+ \`\`\`json
67
+ ${JSON.stringify(skill.inputSchema, null, 2)}
68
+ \`\`\`
69
+
70
+ **Output Schema:**
71
+ \`\`\`json
72
+ ${JSON.stringify(skill.outputSchema, null, 2)}
73
+ \`\`\`
74
+
75
+ **Example:**
76
+ Call the \`${skill.name}\` tool with: ${inputExample}
77
+ `;
78
+ }).join("\n---\n");
79
+ return `## Skill Library
80
+
81
+ ${selectedSkills.length} skill${selectedSkills.length === 1 ? "" : "s"} pre-loaded for this conversation (${totalSkillCount} total in library).
82
+
83
+ ### Available Skill Tools
84
+
85
+ These skills are available as **direct tools** you can call (marked with [SKILL] in description):
86
+
87
+ ${skillToolDocs}
88
+
89
+ ### Skill Management Tools
90
+
91
+ - \`search_skills(query, limit?)\` - Find additional skills not pre-loaded
92
+ - \`get_skill(name)\` - Get full details of any skill
93
+ - \`register_skill(...)\` - Save working code as a new skill
94
+
95
+ ### Using Skills
96
+
97
+ Skills are **regular tools** - call them directly like any other tool. No need to use \`execute_typescript\`.
98
+
99
+ ### Creating New Skills
100
+
101
+ When you write useful, reusable code with \`execute_typescript\`, register it:
102
+
103
+ \`\`\`typescript
104
+ // After verifying code works, call the register_skill tool
105
+ register_skill({
106
+ name: 'compare_npm_packages',
107
+ description: 'Compare download counts for multiple NPM packages',
108
+ code: \`
109
+ const { packages } = input;
110
+ const results = await Promise.all(
111
+ packages.map(pkg => external_getNpmDownloads({ package: pkg }))
112
+ );
113
+ return packages.map((pkg, i) => ({ package: pkg, downloads: results[i].downloads }))
114
+ .sort((a, b) => b.downloads - a.downloads);
115
+ \`,
116
+ inputSchema: {
117
+ type: 'object',
118
+ properties: { packages: { type: 'array', items: { type: 'string' } } },
119
+ required: ['packages']
120
+ },
121
+ outputSchema: {
122
+ type: 'array',
123
+ items: { type: 'object', properties: { package: { type: 'string' }, downloads: { type: 'number' } } }
124
+ },
125
+ usageHints: ['Use when comparing popularity of NPM packages'],
126
+ dependsOn: [],
127
+ });
128
+ \`\`\`
129
+
130
+ **Important**: Newly registered skills become available as tools on the **next message**, not immediately in the current conversation turn.
131
+ `;
132
+ }
133
+ const skillDocs = selectedSkills.map((skill) => {
134
+ const inputExample = generateExampleFromSchema(skill.inputSchema);
135
+ const trustBadge = skill.trustLevel === "trusted" ? "✓ trusted" : skill.trustLevel === "provisional" ? "◐ provisional" : "○ untrusted";
136
+ return `
137
+ ### skill_${skill.name} [${trustBadge}]
138
+
139
+ ${skill.description}
140
+
141
+ ${skill.usageHints.map((h) => `- ${h}`).join("\n")}
142
+
143
+ **Input Schema:**
144
+ \`\`\`json
145
+ ${JSON.stringify(skill.inputSchema, null, 2)}
146
+ \`\`\`
147
+
148
+ **Output Schema:**
149
+ \`\`\`json
150
+ ${JSON.stringify(skill.outputSchema, null, 2)}
151
+ \`\`\`
152
+
153
+ **Example:**
154
+ \`\`\`typescript
155
+ const result = await skill_${skill.name}(${inputExample});
156
+ \`\`\`
157
+ `;
158
+ }).join("\n---\n");
159
+ const typeStubs = generateSkillTypes(selectedSkills);
160
+ return `## Skill Library
161
+
162
+ ${selectedSkills.length} skill${selectedSkills.length === 1 ? "" : "s"} pre-loaded for this conversation (${totalSkillCount} total in library).
163
+
164
+ ### Pre-loaded Skills
165
+
166
+ These are available as \`skill_*\` functions in your TypeScript code:
167
+
168
+ ${skillDocs}
169
+
170
+ ### Type Definitions
171
+
172
+ \`\`\`typescript
173
+ ${typeStubs}
174
+ \`\`\`
175
+
176
+ ### Skill Management Tools
177
+
178
+ - \`search_skills(query, limit?)\` - Find additional skills not pre-loaded
179
+ - \`get_skill(name)\` - Get full details of any skill
180
+ - \`register_skill(...)\` - Save working code as a new skill
181
+
182
+ ### Using Skills
183
+
184
+ Skills work just like \`external_*\` functions inside \`execute_typescript\`:
185
+
186
+ \`\`\`typescript
187
+ // Call a pre-loaded skill
188
+ const stats = await skill_fetch_github_stats({ owner: 'tanstack', repo: 'query' });
189
+
190
+ // Compose skills with external tools
191
+ const repos = await external_searchRepositories({ query: 'react state' });
192
+ const detailed = await Promise.all(
193
+ repos.items.slice(0, 5).map(r =>
194
+ skill_fetch_github_stats({ owner: r.owner.login, repo: r.name })
195
+ )
196
+ );
197
+ \`\`\`
198
+
199
+ ### Creating New Skills
200
+
201
+ When you write useful, reusable code, register it:
202
+
203
+ \`\`\`typescript
204
+ // After verifying code works, call the register_skill tool
205
+ register_skill({
206
+ name: 'compare_npm_packages',
207
+ description: 'Compare download counts for multiple NPM packages',
208
+ code: \`
209
+ const { packages } = input;
210
+ const results = await Promise.all(
211
+ packages.map(pkg => external_getNpmDownloads({ package: pkg }))
212
+ );
213
+ return packages.map((pkg, i) => ({ package: pkg, downloads: results[i].downloads }))
214
+ .sort((a, b) => b.downloads - a.downloads);
215
+ \`,
216
+ inputSchema: {
217
+ type: 'object',
218
+ properties: { packages: { type: 'array', items: { type: 'string' } } },
219
+ required: ['packages']
220
+ },
221
+ outputSchema: {
222
+ type: 'array',
223
+ items: { type: 'object', properties: { package: { type: 'string' }, downloads: { type: 'number' } } }
224
+ },
225
+ usageHints: ['Use when comparing popularity of NPM packages'],
226
+ dependsOn: [],
227
+ });
228
+ \`\`\`
229
+
230
+ **Important**: Newly registered skills become available as tools on the **next message**, not immediately in the current conversation turn.
231
+ `;
232
+ }
233
+ export {
234
+ createSkillsSystemPrompt
235
+ };
236
+ //# sourceMappingURL=create-skills-system-prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-skills-system-prompt.js","sources":["../../src/create-skills-system-prompt.ts"],"sourcesContent":["import { generateSkillTypes } from './generate-skill-types'\nimport type { Skill } from './types'\n\ninterface CreateSkillsSystemPromptOptions {\n /**\n * Skills that were selected for this request\n */\n selectedSkills: Array<Skill>\n\n /**\n * Total number of skills in the library\n */\n totalSkillCount: number\n\n /**\n * Whether skills are exposed as direct tools (not just sandbox bindings)\n * @default true\n */\n skillsAsTools?: boolean\n}\n\n/**\n * Generate example input from a JSON Schema\n */\nfunction generateExampleFromSchema(schema: Record<string, unknown>): string {\n if (schema.type === 'object' && schema.properties) {\n const props = schema.properties as Record<string, { type: string }>\n const example: Record<string, unknown> = {}\n\n for (const [key, value] of Object.entries(props)) {\n if (value.type === 'string') example[key] = `'example_${key}'`\n else if (value.type === 'number') example[key] = 0\n else if (value.type === 'boolean') example[key] = true\n else if (value.type === 'array') example[key] = []\n else example[key] = null\n }\n\n return JSON.stringify(example).replace(/\"/g, '')\n }\n return '{}'\n}\n\n/**\n * Create system prompt documentation for the skill library.\n * This is appended to the Code Mode system prompt.\n */\nexport function createSkillsSystemPrompt({\n selectedSkills,\n totalSkillCount,\n skillsAsTools = true,\n}: CreateSkillsSystemPromptOptions): string {\n // No skills in library\n if (totalSkillCount === 0) {\n return `## Skill Library\n\nYou have access to a skill library for storing reusable code. The library is currently empty.\n\n### Skill Management Tools\n\n- \\`search_skills(query, limit?)\\` - Search for skills (currently empty)\n- \\`get_skill(name)\\` - Get full skill details including code\n- \\`register_skill(...)\\` - Save working code as a reusable skill\n\nWhen you write useful, reusable code, consider registering it as a skill for future use.\n\n**Important**: Newly registered skills become available as tools on the **next message**, not immediately in the current conversation turn.\n`\n }\n\n // No skills selected for this conversation\n if (selectedSkills.length === 0) {\n return `## Skill Library\n\nYou have access to a persistent skill library with ${totalSkillCount} skill${totalSkillCount === 1 ? '' : 's'}. No skills were pre-loaded for this conversation based on context.\n\n### Skill Management Tools\n\n- \\`search_skills(query, limit?)\\` - Search for relevant skills\n- \\`get_skill(name)\\` - Get full skill details including code\n- \\`register_skill(...)\\` - Save working code as a reusable skill\n\nWhen you write useful, reusable code, consider registering it as a skill for future use.\n\n**Important**: Newly registered skills become available as tools on the **next message**, not immediately in the current conversation turn.\n`\n }\n\n if (skillsAsTools) {\n // Skills are available as direct tools\n const skillToolDocs = selectedSkills\n .map((skill) => {\n const inputExample = generateExampleFromSchema(skill.inputSchema)\n const trustBadge =\n skill.trustLevel === 'trusted'\n ? '✓ trusted'\n : skill.trustLevel === 'provisional'\n ? '◐ provisional'\n : '○ untrusted'\n\n return `\n### ${skill.name} [${trustBadge}]\n\n${skill.description}\n\n${skill.usageHints.map((h) => `- ${h}`).join('\\n')}\n\n**Input Schema:**\n\\`\\`\\`json\n${JSON.stringify(skill.inputSchema, null, 2)}\n\\`\\`\\`\n\n**Output Schema:**\n\\`\\`\\`json\n${JSON.stringify(skill.outputSchema, null, 2)}\n\\`\\`\\`\n\n**Example:**\nCall the \\`${skill.name}\\` tool with: ${inputExample}\n`\n })\n .join('\\n---\\n')\n\n return `## Skill Library\n\n${selectedSkills.length} skill${selectedSkills.length === 1 ? '' : 's'} pre-loaded for this conversation (${totalSkillCount} total in library).\n\n### Available Skill Tools\n\nThese skills are available as **direct tools** you can call (marked with [SKILL] in description):\n\n${skillToolDocs}\n\n### Skill Management Tools\n\n- \\`search_skills(query, limit?)\\` - Find additional skills not pre-loaded\n- \\`get_skill(name)\\` - Get full details of any skill\n- \\`register_skill(...)\\` - Save working code as a new skill\n\n### Using Skills\n\nSkills are **regular tools** - call them directly like any other tool. No need to use \\`execute_typescript\\`.\n\n### Creating New Skills\n\nWhen you write useful, reusable code with \\`execute_typescript\\`, register it:\n\n\\`\\`\\`typescript\n// After verifying code works, call the register_skill tool\nregister_skill({\n name: 'compare_npm_packages',\n description: 'Compare download counts for multiple NPM packages',\n code: \\`\n const { packages } = input;\n const results = await Promise.all(\n packages.map(pkg => external_getNpmDownloads({ package: pkg }))\n );\n return packages.map((pkg, i) => ({ package: pkg, downloads: results[i].downloads }))\n .sort((a, b) => b.downloads - a.downloads);\n \\`,\n inputSchema: { \n type: 'object', \n properties: { packages: { type: 'array', items: { type: 'string' } } },\n required: ['packages']\n },\n outputSchema: {\n type: 'array',\n items: { type: 'object', properties: { package: { type: 'string' }, downloads: { type: 'number' } } }\n },\n usageHints: ['Use when comparing popularity of NPM packages'],\n dependsOn: [],\n});\n\\`\\`\\`\n\n**Important**: Newly registered skills become available as tools on the **next message**, not immediately in the current conversation turn.\n`\n }\n\n // Skills as sandbox bindings (legacy mode)\n const skillDocs = selectedSkills\n .map((skill) => {\n const inputExample = generateExampleFromSchema(skill.inputSchema)\n const trustBadge =\n skill.trustLevel === 'trusted'\n ? '✓ trusted'\n : skill.trustLevel === 'provisional'\n ? '◐ provisional'\n : '○ untrusted'\n\n return `\n### skill_${skill.name} [${trustBadge}]\n\n${skill.description}\n\n${skill.usageHints.map((h) => `- ${h}`).join('\\n')}\n\n**Input Schema:**\n\\`\\`\\`json\n${JSON.stringify(skill.inputSchema, null, 2)}\n\\`\\`\\`\n\n**Output Schema:**\n\\`\\`\\`json\n${JSON.stringify(skill.outputSchema, null, 2)}\n\\`\\`\\`\n\n**Example:**\n\\`\\`\\`typescript\nconst result = await skill_${skill.name}(${inputExample});\n\\`\\`\\`\n`\n })\n .join('\\n---\\n')\n\n // Generate type stubs for selected skills\n const typeStubs = generateSkillTypes(selectedSkills)\n\n return `## Skill Library\n\n${selectedSkills.length} skill${selectedSkills.length === 1 ? '' : 's'} pre-loaded for this conversation (${totalSkillCount} total in library).\n\n### Pre-loaded Skills\n\nThese are available as \\`skill_*\\` functions in your TypeScript code:\n\n${skillDocs}\n\n### Type Definitions\n\n\\`\\`\\`typescript\n${typeStubs}\n\\`\\`\\`\n\n### Skill Management Tools\n\n- \\`search_skills(query, limit?)\\` - Find additional skills not pre-loaded\n- \\`get_skill(name)\\` - Get full details of any skill\n- \\`register_skill(...)\\` - Save working code as a new skill\n\n### Using Skills\n\nSkills work just like \\`external_*\\` functions inside \\`execute_typescript\\`:\n\n\\`\\`\\`typescript\n// Call a pre-loaded skill\nconst stats = await skill_fetch_github_stats({ owner: 'tanstack', repo: 'query' });\n\n// Compose skills with external tools\nconst repos = await external_searchRepositories({ query: 'react state' });\nconst detailed = await Promise.all(\n repos.items.slice(0, 5).map(r => \n skill_fetch_github_stats({ owner: r.owner.login, repo: r.name })\n )\n);\n\\`\\`\\`\n\n### Creating New Skills\n\nWhen you write useful, reusable code, register it:\n\n\\`\\`\\`typescript\n// After verifying code works, call the register_skill tool\nregister_skill({\n name: 'compare_npm_packages',\n description: 'Compare download counts for multiple NPM packages',\n code: \\`\n const { packages } = input;\n const results = await Promise.all(\n packages.map(pkg => external_getNpmDownloads({ package: pkg }))\n );\n return packages.map((pkg, i) => ({ package: pkg, downloads: results[i].downloads }))\n .sort((a, b) => b.downloads - a.downloads);\n \\`,\n inputSchema: { \n type: 'object', \n properties: { packages: { type: 'array', items: { type: 'string' } } },\n required: ['packages']\n },\n outputSchema: {\n type: 'array',\n items: { type: 'object', properties: { package: { type: 'string' }, downloads: { type: 'number' } } }\n },\n usageHints: ['Use when comparing popularity of NPM packages'],\n dependsOn: [],\n});\n\\`\\`\\`\n\n**Important**: Newly registered skills become available as tools on the **next message**, not immediately in the current conversation turn.\n`\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { Skill } from './types.js';
2
+ /**
3
+ * Generate TypeScript type stubs for skills.
4
+ * These are included in the system prompt so the LLM knows
5
+ * the exact type signatures of available skills.
6
+ */
7
+ export declare function generateSkillTypes(skills: Array<Skill>): string;
@@ -0,0 +1,87 @@
1
+ function schemaToType(schema) {
2
+ if (typeof schema !== "object") {
3
+ return "unknown";
4
+ }
5
+ const schemaType = schema.type;
6
+ if (schemaType === "string") return "string";
7
+ if (schemaType === "number" || schemaType === "integer") return "number";
8
+ if (schemaType === "boolean") return "boolean";
9
+ if (schemaType === "null") return "null";
10
+ if (schemaType === "array") {
11
+ const items = schema.items;
12
+ const itemType = items ? schemaToType(items) : "unknown";
13
+ return `Array<${itemType}>`;
14
+ }
15
+ if (schemaType === "object" && schema.properties) {
16
+ const properties = schema.properties;
17
+ const required = new Set(
18
+ schema.required ?? []
19
+ );
20
+ const props = Object.entries(properties).map(([key, propSchema]) => {
21
+ const optional = required.has(key) ? "" : "?";
22
+ const propType = schemaToType(propSchema);
23
+ const safeName = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : `"${key}"`;
24
+ return ` ${safeName}${optional}: ${propType};`;
25
+ }).join("\n");
26
+ return `{
27
+ ${props}
28
+ }`;
29
+ }
30
+ if (schema.enum) {
31
+ const enumValues = schema.enum;
32
+ return enumValues.map((v) => JSON.stringify(v)).join(" | ");
33
+ }
34
+ if (schema.anyOf || schema.oneOf) {
35
+ const variants = schema.anyOf || schema.oneOf;
36
+ return variants.map((v) => schemaToType(v)).join(" | ");
37
+ }
38
+ if (Array.isArray(schemaType)) {
39
+ return schemaType.map((t) => {
40
+ if (t === "string") return "string";
41
+ if (t === "number" || t === "integer") return "number";
42
+ if (t === "boolean") return "boolean";
43
+ if (t === "null") return "null";
44
+ if (t === "array") return "Array<unknown>";
45
+ if (t === "object") return "object";
46
+ return "unknown";
47
+ }).join(" | ");
48
+ }
49
+ return "unknown";
50
+ }
51
+ function capitalize(str) {
52
+ return str.charAt(0).toUpperCase() + str.slice(1);
53
+ }
54
+ function toPascalCase(str) {
55
+ return str.split("_").map((part) => capitalize(part)).join("");
56
+ }
57
+ function generateSkillTypes(skills) {
58
+ const declarations = [];
59
+ for (const skill of skills) {
60
+ const baseName = toPascalCase(skill.name);
61
+ const inputTypeName = `Skill${baseName}Input`;
62
+ const outputTypeName = `Skill${baseName}Output`;
63
+ const inputType = schemaToType(skill.inputSchema);
64
+ if (skill.inputSchema.type === "object" && skill.inputSchema.properties && Object.keys(skill.inputSchema.properties).length > 0) {
65
+ declarations.push(`interface ${inputTypeName} ${inputType}`);
66
+ }
67
+ const outputType = schemaToType(skill.outputSchema);
68
+ if (skill.outputSchema.type === "object" && skill.outputSchema.properties && Object.keys(skill.outputSchema.properties).length > 0) {
69
+ declarations.push(`interface ${outputTypeName} ${outputType}`);
70
+ }
71
+ const inputRef = skill.inputSchema.type === "object" && skill.inputSchema.properties && Object.keys(skill.inputSchema.properties).length > 0 ? inputTypeName : inputType;
72
+ const outputRef = skill.outputSchema.type === "object" && skill.outputSchema.properties && Object.keys(skill.outputSchema.properties).length > 0 ? outputTypeName : outputType;
73
+ const hintsDoc = skill.usageHints.map((h) => ` * @hint ${h}`).join("\n");
74
+ declarations.push(
75
+ `/**
76
+ * ${skill.description}
77
+ ${hintsDoc}
78
+ */
79
+ declare function skill_${skill.name}(input: ${inputRef}): Promise<${outputRef}>;`
80
+ );
81
+ }
82
+ return declarations.join("\n\n");
83
+ }
84
+ export {
85
+ generateSkillTypes
86
+ };
87
+ //# sourceMappingURL=generate-skill-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-skill-types.js","sources":["../../src/generate-skill-types.ts"],"sourcesContent":["import type { Skill } from './types'\n\n/**\n * Convert a JSON Schema to a TypeScript type string\n */\nfunction schemaToType(schema: Record<string, unknown>): string {\n if (typeof schema !== 'object') {\n return 'unknown'\n }\n\n const schemaType = schema.type\n\n // Handle basic types\n if (schemaType === 'string') return 'string'\n if (schemaType === 'number' || schemaType === 'integer') return 'number'\n if (schemaType === 'boolean') return 'boolean'\n if (schemaType === 'null') return 'null'\n\n // Handle arrays\n if (schemaType === 'array') {\n const items = schema.items as Record<string, unknown> | undefined\n const itemType = items ? schemaToType(items) : 'unknown'\n return `Array<${itemType}>`\n }\n\n // Handle objects with properties\n if (schemaType === 'object' && schema.properties) {\n const properties = schema.properties as Record<\n string,\n Record<string, unknown>\n >\n const required = new Set(\n (schema.required as Array<string> | undefined) ?? [],\n )\n\n const props = Object.entries(properties)\n .map(([key, propSchema]) => {\n const optional = required.has(key) ? '' : '?'\n const propType = schemaToType(propSchema)\n // Handle property names that need quoting\n const safeName = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)\n ? key\n : `\"${key}\"`\n return ` ${safeName}${optional}: ${propType};`\n })\n .join('\\n')\n\n return `{\\n${props}\\n}`\n }\n\n // Handle enums\n if (schema.enum) {\n const enumValues = schema.enum as Array<unknown>\n return enumValues.map((v) => JSON.stringify(v)).join(' | ')\n }\n\n // Handle union types (anyOf, oneOf)\n if (schema.anyOf || schema.oneOf) {\n const variants = (schema.anyOf || schema.oneOf) as Array<\n Record<string, unknown>\n >\n return variants.map((v) => schemaToType(v)).join(' | ')\n }\n\n // Handle type arrays (e.g., [\"string\", \"null\"])\n if (Array.isArray(schemaType)) {\n return schemaType\n .map((t) => {\n if (t === 'string') return 'string'\n if (t === 'number' || t === 'integer') return 'number'\n if (t === 'boolean') return 'boolean'\n if (t === 'null') return 'null'\n if (t === 'array') return 'Array<unknown>'\n if (t === 'object') return 'object'\n return 'unknown'\n })\n .join(' | ')\n }\n\n // Fallback for unknown schemas\n return 'unknown'\n}\n\n/**\n * Capitalize the first letter of a string\n */\nfunction capitalize(str: string): string {\n return str.charAt(0).toUpperCase() + str.slice(1)\n}\n\n/**\n * Convert snake_case to PascalCase\n */\nfunction toPascalCase(str: string): string {\n return str\n .split('_')\n .map((part) => capitalize(part))\n .join('')\n}\n\n/**\n * Generate TypeScript type stubs for skills.\n * These are included in the system prompt so the LLM knows\n * the exact type signatures of available skills.\n */\nexport function generateSkillTypes(skills: Array<Skill>): string {\n const declarations: Array<string> = []\n\n for (const skill of skills) {\n const baseName = toPascalCase(skill.name)\n const inputTypeName = `Skill${baseName}Input`\n const outputTypeName = `Skill${baseName}Output`\n\n // Generate input type\n const inputType = schemaToType(skill.inputSchema)\n if (\n skill.inputSchema.type === 'object' &&\n skill.inputSchema.properties &&\n Object.keys(skill.inputSchema.properties as object).length > 0\n ) {\n declarations.push(`interface ${inputTypeName} ${inputType}`)\n }\n\n // Generate output type\n const outputType = schemaToType(skill.outputSchema)\n if (\n skill.outputSchema.type === 'object' &&\n skill.outputSchema.properties &&\n Object.keys(skill.outputSchema.properties as object).length > 0\n ) {\n declarations.push(`interface ${outputTypeName} ${outputType}`)\n }\n\n // Determine type references\n const inputRef =\n skill.inputSchema.type === 'object' &&\n skill.inputSchema.properties &&\n Object.keys(skill.inputSchema.properties as object).length > 0\n ? inputTypeName\n : inputType\n\n const outputRef =\n skill.outputSchema.type === 'object' &&\n skill.outputSchema.properties &&\n Object.keys(skill.outputSchema.properties as object).length > 0\n ? outputTypeName\n : outputType\n\n // Generate function declaration with JSDoc\n const hintsDoc = skill.usageHints.map((h) => ` * @hint ${h}`).join('\\n')\n\n declarations.push(\n `/**\n * ${skill.description}\n${hintsDoc}\n */\ndeclare function skill_${skill.name}(input: ${inputRef}): Promise<${outputRef}>;`,\n )\n }\n\n return declarations.join('\\n\\n')\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ export { codeModeWithSkills, createCodeModeWithSkillsConfig, } from './code-mode-with-skills.js';
2
+ export type { CodeModeWithSkillsOptions, CodeModeWithSkillsResult, } from './code-mode-with-skills.js';
3
+ export { createDefaultTrustStrategy, createAlwaysTrustedStrategy, createRelaxedTrustStrategy, createCustomTrustStrategy, } from './trust-strategies.js';
4
+ export type { TrustStrategy } from './trust-strategies.js';
5
+ export { selectRelevantSkills } from './select-relevant-skills.js';
6
+ export { skillsToTools, skillToTool } from './skills-to-tools.js';
7
+ export type { SkillToToolOptions } from './skills-to-tools.js';
8
+ export { skillsToBindings, skillsToSimpleBindings } from './skills-to-bindings.js';
9
+ export { createSkillManagementTools } from './create-skill-management-tools.js';
10
+ export { createSkillsSystemPrompt } from './create-skills-system-prompt.js';
11
+ export { generateSkillTypes } from './generate-skill-types.js';
12
+ export * from './storage.js';
13
+ export type { Skill, SkillIndexEntry, SkillStorage, SkillsConfig, SkillStats, TrustLevel, SkillBinding, } from './types.js';
@@ -0,0 +1,29 @@
1
+ import { codeModeWithSkills, createCodeModeWithSkillsConfig } from "./code-mode-with-skills.js";
2
+ import { createAlwaysTrustedStrategy, createCustomTrustStrategy, createDefaultTrustStrategy, createRelaxedTrustStrategy } from "./trust-strategies.js";
3
+ import { selectRelevantSkills } from "./select-relevant-skills.js";
4
+ import { skillToTool, skillsToTools } from "./skills-to-tools.js";
5
+ import { skillsToBindings, skillsToSimpleBindings } from "./skills-to-bindings.js";
6
+ import { createSkillManagementTools } from "./create-skill-management-tools.js";
7
+ import { createSkillsSystemPrompt } from "./create-skills-system-prompt.js";
8
+ import { generateSkillTypes } from "./generate-skill-types.js";
9
+ import { createFileSkillStorage } from "./storage/file-storage.js";
10
+ import { createMemorySkillStorage } from "./storage/memory-storage.js";
11
+ export {
12
+ codeModeWithSkills,
13
+ createAlwaysTrustedStrategy,
14
+ createCodeModeWithSkillsConfig,
15
+ createCustomTrustStrategy,
16
+ createDefaultTrustStrategy,
17
+ createFileSkillStorage,
18
+ createMemorySkillStorage,
19
+ createRelaxedTrustStrategy,
20
+ createSkillManagementTools,
21
+ createSkillsSystemPrompt,
22
+ generateSkillTypes,
23
+ selectRelevantSkills,
24
+ skillToTool,
25
+ skillsToBindings,
26
+ skillsToSimpleBindings,
27
+ skillsToTools
28
+ };
29
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
@@ -0,0 +1,29 @@
1
+ import { AnyTextAdapter, ModelMessage } from '@tanstack/ai';
2
+ import { Skill, SkillIndexEntry, SkillStorage } from './types.js';
3
+ interface SelectRelevantSkillsOptions {
4
+ /**
5
+ * Text adapter for skill selection (should be a cheap/fast model)
6
+ */
7
+ adapter: AnyTextAdapter;
8
+ /**
9
+ * Current conversation messages
10
+ */
11
+ messages: Array<ModelMessage>;
12
+ /**
13
+ * Skill index (lightweight metadata)
14
+ */
15
+ skillIndex: Array<SkillIndexEntry>;
16
+ /**
17
+ * Maximum number of skills to select
18
+ */
19
+ maxSkills: number;
20
+ /**
21
+ * Storage to load full skill data
22
+ */
23
+ storage: SkillStorage;
24
+ }
25
+ /**
26
+ * Use a cheap/fast LLM to select which skills are relevant for the current conversation
27
+ */
28
+ export declare function selectRelevantSkills({ adapter, messages, skillIndex, maxSkills, storage, }: SelectRelevantSkillsOptions): Promise<Array<Skill>>;
29
+ export {};
@@ -0,0 +1,79 @@
1
+ import { chat } from "@tanstack/ai";
2
+ async function selectRelevantSkills({
3
+ adapter,
4
+ messages,
5
+ skillIndex,
6
+ maxSkills,
7
+ storage
8
+ }) {
9
+ if (skillIndex.length === 0) return [];
10
+ if (messages.length === 0) return [];
11
+ const recentMessages = messages.slice(-5);
12
+ const recentContext = recentMessages.map((m) => {
13
+ let content;
14
+ if (typeof m.content === "string") {
15
+ content = m.content;
16
+ } else if (Array.isArray(m.content)) {
17
+ content = m.content.map((part) => {
18
+ if (typeof part === "string") return part;
19
+ if (part && typeof part === "object" && "text" in part)
20
+ return part.text;
21
+ return "[non-text content]";
22
+ }).join(" ");
23
+ } else {
24
+ content = "[complex content]";
25
+ }
26
+ return `${m.role}: ${content}`;
27
+ }).join("\n");
28
+ const skillCatalog = skillIndex.map((s) => {
29
+ const hints = s.usageHints.length > 0 ? ` (${s.usageHints[0]})` : "";
30
+ return `- ${s.name}: ${s.description}${hints}`;
31
+ }).join("\n");
32
+ const selectionPrompt = `Given this conversation context:
33
+ ---
34
+ ${recentContext}
35
+ ---
36
+
37
+ Which of these skills (if any) would be useful for the next response? Return a JSON array of skill names, max ${maxSkills}. Return [] if none are relevant.
38
+
39
+ Available skills:
40
+ ${skillCatalog}
41
+
42
+ Respond with only the JSON array, no explanation. Example: ["skill_name_1", "skill_name_2"]`;
43
+ try {
44
+ const stream = chat({
45
+ adapter,
46
+ messages: [
47
+ {
48
+ role: "user",
49
+ content: selectionPrompt
50
+ }
51
+ ]
52
+ });
53
+ let responseText = "";
54
+ for await (const chunk of stream) {
55
+ if (chunk.type === "TEXT_MESSAGE_CONTENT") {
56
+ responseText += chunk.delta;
57
+ }
58
+ }
59
+ let jsonText = responseText.trim();
60
+ if (jsonText.startsWith("```")) {
61
+ jsonText = jsonText.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "");
62
+ }
63
+ const selectedNames = JSON.parse(jsonText);
64
+ if (!Array.isArray(selectedNames)) {
65
+ return [];
66
+ }
67
+ const selectedSkills = await Promise.all(
68
+ selectedNames.slice(0, maxSkills).map((name) => storage.get(name))
69
+ );
70
+ return selectedSkills.filter((s) => s !== null);
71
+ } catch (error) {
72
+ console.warn("Skill selection failed, returning empty selection:", error);
73
+ return [];
74
+ }
75
+ }
76
+ export {
77
+ selectRelevantSkills
78
+ };
79
+ //# sourceMappingURL=select-relevant-skills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select-relevant-skills.js","sources":["../../src/select-relevant-skills.ts"],"sourcesContent":["import { chat } from '@tanstack/ai'\nimport type { AnyTextAdapter, ModelMessage, StreamChunk } from '@tanstack/ai'\nimport type { Skill, SkillIndexEntry, SkillStorage } from './types'\n\ninterface SelectRelevantSkillsOptions {\n /**\n * Text adapter for skill selection (should be a cheap/fast model)\n */\n adapter: AnyTextAdapter\n\n /**\n * Current conversation messages\n */\n messages: Array<ModelMessage>\n\n /**\n * Skill index (lightweight metadata)\n */\n skillIndex: Array<SkillIndexEntry>\n\n /**\n * Maximum number of skills to select\n */\n maxSkills: number\n\n /**\n * Storage to load full skill data\n */\n storage: SkillStorage\n}\n\n/**\n * Use a cheap/fast LLM to select which skills are relevant for the current conversation\n */\nexport async function selectRelevantSkills({\n adapter,\n messages,\n skillIndex,\n maxSkills,\n storage,\n}: SelectRelevantSkillsOptions): Promise<Array<Skill>> {\n // Early exit conditions\n if (skillIndex.length === 0) return []\n if (messages.length === 0) return []\n\n // Build context from recent messages (last 5)\n const recentMessages = messages.slice(-5)\n const recentContext = recentMessages\n .map((m) => {\n let content: string\n if (typeof m.content === 'string') {\n content = m.content\n } else if (Array.isArray(m.content)) {\n // Handle content parts (text, images, etc.)\n content = m.content\n .map((part: unknown) => {\n if (typeof part === 'string') return part\n if (part && typeof part === 'object' && 'text' in part)\n return (part as { text: string }).text\n return '[non-text content]'\n })\n .join(' ')\n } else {\n content = '[complex content]'\n }\n return `${m.role}: ${content}`\n })\n .join('\\n')\n\n // Build skill catalog for selection prompt\n const skillCatalog = skillIndex\n .map((s) => {\n const hints = s.usageHints.length > 0 ? ` (${s.usageHints[0]})` : ''\n return `- ${s.name}: ${s.description}${hints}`\n })\n .join('\\n')\n\n // Ask cheap model to select relevant skills\n const selectionPrompt = `Given this conversation context:\n---\n${recentContext}\n---\n\nWhich of these skills (if any) would be useful for the next response? Return a JSON array of skill names, max ${maxSkills}. Return [] if none are relevant.\n\nAvailable skills:\n${skillCatalog}\n\nRespond with only the JSON array, no explanation. Example: [\"skill_name_1\", \"skill_name_2\"]`\n\n try {\n // Use chat to get the selection\n const stream = chat({\n adapter,\n messages: [\n {\n role: 'user',\n content: selectionPrompt,\n },\n ],\n })\n\n // Collect the full response\n let responseText = ''\n for await (const chunk of stream as AsyncIterable<StreamChunk>) {\n if (chunk.type === 'TEXT_MESSAGE_CONTENT') {\n responseText += chunk.delta\n }\n }\n\n // Parse the JSON response\n // Handle potential markdown code blocks\n let jsonText = responseText.trim()\n if (jsonText.startsWith('```')) {\n // Remove markdown code block\n jsonText = jsonText.replace(/^```(?:json)?\\n?/, '').replace(/\\n?```$/, '')\n }\n\n const selectedNames: Array<string> = JSON.parse(jsonText)\n\n if (!Array.isArray(selectedNames)) {\n return []\n }\n\n // Load full skill data for selected skills\n const selectedSkills = await Promise.all(\n selectedNames.slice(0, maxSkills).map((name) => storage.get(name)),\n )\n\n return selectedSkills.filter((s): s is Skill => s !== null)\n } catch (error) {\n // If parsing fails or any error occurs, return empty (safe fallback)\n console.warn('Skill selection failed, returning empty selection:', error)\n return []\n }\n}\n"],"names":[],"mappings":";AAkCA,eAAsB,qBAAqB;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuD;AAErD,MAAI,WAAW,WAAW,EAAG,QAAO,CAAA;AACpC,MAAI,SAAS,WAAW,EAAG,QAAO,CAAA;AAGlC,QAAM,iBAAiB,SAAS,MAAM,EAAE;AACxC,QAAM,gBAAgB,eACnB,IAAI,CAAC,MAAM;AACV,QAAI;AACJ,QAAI,OAAO,EAAE,YAAY,UAAU;AACjC,gBAAU,EAAE;AAAA,IACd,WAAW,MAAM,QAAQ,EAAE,OAAO,GAAG;AAEnC,gBAAU,EAAE,QACT,IAAI,CAAC,SAAkB;AACtB,YAAI,OAAO,SAAS,SAAU,QAAO;AACrC,YAAI,QAAQ,OAAO,SAAS,YAAY,UAAU;AAChD,iBAAQ,KAA0B;AACpC,eAAO;AAAA,MACT,CAAC,EACA,KAAK,GAAG;AAAA,IACb,OAAO;AACL,gBAAU;AAAA,IACZ;AACA,WAAO,GAAG,EAAE,IAAI,KAAK,OAAO;AAAA,EAC9B,CAAC,EACA,KAAK,IAAI;AAGZ,QAAM,eAAe,WAClB,IAAI,CAAC,MAAM;AACV,UAAM,QAAQ,EAAE,WAAW,SAAS,IAAI,KAAK,EAAE,WAAW,CAAC,CAAC,MAAM;AAClE,WAAO,KAAK,EAAE,IAAI,KAAK,EAAE,WAAW,GAAG,KAAK;AAAA,EAC9C,CAAC,EACA,KAAK,IAAI;AAGZ,QAAM,kBAAkB;AAAA;AAAA,EAExB,aAAa;AAAA;AAAA;AAAA,gHAGiG,SAAS;AAAA;AAAA;AAAA,EAGvH,YAAY;AAAA;AAAA;AAIZ,MAAI;AAEF,UAAM,SAAS,KAAK;AAAA,MAClB;AAAA,MACA,UAAU;AAAA,QACR;AAAA,UACE,MAAM;AAAA,UACN,SAAS;AAAA,QAAA;AAAA,MACX;AAAA,IACF,CACD;AAGD,QAAI,eAAe;AACnB,qBAAiB,SAAS,QAAsC;AAC9D,UAAI,MAAM,SAAS,wBAAwB;AACzC,wBAAgB,MAAM;AAAA,MACxB;AAAA,IACF;AAIA,QAAI,WAAW,aAAa,KAAA;AAC5B,QAAI,SAAS,WAAW,KAAK,GAAG;AAE9B,iBAAW,SAAS,QAAQ,oBAAoB,EAAE,EAAE,QAAQ,WAAW,EAAE;AAAA,IAC3E;AAEA,UAAM,gBAA+B,KAAK,MAAM,QAAQ;AAExD,QAAI,CAAC,MAAM,QAAQ,aAAa,GAAG;AACjC,aAAO,CAAA;AAAA,IACT;AAGA,UAAM,iBAAiB,MAAM,QAAQ;AAAA,MACnC,cAAc,MAAM,GAAG,SAAS,EAAE,IAAI,CAAC,SAAS,QAAQ,IAAI,IAAI,CAAC;AAAA,IAAA;AAGnE,WAAO,eAAe,OAAO,CAAC,MAAkB,MAAM,IAAI;AAAA,EAC5D,SAAS,OAAO;AAEd,YAAQ,KAAK,sDAAsD,KAAK;AACxE,WAAO,CAAA;AAAA,EACT;AACF;"}
@@ -0,0 +1,34 @@
1
+ import { ToolExecutionContext } from '@tanstack/ai';
2
+ import { ToolBinding } from '@tanstack/ai-code-mode';
3
+ import { Skill, SkillStorage } from './types.js';
4
+ interface SkillsToBindingsOptions {
5
+ /**
6
+ * Skills to convert to bindings
7
+ */
8
+ skills: Array<Skill>;
9
+ /**
10
+ * Tool execution context for emitting custom events
11
+ */
12
+ context?: ToolExecutionContext;
13
+ /**
14
+ * Function to execute skill code in the sandbox
15
+ * The skill code receives `input` as a variable
16
+ */
17
+ executeInSandbox: (code: string, input: unknown) => Promise<unknown>;
18
+ /**
19
+ * Storage for updating execution stats
20
+ */
21
+ storage: SkillStorage;
22
+ }
23
+ /**
24
+ * Convert skills to sandbox bindings with the skill_ prefix.
25
+ * Skills become callable functions inside the sandbox.
26
+ */
27
+ export declare function skillsToBindings({ skills, context, executeInSandbox, storage, }: SkillsToBindingsOptions): Record<string, ToolBinding>;
28
+ /**
29
+ * Create a simple binding record for skills without full sandbox execution.
30
+ * This is used when skills are being documented in the system prompt
31
+ * but not yet being executed.
32
+ */
33
+ export declare function skillsToSimpleBindings(skills: Array<Skill>): Record<string, ToolBinding>;
34
+ export {};