@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.
- package/README.md +196 -0
- package/dist/esm/agent-store.d.ts +21 -0
- package/dist/esm/agent-store.js +31 -0
- package/dist/esm/agent-store.js.map +1 -0
- package/dist/esm/bindings/tool-to-binding.d.ts +24 -0
- package/dist/esm/bindings/tool-to-binding.js +78 -0
- package/dist/esm/bindings/tool-to-binding.js.map +1 -0
- package/dist/esm/code-wrapper.d.ts +9 -0
- package/dist/esm/code-wrapper.js +18 -0
- package/dist/esm/code-wrapper.js.map +1 -0
- package/dist/esm/create-code-mode-tool.d.ts +50 -0
- package/dist/esm/create-code-mode-tool.js +142 -0
- package/dist/esm/create-code-mode-tool.js.map +1 -0
- package/dist/esm/create-code-mode.d.ts +41 -0
- package/dist/esm/create-code-mode.js +12 -0
- package/dist/esm/create-code-mode.js.map +1 -0
- package/dist/esm/create-system-prompt.d.ts +24 -0
- package/dist/esm/create-system-prompt.js +66 -0
- package/dist/esm/create-system-prompt.js.map +1 -0
- package/dist/esm/index.d.ts +10 -0
- package/dist/esm/index.js +23 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/strip-typescript.d.ts +23 -0
- package/dist/esm/strip-typescript.js +52 -0
- package/dist/esm/strip-typescript.js.map +1 -0
- package/dist/esm/type-generator/json-schema-to-ts.d.ts +31 -0
- package/dist/esm/type-generator/json-schema-to-ts.js +100 -0
- package/dist/esm/type-generator/json-schema-to-ts.js.map +1 -0
- package/dist/esm/types.d.ts +176 -0
- package/package.json +62 -0
- package/src/agent-store.ts +43 -0
- package/src/bindings/tool-to-binding.ts +132 -0
- package/src/code-wrapper.ts +24 -0
- package/src/create-code-mode-tool.ts +254 -0
- package/src/create-code-mode.ts +35 -0
- package/src/create-system-prompt.ts +95 -0
- package/src/index.ts +55 -0
- package/src/strip-typescript.ts +94 -0
- package/src/type-generator/json-schema-to-ts.ts +188 -0
- package/src/types.ts +225 -0
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tanstack/ai-code-mode",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Code Mode for TanStack AI - LLM-driven code execution in secure sandboxes",
|
|
5
|
+
"author": "",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/TanStack/ai.git",
|
|
13
|
+
"directory": "packages/typescript/ai-code-mode"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"module": "./dist/esm/index.js",
|
|
17
|
+
"types": "./dist/esm/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/esm/index.d.ts",
|
|
21
|
+
"import": "./dist/esm/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"src"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "vite build",
|
|
34
|
+
"clean": "premove ./build ./dist",
|
|
35
|
+
"lint:fix": "eslint ./src --fix",
|
|
36
|
+
"test:build": "publint --strict",
|
|
37
|
+
"test:eslint": "eslint ./src",
|
|
38
|
+
"test:lib": "vitest",
|
|
39
|
+
"test:lib:dev": "pnpm test:lib --watch",
|
|
40
|
+
"test:types": "tsc"
|
|
41
|
+
},
|
|
42
|
+
"keywords": [
|
|
43
|
+
"ai",
|
|
44
|
+
"tanstack",
|
|
45
|
+
"code-mode",
|
|
46
|
+
"llm",
|
|
47
|
+
"sandbox",
|
|
48
|
+
"isolate"
|
|
49
|
+
],
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"esbuild": "^0.25.12"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"@tanstack/ai": "workspace:*",
|
|
55
|
+
"zod": "^3.0.0 || ^4.0.0"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@tanstack/ai": "workspace:*",
|
|
59
|
+
"@vitest/coverage-v8": "4.0.14",
|
|
60
|
+
"zod": "^4.2.0"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface AgentSession {
|
|
2
|
+
name: string
|
|
3
|
+
systemPrompt: string
|
|
4
|
+
memory: Record<string, unknown>
|
|
5
|
+
createdAt: number
|
|
6
|
+
lastUsedAt: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface AgentStore {
|
|
10
|
+
get: (name: string) => Promise<AgentSession | null>
|
|
11
|
+
set: (name: string, session: AgentSession) => Promise<void>
|
|
12
|
+
delete: (name: string) => Promise<void>
|
|
13
|
+
list: () => Promise<Array<string>>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class InMemoryAgentStore implements AgentStore {
|
|
17
|
+
private sessions = new Map<string, AgentSession>()
|
|
18
|
+
|
|
19
|
+
get(name: string): Promise<AgentSession | null> {
|
|
20
|
+
return Promise.resolve(this.sessions.get(name) ?? null)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
set(name: string, session: AgentSession): Promise<void> {
|
|
24
|
+
this.sessions.set(name, session)
|
|
25
|
+
return Promise.resolve()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
delete(name: string): Promise<void> {
|
|
29
|
+
this.sessions.delete(name)
|
|
30
|
+
return Promise.resolve()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
list(): Promise<Array<string>> {
|
|
34
|
+
return Promise.resolve(Array.from(this.sessions.keys()))
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function generateAgentName(): string {
|
|
39
|
+
const hex = Array.from({ length: 8 }, () =>
|
|
40
|
+
Math.floor(Math.random() * 16).toString(16),
|
|
41
|
+
).join('')
|
|
42
|
+
return `agent_${hex}`
|
|
43
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { convertSchemaToJsonSchema } from '@tanstack/ai'
|
|
2
|
+
import type { ToolExecutionContext } from '@tanstack/ai'
|
|
3
|
+
import type { CodeModeTool, ToolBinding } from '../types'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Convert an array of TanStack AI tools to a Record of ToolBindings
|
|
7
|
+
*
|
|
8
|
+
* @param tools - Array of tools to convert
|
|
9
|
+
* @param prefix - Optional prefix to add to binding names (e.g., 'external_')
|
|
10
|
+
*/
|
|
11
|
+
export function toolsToBindings(
|
|
12
|
+
tools: Array<CodeModeTool>,
|
|
13
|
+
prefix: string = '',
|
|
14
|
+
): Record<string, ToolBinding> {
|
|
15
|
+
const bindings: Record<string, ToolBinding> = {}
|
|
16
|
+
|
|
17
|
+
for (const tool of tools) {
|
|
18
|
+
const bindingName = `${prefix}${tool.name}`
|
|
19
|
+
bindings[bindingName] = toolToBinding(tool, prefix)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return bindings
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Convert a single TanStack AI tool to a ToolBinding
|
|
27
|
+
*
|
|
28
|
+
* @param tool - Tool to convert
|
|
29
|
+
* @param prefix - Optional prefix to add to binding name (e.g., 'external_')
|
|
30
|
+
* @throws Error if the tool doesn't have an execute function
|
|
31
|
+
*/
|
|
32
|
+
export function toolToBinding(
|
|
33
|
+
tool: CodeModeTool,
|
|
34
|
+
prefix: string = '',
|
|
35
|
+
): ToolBinding {
|
|
36
|
+
// Convert schemas (Zod or Standard Schema) to JSON Schema
|
|
37
|
+
const inputSchema = convertSchemaToJsonSchema(tool.inputSchema) || {
|
|
38
|
+
type: 'object',
|
|
39
|
+
properties: {},
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const outputSchema = tool.outputSchema
|
|
43
|
+
? convertSchemaToJsonSchema(tool.outputSchema)
|
|
44
|
+
: undefined
|
|
45
|
+
|
|
46
|
+
// Get execute function
|
|
47
|
+
// ServerTool has execute, ToolDefinition (without .server()) does not
|
|
48
|
+
let execute: (
|
|
49
|
+
args: unknown,
|
|
50
|
+
context?: ToolExecutionContext,
|
|
51
|
+
) => Promise<unknown>
|
|
52
|
+
|
|
53
|
+
if ('execute' in tool && typeof tool.execute === 'function') {
|
|
54
|
+
const toolExecute = tool.execute
|
|
55
|
+
execute = (args: unknown, context?: ToolExecutionContext) => {
|
|
56
|
+
// Pass context to the underlying tool so it can emit custom events
|
|
57
|
+
return Promise.resolve(toolExecute(args, context))
|
|
58
|
+
}
|
|
59
|
+
} else if ('__toolSide' in tool && tool.__toolSide === 'definition') {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Tool "${tool.name}" is a ToolDefinition without an execute function. ` +
|
|
62
|
+
`Call .server(fn) to provide an implementation before using with Code Mode.`,
|
|
63
|
+
)
|
|
64
|
+
} else {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Tool "${tool.name}" does not have an execute function. ` +
|
|
67
|
+
`Code Mode requires tools with implementations.`,
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
name: `${prefix}${tool.name}`,
|
|
73
|
+
description: tool.description,
|
|
74
|
+
inputSchema,
|
|
75
|
+
outputSchema,
|
|
76
|
+
execute,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Create event-aware bindings that emit custom events for each external function call.
|
|
82
|
+
* Wraps each binding's execute function to emit events before and after execution.
|
|
83
|
+
*
|
|
84
|
+
* @param bindings - Original tool bindings
|
|
85
|
+
* @param emitCustomEvent - Callback to emit custom events to the stream
|
|
86
|
+
*/
|
|
87
|
+
export function createEventAwareBindings(
|
|
88
|
+
bindings: Record<string, ToolBinding>,
|
|
89
|
+
emitCustomEvent: (eventName: string, data: Record<string, any>) => void,
|
|
90
|
+
): Record<string, ToolBinding> {
|
|
91
|
+
const wrapped: Record<string, ToolBinding> = {}
|
|
92
|
+
|
|
93
|
+
for (const [name, binding] of Object.entries(bindings)) {
|
|
94
|
+
wrapped[name] = {
|
|
95
|
+
...binding,
|
|
96
|
+
execute: async (args: unknown) => {
|
|
97
|
+
// Emit call event
|
|
98
|
+
emitCustomEvent('code_mode:external_call', {
|
|
99
|
+
function: name,
|
|
100
|
+
args,
|
|
101
|
+
timestamp: Date.now(),
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const startTime = Date.now()
|
|
105
|
+
try {
|
|
106
|
+
// Create context for the underlying tool so it can also emit events
|
|
107
|
+
const toolContext: ToolExecutionContext = { emitCustomEvent }
|
|
108
|
+
const result = await binding.execute(args, toolContext)
|
|
109
|
+
|
|
110
|
+
// Emit result event
|
|
111
|
+
emitCustomEvent('code_mode:external_result', {
|
|
112
|
+
function: name,
|
|
113
|
+
result,
|
|
114
|
+
duration: Date.now() - startTime,
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
return result
|
|
118
|
+
} catch (error) {
|
|
119
|
+
// Emit error event
|
|
120
|
+
emitCustomEvent('code_mode:external_error', {
|
|
121
|
+
function: name,
|
|
122
|
+
error: error instanceof Error ? error.message : String(error),
|
|
123
|
+
duration: Date.now() - startTime,
|
|
124
|
+
})
|
|
125
|
+
throw error
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return wrapped
|
|
132
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrap user code in an async IIFE to support top-level await
|
|
3
|
+
*
|
|
4
|
+
* The code is wrapped so that:
|
|
5
|
+
* 1. Top-level await works
|
|
6
|
+
* 2. Return values are captured and serialized (for isolate boundary transfer)
|
|
7
|
+
* 3. Errors propagate correctly
|
|
8
|
+
*/
|
|
9
|
+
export function wrapCode(code: string): string {
|
|
10
|
+
// Results must be serialized to JSON because objects can't be transferred
|
|
11
|
+
// directly across the isolate boundary in isolated-vm
|
|
12
|
+
return `
|
|
13
|
+
(async function() {
|
|
14
|
+
try {
|
|
15
|
+
const __userResult = await (async function() {
|
|
16
|
+
${code}
|
|
17
|
+
})();
|
|
18
|
+
return JSON.stringify(__userResult);
|
|
19
|
+
} catch (__error) {
|
|
20
|
+
throw __error;
|
|
21
|
+
}
|
|
22
|
+
})()
|
|
23
|
+
`
|
|
24
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { toolDefinition } from '@tanstack/ai'
|
|
3
|
+
import {
|
|
4
|
+
createEventAwareBindings,
|
|
5
|
+
toolsToBindings,
|
|
6
|
+
} from './bindings/tool-to-binding'
|
|
7
|
+
import { stripTypeScript } from './strip-typescript'
|
|
8
|
+
import type { ServerTool, ToolExecutionContext } from '@tanstack/ai'
|
|
9
|
+
import type {
|
|
10
|
+
CodeModeTool,
|
|
11
|
+
CodeModeToolConfig,
|
|
12
|
+
CodeModeToolResult,
|
|
13
|
+
IsolateContext,
|
|
14
|
+
} from './types'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Schema for the execute_typescript tool input
|
|
18
|
+
*/
|
|
19
|
+
const executeTypescriptInputSchema = z.object({
|
|
20
|
+
typescriptCode: z
|
|
21
|
+
.string()
|
|
22
|
+
.describe(
|
|
23
|
+
'TypeScript code to execute in the sandbox. ' +
|
|
24
|
+
'Use external_* functions to call available APIs. ' +
|
|
25
|
+
'Return a value to pass results back.',
|
|
26
|
+
),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Schema for the execute_typescript tool output
|
|
31
|
+
*/
|
|
32
|
+
const executeTypescriptOutputSchema = z.object({
|
|
33
|
+
success: z.boolean().describe('Whether execution completed without errors'),
|
|
34
|
+
result: z
|
|
35
|
+
.unknown()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe('Return value from the executed code'),
|
|
38
|
+
logs: z
|
|
39
|
+
.array(z.string())
|
|
40
|
+
.optional()
|
|
41
|
+
.describe('Console output captured during execution'),
|
|
42
|
+
error: z
|
|
43
|
+
.object({
|
|
44
|
+
message: z.string(),
|
|
45
|
+
name: z.string().optional(),
|
|
46
|
+
line: z.number().optional(),
|
|
47
|
+
})
|
|
48
|
+
.optional()
|
|
49
|
+
.describe('Error details if execution failed'),
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
export type ExecuteTypescriptInput = z.infer<
|
|
53
|
+
typeof executeTypescriptInputSchema
|
|
54
|
+
>
|
|
55
|
+
export type ExecuteTypescriptOutput = z.infer<
|
|
56
|
+
typeof executeTypescriptOutputSchema
|
|
57
|
+
>
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create an execute_typescript tool that can be used alongside other agent tools.
|
|
61
|
+
*
|
|
62
|
+
* This tool allows an LLM to execute TypeScript code in a secure sandbox.
|
|
63
|
+
* Tools passed in the config become `external_*` functions available inside the sandbox.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* import { createCodeMode } from '@tanstack/ai-code-mode'
|
|
68
|
+
* import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
|
|
69
|
+
*
|
|
70
|
+
* const { tool, systemPrompt } = createCodeMode({
|
|
71
|
+
* driver: createNodeIsolateDriver(),
|
|
72
|
+
* tools: [weatherTool, dbTool], // Become external_fetchWeather, external_dbQuery
|
|
73
|
+
* timeout: 30000,
|
|
74
|
+
* })
|
|
75
|
+
*
|
|
76
|
+
* chat({
|
|
77
|
+
* systemPrompts: [myPrompt, systemPrompt],
|
|
78
|
+
* tools: [tool, searchTool, emailTool],
|
|
79
|
+
* messages,
|
|
80
|
+
* })
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export function createCodeModeTool(
|
|
84
|
+
config: CodeModeToolConfig,
|
|
85
|
+
): ServerTool<
|
|
86
|
+
typeof executeTypescriptInputSchema,
|
|
87
|
+
typeof executeTypescriptOutputSchema,
|
|
88
|
+
'execute_typescript'
|
|
89
|
+
> {
|
|
90
|
+
const {
|
|
91
|
+
driver,
|
|
92
|
+
tools,
|
|
93
|
+
timeout = 30000,
|
|
94
|
+
memoryLimit = 128,
|
|
95
|
+
getSkillBindings,
|
|
96
|
+
} = config
|
|
97
|
+
|
|
98
|
+
// Validate tools
|
|
99
|
+
if (tools.length === 0) {
|
|
100
|
+
throw new Error('At least one tool must be provided to createCodeModeTool')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Transform tools to bindings with external_ prefix (static bindings)
|
|
104
|
+
const staticBindings = toolsToBindings(tools, 'external_')
|
|
105
|
+
|
|
106
|
+
// Create the tool definition
|
|
107
|
+
const definition = toolDefinition({
|
|
108
|
+
name: 'execute_typescript' as const,
|
|
109
|
+
description: buildToolDescription(tools),
|
|
110
|
+
inputSchema: executeTypescriptInputSchema,
|
|
111
|
+
outputSchema: executeTypescriptOutputSchema,
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
// Return server tool with execute function that accepts context
|
|
115
|
+
return definition.server(
|
|
116
|
+
async (
|
|
117
|
+
input,
|
|
118
|
+
toolContext?: ToolExecutionContext,
|
|
119
|
+
): Promise<CodeModeToolResult> => {
|
|
120
|
+
const { typescriptCode } = input
|
|
121
|
+
|
|
122
|
+
// Get emitCustomEvent from context or use no-op
|
|
123
|
+
const emitCustomEvent = toolContext?.emitCustomEvent || (() => {})
|
|
124
|
+
|
|
125
|
+
if (!typescriptCode || typeof typescriptCode !== 'string') {
|
|
126
|
+
return {
|
|
127
|
+
success: false,
|
|
128
|
+
error: {
|
|
129
|
+
message: 'typescriptCode must be a non-empty string',
|
|
130
|
+
name: 'ValidationError',
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Create a fresh sandbox context for this execution
|
|
136
|
+
let isolateContext: IsolateContext | null = null
|
|
137
|
+
|
|
138
|
+
// Emit execution started event immediately
|
|
139
|
+
emitCustomEvent('code_mode:execution_started', {
|
|
140
|
+
timestamp: Date.now(),
|
|
141
|
+
codeLength: typescriptCode.length,
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
// Step 1: Strip TypeScript (also serves as syntax validation via esbuild)
|
|
146
|
+
let strippedCode: string
|
|
147
|
+
try {
|
|
148
|
+
strippedCode = await stripTypeScript(typescriptCode)
|
|
149
|
+
} catch (error) {
|
|
150
|
+
// Type/syntax error from esbuild
|
|
151
|
+
return {
|
|
152
|
+
success: false,
|
|
153
|
+
error: {
|
|
154
|
+
message: error instanceof Error ? error.message : String(error),
|
|
155
|
+
name: 'TypeScriptError',
|
|
156
|
+
},
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Step 2: Get dynamic skill bindings if available
|
|
161
|
+
const skillBindings = getSkillBindings ? await getSkillBindings() : {}
|
|
162
|
+
|
|
163
|
+
// Step 3: Merge static and dynamic bindings, then wrap with event awareness
|
|
164
|
+
const allBindings = { ...staticBindings, ...skillBindings }
|
|
165
|
+
const eventAwareBindings = createEventAwareBindings(
|
|
166
|
+
allBindings,
|
|
167
|
+
emitCustomEvent,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
// Step 4: Create sandbox context with event-aware bindings
|
|
171
|
+
isolateContext = await driver.createContext({
|
|
172
|
+
bindings: eventAwareBindings,
|
|
173
|
+
timeout,
|
|
174
|
+
memoryLimit,
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
// Step 5: Execute the code in the sandbox
|
|
178
|
+
const executionResult = await isolateContext.execute(strippedCode)
|
|
179
|
+
|
|
180
|
+
// Emit console logs as custom events
|
|
181
|
+
if (executionResult.logs && executionResult.logs.length > 0) {
|
|
182
|
+
for (const log of executionResult.logs) {
|
|
183
|
+
// Parse log level from prefix (added by sandbox console implementation)
|
|
184
|
+
let level: 'log' | 'warn' | 'error' | 'info' = 'log'
|
|
185
|
+
let message = log
|
|
186
|
+
|
|
187
|
+
if (log.startsWith('ERROR: ')) {
|
|
188
|
+
level = 'error'
|
|
189
|
+
message = log.slice(7)
|
|
190
|
+
} else if (log.startsWith('WARN: ')) {
|
|
191
|
+
level = 'warn'
|
|
192
|
+
message = log.slice(6)
|
|
193
|
+
} else if (log.startsWith('INFO: ')) {
|
|
194
|
+
level = 'info'
|
|
195
|
+
message = log.slice(6)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
emitCustomEvent('code_mode:console', {
|
|
199
|
+
level,
|
|
200
|
+
message,
|
|
201
|
+
timestamp: Date.now(),
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (executionResult.success) {
|
|
207
|
+
return {
|
|
208
|
+
success: true,
|
|
209
|
+
result: executionResult.value,
|
|
210
|
+
logs: executionResult.logs,
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
return {
|
|
214
|
+
success: false,
|
|
215
|
+
error: executionResult.error
|
|
216
|
+
? {
|
|
217
|
+
message: executionResult.error.message,
|
|
218
|
+
name: executionResult.error.name,
|
|
219
|
+
}
|
|
220
|
+
: { message: 'Unknown execution error' },
|
|
221
|
+
logs: executionResult.logs,
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
} catch (error) {
|
|
225
|
+
return {
|
|
226
|
+
success: false,
|
|
227
|
+
error: {
|
|
228
|
+
message: error instanceof Error ? error.message : String(error),
|
|
229
|
+
name: error instanceof Error ? error.name : 'Error',
|
|
230
|
+
},
|
|
231
|
+
}
|
|
232
|
+
} finally {
|
|
233
|
+
// Always clean up the sandbox context
|
|
234
|
+
if (isolateContext) {
|
|
235
|
+
await isolateContext.dispose()
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Build the tool description including available external functions
|
|
244
|
+
*/
|
|
245
|
+
function buildToolDescription(tools: Array<CodeModeTool>): string {
|
|
246
|
+
const externalFunctions = tools.map((t) => `external_${t.name}`).join(', ')
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
`Execute TypeScript code in a secure sandbox environment. ` +
|
|
250
|
+
`The code can use these external API functions: ${externalFunctions}. ` +
|
|
251
|
+
`All external_* calls are async and must be awaited. ` +
|
|
252
|
+
`Return a value to pass results back. Use console.log() for debugging.`
|
|
253
|
+
)
|
|
254
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createCodeModeTool } from './create-code-mode-tool'
|
|
2
|
+
import { createCodeModeSystemPrompt } from './create-system-prompt'
|
|
3
|
+
import type { CodeModeToolConfig } from './types'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create both the `execute_typescript` tool and its matching system prompt
|
|
7
|
+
* from a single config object.
|
|
8
|
+
*
|
|
9
|
+
* This is the recommended way to set up Code Mode — it ensures the tool and
|
|
10
|
+
* system prompt always stay in sync.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { createCodeMode } from '@tanstack/ai-code-mode'
|
|
15
|
+
* import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
|
|
16
|
+
*
|
|
17
|
+
* const { tool, systemPrompt } = createCodeMode({
|
|
18
|
+
* driver: createNodeIsolateDriver(),
|
|
19
|
+
* tools: [weatherTool, dbTool],
|
|
20
|
+
* timeout: 30000,
|
|
21
|
+
* })
|
|
22
|
+
*
|
|
23
|
+
* chat({
|
|
24
|
+
* systemPrompts: [myPrompt, systemPrompt],
|
|
25
|
+
* tools: [tool, ...otherTools],
|
|
26
|
+
* messages,
|
|
27
|
+
* })
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function createCodeMode(config: CodeModeToolConfig) {
|
|
31
|
+
return {
|
|
32
|
+
tool: createCodeModeTool(config),
|
|
33
|
+
systemPrompt: createCodeModeSystemPrompt(config),
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { toolsToBindings } from './bindings/tool-to-binding'
|
|
2
|
+
import { generateTypeStubs } from './type-generator/json-schema-to-ts'
|
|
3
|
+
import type { CodeModeToolConfig } from './types'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create a system prompt snippet that documents the execute_typescript tool
|
|
7
|
+
* and all available external_* functions.
|
|
8
|
+
*
|
|
9
|
+
* Add this to your system prompts array when using createCodeModeTool.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { createCodeMode } from '@tanstack/ai-code-mode'
|
|
14
|
+
* import { createNodeIsolateDriver } from '@tanstack/ai-isolate-node'
|
|
15
|
+
*
|
|
16
|
+
* const { tool, systemPrompt } = createCodeMode({
|
|
17
|
+
* driver: createNodeIsolateDriver(),
|
|
18
|
+
* tools: [weatherTool, dbTool],
|
|
19
|
+
* })
|
|
20
|
+
*
|
|
21
|
+
* chat({
|
|
22
|
+
* systemPrompts: ['You are a helpful assistant.', systemPrompt],
|
|
23
|
+
* tools: [tool, ...otherTools],
|
|
24
|
+
* })
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function createCodeModeSystemPrompt(config: CodeModeToolConfig): string {
|
|
28
|
+
const { tools } = config
|
|
29
|
+
|
|
30
|
+
// Transform tools to bindings with external_ prefix to generate correct type stubs
|
|
31
|
+
const bindings = toolsToBindings(tools, 'external_')
|
|
32
|
+
|
|
33
|
+
// Generate TypeScript type stubs for the external functions
|
|
34
|
+
const typeStubs = generateTypeStubs(bindings)
|
|
35
|
+
|
|
36
|
+
// Build function documentation
|
|
37
|
+
const functionDocs = Object.entries(bindings)
|
|
38
|
+
.map(([name, binding]) => {
|
|
39
|
+
const doc = `- \`${name}(input)\`: ${binding.description}`
|
|
40
|
+
return doc
|
|
41
|
+
})
|
|
42
|
+
.join('\n')
|
|
43
|
+
|
|
44
|
+
return `## Code Execution Tool
|
|
45
|
+
|
|
46
|
+
You have access to \`execute_typescript\` which runs TypeScript code in a sandboxed environment.
|
|
47
|
+
|
|
48
|
+
### When to Use
|
|
49
|
+
|
|
50
|
+
Use \`execute_typescript\` when you need to:
|
|
51
|
+
- Process data with loops, conditionals, or complex logic
|
|
52
|
+
- Make multiple API calls in parallel (Promise.all)
|
|
53
|
+
- Transform, filter, or aggregate data
|
|
54
|
+
- Perform calculations or data analysis
|
|
55
|
+
|
|
56
|
+
For simple operations, prefer calling tools directly.
|
|
57
|
+
|
|
58
|
+
### Available External APIs
|
|
59
|
+
|
|
60
|
+
Inside your TypeScript code, you can call these async functions:
|
|
61
|
+
|
|
62
|
+
${functionDocs}
|
|
63
|
+
|
|
64
|
+
### Type Definitions
|
|
65
|
+
|
|
66
|
+
\`\`\`typescript
|
|
67
|
+
${typeStubs}
|
|
68
|
+
\`\`\`
|
|
69
|
+
|
|
70
|
+
### Example
|
|
71
|
+
|
|
72
|
+
\`\`\`typescript
|
|
73
|
+
// Fetch weather for multiple cities in parallel
|
|
74
|
+
const cities = ["Tokyo", "Paris", "NYC"];
|
|
75
|
+
const results = await Promise.all(
|
|
76
|
+
cities.map(city => external_fetchWeather({ location: city }))
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// Find the warmest city
|
|
80
|
+
const warmest = results.reduce((prev, curr) =>
|
|
81
|
+
curr.temperature > prev.temperature ? curr : prev
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return { warmestCity: warmest.location, temperature: warmest.temperature };
|
|
85
|
+
\`\`\`
|
|
86
|
+
|
|
87
|
+
### Important Notes
|
|
88
|
+
|
|
89
|
+
- All \`external_*\` calls are async - always use \`await\`
|
|
90
|
+
- Return a value to pass results back to you
|
|
91
|
+
- Use \`console.log()\` for debugging (logs are captured)
|
|
92
|
+
- The sandbox is isolated - no network access or file system
|
|
93
|
+
- Each execution is independent (no shared state between calls)
|
|
94
|
+
`
|
|
95
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Code Mode Tool API
|
|
2
|
+
export { createCodeModeTool } from './create-code-mode-tool'
|
|
3
|
+
export type {
|
|
4
|
+
ExecuteTypescriptInput,
|
|
5
|
+
ExecuteTypescriptOutput,
|
|
6
|
+
} from './create-code-mode-tool'
|
|
7
|
+
|
|
8
|
+
export { createCodeModeSystemPrompt } from './create-system-prompt'
|
|
9
|
+
export { createCodeMode } from './create-code-mode'
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
InMemoryAgentStore,
|
|
13
|
+
generateAgentName,
|
|
14
|
+
type AgentSession,
|
|
15
|
+
type AgentStore,
|
|
16
|
+
} from './agent-store'
|
|
17
|
+
|
|
18
|
+
// Bindings utilities (useful for custom implementations)
|
|
19
|
+
export {
|
|
20
|
+
toolToBinding,
|
|
21
|
+
toolsToBindings,
|
|
22
|
+
createEventAwareBindings,
|
|
23
|
+
} from './bindings/tool-to-binding'
|
|
24
|
+
|
|
25
|
+
// Type generator (useful for custom system prompts)
|
|
26
|
+
export {
|
|
27
|
+
generateTypeStubs,
|
|
28
|
+
jsonSchemaToTypeScript,
|
|
29
|
+
type TypeGeneratorOptions,
|
|
30
|
+
} from './type-generator/json-schema-to-ts'
|
|
31
|
+
|
|
32
|
+
// TypeScript stripping utility
|
|
33
|
+
export { stripTypeScript } from './strip-typescript'
|
|
34
|
+
|
|
35
|
+
// Code wrapper utility (used by isolate drivers)
|
|
36
|
+
export { wrapCode } from './code-wrapper'
|
|
37
|
+
|
|
38
|
+
// All types
|
|
39
|
+
export type {
|
|
40
|
+
// Tool-based API types
|
|
41
|
+
CodeModeToolConfig,
|
|
42
|
+
CodeModeToolResult,
|
|
43
|
+
// Isolate driver interfaces (used by driver packages)
|
|
44
|
+
IsolateDriver,
|
|
45
|
+
IsolateConfig,
|
|
46
|
+
IsolateContext,
|
|
47
|
+
ExecutionResult,
|
|
48
|
+
NormalizedError,
|
|
49
|
+
// Tool binding (used by driver packages)
|
|
50
|
+
ToolBinding,
|
|
51
|
+
// Tool input types
|
|
52
|
+
CodeModeTool,
|
|
53
|
+
// Re-exported from @tanstack/ai
|
|
54
|
+
ToolExecutionContext,
|
|
55
|
+
} from './types'
|