@tanstack/ai-code-mode 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 (40) hide show
  1. package/README.md +196 -0
  2. package/dist/esm/agent-store.d.ts +21 -0
  3. package/dist/esm/agent-store.js +31 -0
  4. package/dist/esm/agent-store.js.map +1 -0
  5. package/dist/esm/bindings/tool-to-binding.d.ts +24 -0
  6. package/dist/esm/bindings/tool-to-binding.js +78 -0
  7. package/dist/esm/bindings/tool-to-binding.js.map +1 -0
  8. package/dist/esm/code-wrapper.d.ts +9 -0
  9. package/dist/esm/code-wrapper.js +18 -0
  10. package/dist/esm/code-wrapper.js.map +1 -0
  11. package/dist/esm/create-code-mode-tool.d.ts +50 -0
  12. package/dist/esm/create-code-mode-tool.js +142 -0
  13. package/dist/esm/create-code-mode-tool.js.map +1 -0
  14. package/dist/esm/create-code-mode.d.ts +41 -0
  15. package/dist/esm/create-code-mode.js +12 -0
  16. package/dist/esm/create-code-mode.js.map +1 -0
  17. package/dist/esm/create-system-prompt.d.ts +24 -0
  18. package/dist/esm/create-system-prompt.js +66 -0
  19. package/dist/esm/create-system-prompt.js.map +1 -0
  20. package/dist/esm/index.d.ts +10 -0
  21. package/dist/esm/index.js +23 -0
  22. package/dist/esm/index.js.map +1 -0
  23. package/dist/esm/strip-typescript.d.ts +23 -0
  24. package/dist/esm/strip-typescript.js +52 -0
  25. package/dist/esm/strip-typescript.js.map +1 -0
  26. package/dist/esm/type-generator/json-schema-to-ts.d.ts +31 -0
  27. package/dist/esm/type-generator/json-schema-to-ts.js +100 -0
  28. package/dist/esm/type-generator/json-schema-to-ts.js.map +1 -0
  29. package/dist/esm/types.d.ts +176 -0
  30. package/package.json +62 -0
  31. package/src/agent-store.ts +43 -0
  32. package/src/bindings/tool-to-binding.ts +132 -0
  33. package/src/code-wrapper.ts +24 -0
  34. package/src/create-code-mode-tool.ts +254 -0
  35. package/src/create-code-mode.ts +35 -0
  36. package/src/create-system-prompt.ts +95 -0
  37. package/src/index.ts +55 -0
  38. package/src/strip-typescript.ts +94 -0
  39. package/src/type-generator/json-schema-to-ts.ts +188 -0
  40. package/src/types.ts +225 -0
