@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,204 @@
1
+ import {
2
+ createCodeModeSystemPrompt,
3
+ createCodeModeTool,
4
+ toolsToBindings,
5
+ } from '@tanstack/ai-code-mode'
6
+ import { createToolRegistry } from '@tanstack/ai'
7
+ import { selectRelevantSkills } from './select-relevant-skills'
8
+ import { createSkillManagementTools } from './create-skill-management-tools'
9
+ import { createSkillsSystemPrompt } from './create-skills-system-prompt'
10
+ import { skillsToTools } from './skills-to-tools'
11
+ import type {
12
+ CodeModeWithSkillsOptions,
13
+ CodeModeWithSkillsResult,
14
+ Skill,
15
+ } from './types'
16
+
17
+ export type { CodeModeWithSkillsOptions, CodeModeWithSkillsResult }
18
+
19
+ /**
20
+ * Create Code Mode tools and system prompt with skills integration.
21
+ *
22
+ * This function:
23
+ * 1. Loads the skill index from storage
24
+ * 2. Uses a cheap/fast LLM to select relevant skills based on conversation context
25
+ * 3. Creates the execute_typescript tool with dynamic skill bindings
26
+ * 4. Creates skill management tools (search, get, register)
27
+ * 5. Generates system prompts documenting available skills
28
+ * 6. Returns a ToolRegistry that allows dynamic skill additions mid-stream
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const { toolsRegistry, systemPrompt, selectedSkills } = await codeModeWithSkills({
33
+ * config: {
34
+ * driver: createNodeIsolateDriver(),
35
+ * tools: allTools,
36
+ * timeout: 60000,
37
+ * },
38
+ * adapter: openaiText('gpt-4o-mini'), // Cheap model for selection
39
+ * skills: {
40
+ * storage: createFileSkillStorage('./.skills'),
41
+ * maxSkillsInContext: 5,
42
+ * },
43
+ * messages,
44
+ * });
45
+ *
46
+ * const stream = chat({
47
+ * adapter: openaiText('gpt-4o'), // Main model
48
+ * toolRegistry: toolsRegistry, // Dynamic tool registry
49
+ * messages,
50
+ * systemPrompts: [BASE_PROMPT, systemPrompt],
51
+ * });
52
+ * ```
53
+ */
54
+ export async function codeModeWithSkills({
55
+ config,
56
+ adapter,
57
+ skills,
58
+ messages,
59
+ skillsAsTools = true,
60
+ }: CodeModeWithSkillsOptions): Promise<CodeModeWithSkillsResult> {
61
+ const { storage, maxSkillsInContext = 5 } = skills
62
+
63
+ // 1. Load the skill index (lightweight metadata only)
64
+ const skillIndex = await storage.loadIndex()
65
+
66
+ // 2. Use adapter to select relevant skills based on transcript
67
+ const selectedSkills = await selectRelevantSkills({
68
+ adapter,
69
+ messages,
70
+ skillIndex,
71
+ maxSkills: maxSkillsInContext,
72
+ storage,
73
+ })
74
+
75
+ // Pre-compute bindings from base tools (shared across skill executions)
76
+ const baseBindings = toolsToBindings(config.tools, 'external_')
77
+
78
+ // 3. Create the execute_typescript tool with dynamic skill bindings
79
+ const codeModeTool = createCodeModeTool({
80
+ ...config,
81
+ // Dynamic skill bindings - fetched at execution time
82
+ getSkillBindings: async () => {
83
+ // Get all skills from storage (includes newly registered ones)
84
+ const allSkills = await storage.loadAll()
85
+ // Convert to bindings with skill_ prefix
86
+ const skillBindings: Record<string, any> = {}
87
+ for (const skill of allSkills) {
88
+ // Create a simple binding that executes the skill code
89
+ skillBindings[`skill_${skill.name}`] = {
90
+ name: `skill_${skill.name}`,
91
+ description: skill.description,
92
+ inputSchema: skill.inputSchema,
93
+ outputSchema: skill.outputSchema,
94
+ execute: async (input: unknown) => {
95
+ // This is a simplified execution - the full skillToTool handles events
96
+ const wrappedCode = `const input = ${JSON.stringify(input)};\n${skill.code}`
97
+ const { stripTypeScript, createEventAwareBindings } =
98
+ await import('@tanstack/ai-code-mode')
99
+ const strippedCode = await stripTypeScript(wrappedCode)
100
+ const context = await config.driver.createContext({
101
+ bindings: createEventAwareBindings(baseBindings, () => {}),
102
+ timeout: config.timeout,
103
+ memoryLimit: config.memoryLimit,
104
+ })
105
+ try {
106
+ const result = await context.execute(strippedCode)
107
+ if (!result.success) {
108
+ throw new Error(
109
+ result.error?.message || 'Skill execution failed',
110
+ )
111
+ }
112
+ return result.value
113
+ } finally {
114
+ await context.dispose()
115
+ }
116
+ },
117
+ }
118
+ }
119
+ return skillBindings
120
+ },
121
+ })
122
+
123
+ // 4. Create a mutable tool registry
124
+ const registry = createToolRegistry()
125
+
126
+ // 5. Add the execute_typescript tool to the registry
127
+ registry.add(codeModeTool)
128
+
129
+ // 6. Create skill management tools (they need access to the registry)
130
+ const skillManagementTools = createSkillManagementTools({
131
+ storage,
132
+ registry,
133
+ config,
134
+ baseBindings,
135
+ })
136
+
137
+ for (const tool of skillManagementTools) {
138
+ registry.add(tool)
139
+ }
140
+
141
+ // 7. Convert selected skills to direct tools and add to registry (if enabled)
142
+ if (skillsAsTools && selectedSkills.length > 0) {
143
+ const skillToolsList = skillsToTools({
144
+ skills: selectedSkills,
145
+ driver: config.driver,
146
+ tools: config.tools,
147
+ storage,
148
+ timeout: config.timeout,
149
+ memoryLimit: config.memoryLimit,
150
+ })
151
+
152
+ for (const skillTool of skillToolsList) {
153
+ registry.add(skillTool)
154
+ }
155
+ }
156
+
157
+ // 8. Generate combined system prompt
158
+ const basePrompt = createCodeModeSystemPrompt(config)
159
+ const skillsPrompt = createSkillsSystemPrompt({
160
+ selectedSkills,
161
+ totalSkillCount: skillIndex.length,
162
+ skillsAsTools,
163
+ })
164
+ const systemPrompt = basePrompt + '\n\n' + skillsPrompt
165
+
166
+ return {
167
+ toolsRegistry: registry,
168
+ systemPrompt,
169
+ selectedSkills,
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Create a Code Mode tool configuration extended with skills.
175
+ * This is an alternative to codeModeWithSkills that returns
176
+ * a config object instead of directly creating tools.
177
+ *
178
+ * Useful when you want more control over the tool creation process.
179
+ */
180
+ export function createCodeModeWithSkillsConfig({
181
+ config,
182
+ selectedSkills,
183
+ storage,
184
+ }: {
185
+ config: CodeModeWithSkillsOptions['config']
186
+ selectedSkills: Array<Skill>
187
+ storage: CodeModeWithSkillsOptions['skills']['storage']
188
+ }) {
189
+ // Create skill tools for direct calling
190
+ const skillToolsList = skillsToTools({
191
+ skills: selectedSkills,
192
+ driver: config.driver,
193
+ tools: config.tools,
194
+ storage,
195
+ timeout: config.timeout,
196
+ memoryLimit: config.memoryLimit,
197
+ })
198
+
199
+ return {
200
+ ...config,
201
+ skillTools: skillToolsList,
202
+ selectedSkills,
203
+ }
204
+ }
@@ -0,0 +1,296 @@
1
+ import { toolDefinition } from '@tanstack/ai'
2
+ import { toolsToBindings } from '@tanstack/ai-code-mode'
3
+ import { z } from 'zod'
4
+ import { createDefaultTrustStrategy } from './trust-strategies'
5
+ import { skillToTool } from './skills-to-tools'
6
+ import type { ServerTool, ToolRegistry } from '@tanstack/ai'
7
+ import type { CodeModeToolConfig, ToolBinding } from '@tanstack/ai-code-mode'
8
+ import type { SkillStorage } from './types'
9
+ import type { TrustStrategy } from './trust-strategies'
10
+
11
+ interface CreateSkillManagementToolsOptions {
12
+ /**
13
+ * Storage implementation for skills
14
+ */
15
+ storage: SkillStorage
16
+
17
+ /**
18
+ * Trust strategy for determining initial trust level.
19
+ * If not provided, uses the storage's trustStrategy or falls back to default.
20
+ */
21
+ trustStrategy?: TrustStrategy
22
+
23
+ /**
24
+ * Tool registry for adding newly registered skills immediately.
25
+ * When provided, register_skill will add the new skill to this registry
26
+ * so it's available as a direct tool in the current chat session.
27
+ */
28
+ registry?: ToolRegistry
29
+
30
+ /**
31
+ * Code mode config for creating skill tools.
32
+ * Required when registry is provided.
33
+ */
34
+ config?: CodeModeToolConfig
35
+
36
+ /**
37
+ * Pre-computed bindings for external_* functions.
38
+ * Required when registry is provided.
39
+ */
40
+ baseBindings?: Record<string, ToolBinding>
41
+ }
42
+
43
+ /**
44
+ * Create tools for searching, retrieving, and registering skills.
45
+ * These tools allow the LLM to interact with the skill library at runtime.
46
+ *
47
+ * When registry, config, and baseBindings are provided, newly registered skills
48
+ * will be immediately added to the registry and available as direct tools.
49
+ */
50
+ export function createSkillManagementTools({
51
+ storage,
52
+ trustStrategy,
53
+ registry,
54
+ config,
55
+ baseBindings,
56
+ }: CreateSkillManagementToolsOptions): Array<ServerTool<any, any, any>> {
57
+ // Use provided strategy, or storage's strategy, or default
58
+ const strategy =
59
+ trustStrategy ?? storage.trustStrategy ?? createDefaultTrustStrategy()
60
+
61
+ // Compute bindings if not provided but config is available
62
+ const bindings =
63
+ baseBindings ?? (config ? toolsToBindings(config.tools, 'external_') : {})
64
+ return [
65
+ // Search for skills
66
+ toolDefinition({
67
+ name: 'search_skills',
68
+ description:
69
+ 'Search the skill library for reusable skills. Use this to find skills that can help accomplish a task. Returns matching skills with their descriptions.',
70
+ inputSchema: z.object({
71
+ query: z
72
+ .string()
73
+ .describe('Search query describing what you want to accomplish'),
74
+ limit: z
75
+ .number()
76
+ .optional()
77
+ .default(5)
78
+ .describe('Maximum number of results (default: 5)'),
79
+ }),
80
+ outputSchema: z.array(
81
+ z.object({
82
+ name: z.string(),
83
+ description: z.string(),
84
+ usageHints: z.array(z.string()),
85
+ trustLevel: z.enum(['untrusted', 'provisional', 'trusted']),
86
+ }),
87
+ ),
88
+ }).server(async ({ query, limit }) => {
89
+ const results = await storage.search(query, { limit: limit ?? 5 })
90
+ return results.map((s) => ({
91
+ name: s.name,
92
+ description: s.description,
93
+ usageHints: s.usageHints,
94
+ trustLevel: s.trustLevel,
95
+ }))
96
+ }),
97
+
98
+ // Get full skill details
99
+ toolDefinition({
100
+ name: 'get_skill',
101
+ description:
102
+ 'Get the full implementation details of a skill, including its code. Use this after search_skills to see how a skill works before using it.',
103
+ inputSchema: z.object({
104
+ name: z.string().describe('The skill name (without skill_ prefix)'),
105
+ }),
106
+ outputSchema: z.object({
107
+ name: z.string().optional(),
108
+ description: z.string().optional(),
109
+ code: z.string().optional(),
110
+ inputSchema: z.string().optional().describe('JSON Schema as string'),
111
+ outputSchema: z.string().optional().describe('JSON Schema as string'),
112
+ usageHints: z.array(z.string()).optional(),
113
+ dependsOn: z.array(z.string()).optional(),
114
+ trustLevel: z.enum(['untrusted', 'provisional', 'trusted']).optional(),
115
+ stats: z
116
+ .object({
117
+ executions: z.number(),
118
+ successRate: z.number(),
119
+ })
120
+ .optional(),
121
+ error: z.string().optional(),
122
+ }),
123
+ }).server(async ({ name }) => {
124
+ const skill = await storage.get(name)
125
+ if (!skill) {
126
+ return { error: `Skill '${name}' not found` }
127
+ }
128
+ return {
129
+ name: skill.name,
130
+ description: skill.description,
131
+ code: skill.code,
132
+ inputSchema: JSON.stringify(skill.inputSchema),
133
+ outputSchema: JSON.stringify(skill.outputSchema),
134
+ usageHints: skill.usageHints,
135
+ dependsOn: skill.dependsOn,
136
+ trustLevel: skill.trustLevel,
137
+ stats: skill.stats,
138
+ }
139
+ }),
140
+
141
+ // Register a new skill
142
+ toolDefinition({
143
+ name: 'register_skill',
144
+ description:
145
+ 'Save working TypeScript code as a reusable skill for future use. Only register code that has been tested and works correctly. The skill becomes available as a callable tool immediately.',
146
+ inputSchema: z.object({
147
+ name: z
148
+ .string()
149
+ .regex(
150
+ /^[a-z][a-z0-9_]*$/,
151
+ 'Must be snake_case starting with a letter',
152
+ )
153
+ .describe(
154
+ 'Unique skill name in snake_case (e.g., fetch_github_stats)',
155
+ ),
156
+ description: z
157
+ .string()
158
+ .describe('Clear description of what the skill does'),
159
+ code: z
160
+ .string()
161
+ .describe(
162
+ 'The TypeScript code. Receives `input` variable, can call external_* and skill_* functions, should return a value.',
163
+ ),
164
+ inputSchema: z
165
+ .string()
166
+ .describe(
167
+ 'JSON Schema as a JSON string describing the input parameter, e.g. {"type":"object","properties":{"a":{"type":"number"}},"required":["a"]}',
168
+ ),
169
+ outputSchema: z
170
+ .string()
171
+ .describe(
172
+ 'JSON Schema as a JSON string describing the return value, e.g. {"type":"object","properties":{"result":{"type":"number"}}}',
173
+ ),
174
+ usageHints: z
175
+ .array(z.string())
176
+ .describe(
177
+ 'Hints about when to use this skill, e.g. "Use when user asks about..."',
178
+ ),
179
+ dependsOn: z
180
+ .array(z.string())
181
+ .optional()
182
+ .default([])
183
+ .describe('Names of other skills this skill calls'),
184
+ }),
185
+ outputSchema: z.object({
186
+ success: z.boolean().optional(),
187
+ skillId: z.string().optional(),
188
+ name: z.string().optional(),
189
+ message: z.string().optional(),
190
+ error: z.string().optional(),
191
+ }),
192
+ }).server(async (rawSkillDef, context) => {
193
+ // Parse the JSON string schemas
194
+ let inputSchema: Record<string, unknown>
195
+ let outputSchema: Record<string, unknown>
196
+ try {
197
+ inputSchema = JSON.parse(rawSkillDef.inputSchema) as Record<
198
+ string,
199
+ unknown
200
+ >
201
+ } catch {
202
+ return { error: 'inputSchema must be a valid JSON string' }
203
+ }
204
+ try {
205
+ outputSchema = JSON.parse(rawSkillDef.outputSchema) as Record<
206
+ string,
207
+ unknown
208
+ >
209
+ } catch {
210
+ return { error: 'outputSchema must be a valid JSON string' }
211
+ }
212
+
213
+ const skillDef = {
214
+ ...rawSkillDef,
215
+ inputSchema,
216
+ outputSchema,
217
+ }
218
+ try {
219
+ // Validate the skill name isn't reserved
220
+ if (skillDef.name.startsWith('external_')) {
221
+ return { error: "Skill names cannot start with 'external_'" }
222
+ }
223
+ if (skillDef.name.startsWith('skill_')) {
224
+ return {
225
+ error:
226
+ "Skill names should not include the 'skill_' prefix - it will be added automatically",
227
+ }
228
+ }
229
+
230
+ // Check if skill already exists
231
+ const existing = await storage.get(skillDef.name)
232
+ if (existing) {
233
+ return {
234
+ error: `Skill '${skillDef.name}' already exists. Use a different name or update the existing skill.`,
235
+ }
236
+ }
237
+
238
+ // Generate a unique ID
239
+ const id = crypto.randomUUID()
240
+
241
+ // Get initial trust level from strategy
242
+ const initialTrustLevel = strategy.getInitialTrustLevel()
243
+
244
+ // Save the skill
245
+ const skill = await storage.save({
246
+ id,
247
+ name: skillDef.name,
248
+ description: skillDef.description,
249
+ code: skillDef.code,
250
+ inputSchema: skillDef.inputSchema,
251
+ outputSchema: skillDef.outputSchema,
252
+ usageHints: skillDef.usageHints,
253
+ dependsOn: skillDef.dependsOn ?? [],
254
+ trustLevel: initialTrustLevel,
255
+ stats: { executions: 0, successRate: 0 },
256
+ })
257
+
258
+ // If registry and config are available, add the skill as a tool immediately
259
+ if (registry && config) {
260
+ const skillTool = skillToTool({
261
+ skill,
262
+ driver: config.driver,
263
+ bindings,
264
+ storage,
265
+ timeout: config.timeout,
266
+ memoryLimit: config.memoryLimit,
267
+ })
268
+ registry.add(skillTool)
269
+ console.log(
270
+ `[register_skill] Added skill '${skill.name}' to registry immediately`,
271
+ )
272
+ }
273
+
274
+ // Emit event for UI notification
275
+ context?.emitCustomEvent('skill:registered', {
276
+ id: skill.id,
277
+ name: skill.name,
278
+ description: skill.description,
279
+ timestamp: Date.now(),
280
+ })
281
+
282
+ return {
283
+ success: true,
284
+ skillId: skill.id,
285
+ name: skill.name,
286
+ message: `Skill '${skill.name}' registered successfully and is now available as the '${skill.name}' tool.`,
287
+ }
288
+ } catch (error) {
289
+ console.error('[register_skill] Error:', error)
290
+ return {
291
+ error: `Failed to register skill: ${error instanceof Error ? error.message : String(error)}`,
292
+ }
293
+ }
294
+ }),
295
+ ]
296
+ }