@@ -0,0 +1,94 @@
1
+ import { transform } from 'esbuild'
2
+
3
+ // Unique markers for wrapping/unwrapping code
4
+ const WRAPPER_START = '___TANSTACK_WRAPPER_START___'
5
+ const WRAPPER_END = '___TANSTACK_WRAPPER_END___'
6
+
7
+ /**
8
+ * Strip TypeScript syntax from code, converting it to plain JavaScript.
9
+ *
10
+ * This is a safety net to ensure that even if an LLM generates TypeScript
11
+ * code with type annotations, it will be converted to valid JavaScript
12
+ * before being sent to the sandbox for execution.
13
+ *
14
+ * Uses esbuild's transform API which is extremely fast and handles all
15
+ * TypeScript syntax including:
16
+ * - Type annotations (: string, : number, etc.)
17
+ * - Generic types (Array<T>, Record<K, V>, etc.)
18
+ * - Interface and type declarations
19
+ * - Type assertions
20
+ * - Enums (converted to JavaScript objects)
21
+ *
22
+ * The code is wrapped in an async function before transformation to allow
23
+ * top-level `return` and `await` statements, then unwrapped after.
24
+ *
25
+ * @param code - TypeScript or JavaScript code
26
+ * @returns Plain JavaScript code with all type syntax removed
27
+ * @throws Error if esbuild fails (e.g., syntax error) or wrapper extraction fails
28
+ */
29
+ export async function stripTypeScript(code: string): Promise<string> {
30
+ // Wrap the code in an async function to allow top-level return/await
31
+ // This is necessary because esbuild's ESM format doesn't allow top-level returns
32
+ const wrappedCode = `async function ${WRAPPER_START}() {\n${code}\n}; ${WRAPPER_END}`
33
+
34
+ const result = await transform(wrappedCode, {
35
+ loader: 'ts',
36
+ // Don't minify - keep the code readable for debugging
37
+ minify: false,
38
+ // Don't use keepNames as it adds __name() helper calls that aren't available in the sandbox
39
+ keepNames: false,
40
+ // Target modern JavaScript (ES2022 has top-level await)
41
+ target: 'es2022',
42
+ })
43
+
44
+ // Extract the code from inside the wrapper function
45
+ const transformed = result.code
46
+
47
+ // Find the function declaration start
48
+ const functionStart = transformed.indexOf(`async function ${WRAPPER_START}()`)
49
+ if (functionStart === -1) {
50
+ throw new Error(
51
+ '[stripTypeScript] Could not find wrapper function start in transformed output',
52
+ )
53
+ }
54
+
55
+ // Find the opening brace of the function
56
+ const openBrace = transformed.indexOf('{', functionStart)
57
+ if (openBrace === -1) {
58
+ throw new Error(
59
+ '[stripTypeScript] Could not find opening brace in transformed output',
60
+ )
61
+ }
62
+
63
+ // Find the end marker (regardless of formatting)
64
+ const endMarkerIndex = transformed.indexOf(WRAPPER_END)
65
+ if (endMarkerIndex === -1) {
66
+ throw new Error(
67
+ '[stripTypeScript] Could not find end marker in transformed output',
68
+ )
69
+ }
70
+
71
+ // Find the closing brace of the function (last } before the end marker)
72
+ // We need to find the } that matches the function opening
73
+ const codeBeforeEndMarker = transformed.substring(
74
+ openBrace + 1,
75
+ endMarkerIndex,
76
+ )
77
+
78
+ // Find the last } before the end marker, accounting for the semicolon
79
+ // The code will be: ...function body...}; WRAPPER_END or ...};\nWRAPPER_END
80
+ const closingBraceIndex = codeBeforeEndMarker.lastIndexOf('}')
81
+
82
+ if (closingBraceIndex === -1) {
83
+ throw new Error(
84
+ '[stripTypeScript] Could not find closing brace in transformed output',
85
+ )
86
+ }
87
+
88
+ // Extract the function body (between { and })
89
+ const functionBody = codeBeforeEndMarker
90
+ .substring(0, closingBraceIndex)
91
+ .trim()
92
+
93
+ return functionBody
94
+ }
@@ -0,0 +1,188 @@
1
+ import type { ToolBinding } from '../types'
2
+
3
+ /**
4
+ * Options for type stub generation
5
+ */
6
+ export interface TypeGeneratorOptions {
7
+ /**
8
+ * Include JSDoc comments with descriptions
9
+ * @default true
10
+ */
11
+ includeDescriptions?: boolean
12
+ }
13
+
14
+ /**
15
+ * Generate TypeScript type stubs for all tool bindings
16
+ *
17
+ * These stubs are included in the LLM system prompt so it knows
18
+ * the exact type signatures of available tools.
19
+ *
20
+ * Tool names match the actual function names injected into the sandbox.
21
+ */
22
+ export function generateTypeStubs(
23
+ bindings: Record<string, ToolBinding>,
24
+ options: TypeGeneratorOptions = {},
25
+ ): string {
26
+ const { includeDescriptions = true } = options
27
+
28
+ const declarations: Array<string> = []
29
+
30
+ for (const [name, binding] of Object.entries(bindings)) {
31
+ const inputTypeName = `${capitalize(name)}Input`
32
+ const outputTypeName = `${capitalize(name)}Output`
33
+
34
+ // Generate input type
35
+ const inputType = jsonSchemaToTypeScript(binding.inputSchema, inputTypeName)
36
+ if (inputType.declaration) {
37
+ declarations.push(inputType.declaration)
38
+ }
39
+
40
+ // Generate output type if present
41
+ let outputTypeRef = 'unknown'
42
+ if (binding.outputSchema) {
43
+ const outputType = jsonSchemaToTypeScript(
44
+ binding.outputSchema,
45
+ outputTypeName,
46
+ )
47
+ if (outputType.declaration) {
48
+ declarations.push(outputType.declaration)
49
+ }
50
+ outputTypeRef = outputType.name
51
+ }
52
+
53
+ // Generate function declaration matching the actual sandbox function name
54
+ const description =
55
+ includeDescriptions && binding.description
56
+ ? `/** ${binding.description} */\n`
57
+ : ''
58
+
59
+ declarations.push(
60
+ `${description}declare function ${name}(input: ${inputType.name}): Promise<${outputTypeRef}>;`,
61
+ )
62
+ }
63
+
64
+ return declarations.join('\n\n')
65
+ }
66
+
67
+ interface TypeResult {
68
+ name: string
69
+ declaration: string
70
+ }
71
+
72
+ /**
73
+ * Convert a JSON Schema to a TypeScript type
74
+ *
75
+ * Supports basic types: string, number, boolean, object, array
76
+ */
77
+ export function jsonSchemaToTypeScript(
78
+ schema: Record<string, unknown>,
79
+ typeName: string,
80
+ ): TypeResult {
81
+ const type = schemaToType(schema)
82
+
83
+ // For object schemas with properties, create a named interface
84
+ if (
85
+ schema.type === 'object' &&
86
+ schema.properties &&
87
+ Object.keys(schema.properties as object).length > 0
88
+ ) {
89
+ return {
90
+ name: typeName,
91
+ declaration: `interface ${typeName} ${type}`,
92
+ }
93
+ }
94
+
95
+ // For simple types or empty objects, create a type alias
96
+ return {
97
+ name: type,
98
+ declaration: '',
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Convert a JSON Schema to a TypeScript type string
104
+ */
105
+ function schemaToType(schema: Record<string, unknown>): string {
106
+ if (typeof schema !== 'object') {
107
+ return 'unknown'
108
+ }
109
+
110
+ const schemaType = schema.type
111
+
112
+ // Handle basic types
113
+ if (schemaType === 'string') return 'string'
114
+ if (schemaType === 'number' || schemaType === 'integer') return 'number'
115
+ if (schemaType === 'boolean') return 'boolean'
116
+ if (schemaType === 'null') return 'null'
117
+
118
+ // Handle arrays
119
+ if (schemaType === 'array') {
120
+ const items = schema.items as Record<string, unknown> | undefined
121
+ const itemType = items ? schemaToType(items) : 'unknown'
122
+ return `Array<${itemType}>`
123
+ }
124
+
125
+ // Handle objects with properties
126
+ if (schemaType === 'object' && schema.properties) {
127
+ const properties = schema.properties as Record<
128
+ string,
129
+ Record<string, unknown>
130
+ >
131
+ const required = new Set(
132
+ (schema.required as Array<string> | undefined) ?? [],
133
+ )
134
+
135
+ const props = Object.entries(properties)
136
+ .map(([key, propSchema]) => {
137
+ const optional = required.has(key) ? '' : '?'
138
+ const propType = schemaToType(propSchema)
139
+ // Handle property names that need quoting
140
+ const safeName = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)
141
+ ? key
142
+ : `"${key}"`
143
+ return ` ${safeName}${optional}: ${propType};`
144
+ })
145
+ .join('\n')
146
+
147
+ return `{\n${props}\n}`
148
+ }
149
+
150
+ // Handle enums
151
+ if (schema.enum) {
152
+ const enumValues = schema.enum as Array<unknown>
153
+ return enumValues.map((v) => JSON.stringify(v)).join(' | ')
154
+ }
155
+
156
+ // Handle union types (anyOf, oneOf)
157
+ if (schema.anyOf || schema.oneOf) {
158
+ const variants = (schema.anyOf || schema.oneOf) as Array<
159
+ Record<string, unknown>
160
+ >
161
+ return variants.map((v) => schemaToType(v)).join(' | ')
162
+ }
163
+
164
+ // Handle type arrays (e.g., ["string", "null"])
165
+ if (Array.isArray(schemaType)) {
166
+ return schemaType
167
+ .map((t) => {
168
+ if (t === 'string') return 'string'
169
+ if (t === 'number' || t === 'integer') return 'number'
170
+ if (t === 'boolean') return 'boolean'
171
+ if (t === 'null') return 'null'
172
+ if (t === 'array') return 'Array<unknown>'
173
+ if (t === 'object') return 'object'
174
+ return 'unknown'
175
+ })
176
+ .join(' | ')
177
+ }
178
+
179
+ // Fallback for unknown schemas
180
+ return 'unknown'
181
+ }
182
+
183
+ /**
184
+ * Capitalize the first letter of a string
185
+ */
186
+ function capitalize(str: string): string {
187
+ return str.charAt(0).toUpperCase() + str.slice(1)
188
+ }
package/src/types.ts ADDED
@@ -0,0 +1,225 @@
1
+ import type {
2
+ ServerTool,
3
+ ToolDefinition,
4
+ ToolExecutionContext,
5
+ } from '@tanstack/ai'
6
+
7
+ // ============================================================================
8
+ // Isolate Driver Interfaces
9
+ // ============================================================================
10
+
11
+ /**
12
+ * Interface for isolate/sandbox drivers
13
+ * Each runtime environment implements this to provide sandboxed code execution
14
+ */
15
+ export interface IsolateDriver {
16
+ /**
17
+ * Create a new isolated execution context with tool bindings
18
+ */
19
+ createContext: (config: IsolateConfig) => Promise<IsolateContext>
20
+ }
21
+
22
+ /**
23
+ * Configuration for creating an isolate context
24
+ */
25
+ export interface IsolateConfig {
26
+ /**
27
+ * Tools transformed into callable bindings for the sandbox
28
+ */
29
+ bindings: Record<string, ToolBinding>
30
+
31
+ /**
32
+ * Execution timeout in milliseconds (default: 30000)
33
+ */
34
+ timeout?: number
35
+
36
+ /**
37
+ * Memory limit in MB (default: 128)
38
+ */
39
+ memoryLimit?: number
40
+ }
41
+
42
+ /**
43
+ * Isolated execution context with tool bindings injected
44
+ */
45
+ export interface IsolateContext {
46
+ /**
47
+ * Execute generated code and return results
48
+ */
49
+ execute: <T = unknown>(code: string) => Promise<ExecutionResult<T>>
50
+
51
+ /**
52
+ * Clean up sandbox resources
53
+ */
54
+ dispose: () => Promise<void>
55
+ }
56
+
57
+ /**
58
+ * Result of code execution in the sandbox
59
+ */
60
+ export interface ExecutionResult<T = unknown> {
61
+ /**
62
+ * Whether execution completed without errors
63
+ */
64
+ success: boolean
65
+
66
+ /**
67
+ * Return value from the executed code (if successful)
68
+ */
69
+ value?: T
70
+
71
+ /**
72
+ * Normalized error information (if failed)
73
+ */
74
+ error?: NormalizedError
75
+
76
+ /**
77
+ * Console output captured during execution
78
+ */
79
+ logs?: Array<string>
80
+ }
81
+
82
+ /**
83
+ * Normalized error format for cross-runtime compatibility
84
+ */
85
+ export interface NormalizedError {
86
+ /**
87
+ * Error name/type
88
+ */
89
+ name: string
90
+
91
+ /**
92
+ * Error message
93
+ */
94
+ message: string
95
+
96
+ /**
97
+ * Stack trace (if available)
98
+ */
99
+ stack?: string
100
+
101
+ /**
102
+ * Error code (if available)
103
+ */
104
+ code?: string
105
+ }
106
+
107
+ // ============================================================================
108
+ // Tool Binding Interfaces
109
+ // ============================================================================
110
+
111
+ /**
112
+ * A tool transformed into a format suitable for sandbox injection
113
+ */
114
+ export interface ToolBinding {
115
+ /**
116
+ * Unique tool identifier
117
+ */
118
+ name: string
119
+
120
+ /**
121
+ * Human-readable description for the LLM
122
+ */
123
+ description: string
124
+
125
+ /**
126
+ * JSON Schema for tool input parameters
127
+ */
128
+ inputSchema: Record<string, unknown>
129
+
130
+ /**
131
+ * JSON Schema for tool output (optional)
132
+ */
133
+ outputSchema?: Record<string, unknown>
134
+
135
+ /**
136
+ * The execute function that will be injected into the sandbox.
137
+ * Accepts optional context for emitting custom events.
138
+ */
139
+ execute: (args: unknown, context?: ToolExecutionContext) => Promise<unknown>
140
+ }
141
+
142
+ // Re-export for convenience
143
+ export type { ToolExecutionContext }
144
+
145
+ // ============================================================================
146
+ // Code Mode Tool Types
147
+ // ============================================================================
148
+
149
+ /**
150
+ * Tool types that can be passed to Code Mode
151
+ */
152
+ export type CodeModeTool =
153
+ | ServerTool<any, any, any>
154
+ | ToolDefinition<any, any, any>
155
+
156
+ /**
157
+ * Configuration for createCodeModeTool
158
+ */
159
+ export interface CodeModeToolConfig {
160
+ /**
161
+ * Isolate driver for sandboxed code execution
162
+ */
163
+ driver: IsolateDriver
164
+
165
+ /**
166
+ * Tools to expose as external_* functions in the sandbox
167
+ */
168
+ tools: Array<CodeModeTool>
169
+
170
+ /**
171
+ * Execution timeout in milliseconds (default: 30000)
172
+ */
173
+ timeout?: number
174
+
175
+ /**
176
+ * Memory limit for isolate in MB (default: 128)
177
+ */
178
+ memoryLimit?: number
179
+
180
+ /**
181
+ * Optional function to get additional bindings dynamically.
182
+ * Called at execution time (each execute_typescript call) to get current skill bindings.
183
+ * These are merged with the static external_* bindings.
184
+ *
185
+ * @returns Record of skill bindings with skill_ prefix
186
+ *
187
+ * @example
188
+ * ```typescript
189
+ * getSkillBindings: async () => {
190
+ * const skills = await storage.loadAll()
191
+ * return skillsToBindings(skills, 'skill_')
192
+ * }
193
+ * ```
194
+ */
195
+ getSkillBindings?: () => Promise<Record<string, ToolBinding>>
196
+ }
197
+
198
+ /**
199
+ * Result returned by the execute_typescript tool
200
+ */
201
+ export interface CodeModeToolResult {
202
+ /**
203
+ * Whether execution completed without errors
204
+ */
205
+ success: boolean
206
+
207
+ /**
208
+ * Return value from the executed code (if successful)
209
+ */
210
+ result?: unknown
211
+
212
+ /**
213
+ * Console output captured during execution
214
+ */
215
+ logs?: Array<string>
216
+
217
+ /**
218
+ * Error details if execution failed
219
+ */
220
+ error?: {
221
+ message: string
222
+ name?: string
223
+ line?: number
224
+ }
225
+ }