Unified · Split
File renamed without changes.
| @@ -0,0 +1,54 @@ | |||
| 1 | { | ||
| 2 | "lockfileVersion": 1, | ||
| 3 | "configVersion": 1, | ||
| 4 | "workspaces": { | ||
| 5 | "": { | ||
| 6 | "name": "executor-v3", | ||
| 7 | "devDependencies": { | ||
| 8 | "typescript": "^5.9.3", | ||
| 9 | }, | ||
| 10 | }, | ||
| 11 | "packages/ai-sdk-adapter": { | ||
| 12 | "name": "@executor-v3/ai-sdk-adapter", | ||
| 13 | "dependencies": { | ||
| 14 | "ai": "^6.0.0", | ||
| 15 | "zod": "^4.3.6", | ||
| 16 | }, | ||
| 17 | "devDependencies": { | ||
| 18 | "@types/node": "^24.3.1", | ||
| 19 | "bun-types": "^1.2.22", | ||
| 20 | }, | ||
| 21 | }, | ||
| 22 | }, | ||
| 23 | "packages": { | ||
| 24 | "@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.66", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-SIQ0YY0iMuv+07HLsZ+bB990zUJ6S4ujORAh+Jv1V2KGNn73qQKnGO0JBk+w+Res8YqOFSycwDoWcFlQrVxS4A=="], | ||
| 25 | |||
| 26 | "@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="], | ||
| 27 | |||
| 28 | "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.19", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-3eG55CrSWCu2SXlqq2QCsFjo3+E7+Gmg7i/oRVoSZzIodTuDSfLb3MRje67xE9RFea73Zao7Lm4mADIfUETKGg=="], | ||
| 29 | |||
| 30 | "@executor-v3/ai-sdk-adapter": ["@executor-v3/ai-sdk-adapter@workspace:packages/ai-sdk-adapter"], | ||
| 31 | |||
| 32 | "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], | ||
| 33 | |||
| 34 | "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], | ||
| 35 | |||
| 36 | "@types/node": ["@types/node@24.11.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-fPxQqz4VTgPI/IQ+lj9r0h+fDR66bzoeMGHp8ASee+32OSGIkeASsoZuJixsQoVef1QJbeubcPBxKk22QVoWdw=="], | ||
| 37 | |||
| 38 | "@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="], | ||
| 39 | |||
| 40 | "ai": ["ai@6.0.116", "", { "dependencies": { "@ai-sdk/gateway": "3.0.66", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7yM+cTmyRLeNIXwt4Vj+mrrJgVQ9RMIW5WO0ydoLoYkewIvsMcvUmqS4j2RJTUXaF1HphwmSKUMQ/HypNRGOmA=="], | ||
| 41 | |||
| 42 | "bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="], | ||
| 43 | |||
| 44 | "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="], | ||
| 45 | |||
| 46 | "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], | ||
| 47 | |||
| 48 | "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], | ||
| 49 | |||
| 50 | "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], | ||
| 51 | |||
| 52 | "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], | ||
| 53 | } | ||
| 54 | } | ||
| @@ -0,0 +1,15 @@ | |||
| 1 | { | ||
| 2 | "name": "executor-v3", | ||
| 3 | "private": true, | ||
| 4 | "type": "module", | ||
| 5 | "packageManager": "bun@1.2.22", | ||
| 6 | "workspaces": [ | ||
| 7 | "packages/*" | ||
| 8 | ], | ||
| 9 | "scripts": { | ||
| 10 | "typecheck": "bun run --filter='*' typecheck" | ||
| 11 | }, | ||
| 12 | "devDependencies": { | ||
| 13 | "typescript": "^5.9.3" | ||
| 14 | } | ||
| 15 | } | ||
| @@ -0,0 +1,23 @@ | |||
| 1 | { | ||
| 2 | "name": "@executor-v3/ai-sdk-adapter", | ||
| 3 | "private": true, | ||
| 4 | "type": "module", | ||
| 5 | "exports": { | ||
| 6 | ".": "./src/index.ts", | ||
| 7 | "./ai": "./src/ai.ts", | ||
| 8 | "./example": "./src/example.ts", | ||
| 9 | "./example-static": "./src/example-static.ts", | ||
| 10 | "./example-dynamic": "./src/example-dynamic.ts" | ||
| 11 | }, | ||
| 12 | "scripts": { | ||
| 13 | "typecheck": "bunx tsc --noEmit -p tsconfig.json" | ||
| 14 | }, | ||
| 15 | "dependencies": { | ||
| 16 | "ai": "^6.0.0", | ||
| 17 | "zod": "^4.3.6" | ||
| 18 | }, | ||
| 19 | "devDependencies": { | ||
| 20 | "@types/node": "^24.3.1", | ||
| 21 | "bun-types": "^1.2.22" | ||
| 22 | } | ||
| 23 | } | ||
| @@ -0,0 +1,12 @@ | |||
| 1 | export { | ||
| 2 | createDynamicDiscovery, | ||
| 3 | createStaticDiscoveryFromTools, | ||
| 4 | createToolsFromAiSdkTools, | ||
| 5 | toExecutorTool, | ||
| 6 | wrapTool, | ||
| 7 | type AiSdkToolMap, | ||
| 8 | type ExecutorAiSdkMetadata, | ||
| 9 | type ExecutorToolDefinition, | ||
| 10 | type ExecutorToolInput, | ||
| 11 | type ExecutorToolMap, | ||
| 12 | } from "./index"; | ||
| @@ -0,0 +1,54 @@ | |||
| 1 | import { | ||
| 2 | createDynamicDiscovery, | ||
| 3 | type SearchProvider, | ||
| 4 | type ToolDirectory, | ||
| 5 | type ToolPath, | ||
| 6 | } from "./index"; | ||
| 7 | |||
| 8 | const asToolPath = (value: string): ToolPath => value as ToolPath; | ||
| 9 | |||
| 10 | const directory: ToolDirectory = { | ||
| 11 | async listNamespaces() { | ||
| 12 | return [ | ||
| 13 | { namespace: "source.src_api", toolCount: 6800 }, | ||
| 14 | { namespace: "source.src_mcp", toolCount: 3200 }, | ||
| 15 | ]; | ||
| 16 | }, | ||
| 17 | async listTools() { | ||
| 18 | return [{ path: asToolPath("source.src_api.github.issues.list") }]; | ||
| 19 | }, | ||
| 20 | async getByPath({ path }: { path: ToolPath; includeSchemas: boolean }) { | ||
| 21 | return { | ||
| 22 | path, | ||
| 23 | sourceKey: "source.src_api", | ||
| 24 | description: "Hydrated metadata for selected path", | ||
| 25 | inputHint: "object", | ||
| 26 | outputHint: "object", | ||
| 27 | }; | ||
| 28 | }, | ||
| 29 | async getByPaths({ paths }: { paths: readonly ToolPath[]; includeSchemas: boolean }) { | ||
| 30 | return paths.map((path) => ({ | ||
| 31 | path, | ||
| 32 | sourceKey: "source.src_api", | ||
| 33 | description: "Hydrated from metadata store", | ||
| 34 | inputHint: "object", | ||
| 35 | outputHint: "object", | ||
| 36 | })); | ||
| 37 | }, | ||
| 38 | }; | ||
| 39 | |||
| 40 | const search: SearchProvider = { | ||
| 41 | async search({ limit }) { | ||
| 42 | return [ | ||
| 43 | { path: asToolPath("source.src_api.github.issues.list"), score: 0.99 }, | ||
| 44 | { path: asToolPath("source.src_api.github.issues.create"), score: 0.92 }, | ||
| 45 | ].slice(0, limit); | ||
| 46 | }, | ||
| 47 | }; | ||
| 48 | |||
| 49 | export const dynamicDemo = createDynamicDiscovery({ | ||
| 50 | directory, | ||
| 51 | search, | ||
| 52 | }); | ||
| 53 | |||
| 54 | console.log(dynamicDemo) | ||
| @@ -0,0 +1,42 @@ | |||
| 1 | import { convertFileListToFileUIParts, tool } from "ai"; | ||
| 2 | import { z } from "zod"; | ||
| 3 | |||
| 4 | import { createStaticDiscoveryFromTools, toExecutorTool } from "./index"; | ||
| 5 | |||
| 6 | const listIssues = tool({ | ||
| 7 | description: "List repository issues", | ||
| 8 | inputSchema: z.object({ owner: z.string(), repo: z.string() }), | ||
| 9 | execute: async ({ owner, repo }: { owner: string; repo: string }) => ({ | ||
| 10 | items: [`${owner}/${repo}#1`], | ||
| 11 | }), | ||
| 12 | }); | ||
| 13 | |||
| 14 | const createIssue = tool({ | ||
| 15 | description: "Create repository issue", | ||
| 16 | inputSchema: z.object({ | ||
| 17 | owner: z.string(), | ||
| 18 | repo: z.string(), | ||
| 19 | title: z.string(), | ||
| 20 | }), | ||
| 21 | execute: async ({ owner, repo, title }: { owner: string; repo: string; title: string }) => ({ | ||
| 22 | id: "issue_123", | ||
| 23 | owner, | ||
| 24 | repo, | ||
| 25 | title, | ||
| 26 | }), | ||
| 27 | }); | ||
| 28 | |||
| 29 | const wrappedTools = { | ||
| 30 | "github.issues.list": listIssues, | ||
| 31 | "github.issues.create": toExecutorTool({ | ||
| 32 | tool: createIssue, | ||
| 33 | metadata: { | ||
| 34 | interaction: "required", | ||
| 35 | }, | ||
| 36 | }), | ||
| 37 | }; | ||
| 38 | |||
| 39 | export const staticDemo = createStaticDiscoveryFromTools({ | ||
| 40 | tools: wrappedTools, | ||
| 41 | sourceKey: "api.github", | ||
| 42 | }); | ||
| @@ -0,0 +1,2 @@ | |||
| 1 | export { staticDemo } from "./example-static"; | ||
| 2 | export { dynamicDemo } from "./example-dynamic"; | ||
| @@ -0,0 +1,435 @@ | |||
| 1 | import type { ToolSet } from "ai"; | ||
| 2 | |||
| 3 | export type ToolPath = string & { readonly __toolPath: unique symbol }; | ||
| 4 | |||
| 5 | export type ToolDescriptor = { | ||
| 6 | path: ToolPath; | ||
| 7 | sourceKey: string; | ||
| 8 | description?: string; | ||
| 9 | interaction?: "auto" | "required"; | ||
| 10 | inputHint?: string; | ||
| 11 | outputHint?: string; | ||
| 12 | inputSchemaJson?: string; | ||
| 13 | outputSchemaJson?: string; | ||
| 14 | refHintKeys?: readonly string[]; | ||
| 15 | }; | ||
| 16 | |||
| 17 | export type ExecutorAiSdkMetadata = { | ||
| 18 | interaction?: "auto" | "required"; | ||
| 19 | inputHint?: string; | ||
| 20 | outputHint?: string; | ||
| 21 | inputSchemaJson?: string; | ||
| 22 | outputSchemaJson?: string; | ||
| 23 | refHintKeys?: readonly string[]; | ||
| 24 | sourceKey?: string; | ||
| 25 | }; | ||
| 26 | |||
| 27 | export type AiSdkToolMap = ToolSet; | ||
| 28 | |||
| 29 | export type ExecutorToolDefinition = { | ||
| 30 | tool: ToolSet[string]; | ||
| 31 | metadata?: ExecutorAiSdkMetadata; | ||
| 32 | }; | ||
| 33 | |||
| 34 | export type ExecutorToolInput = ToolSet[string] | ExecutorToolDefinition; | ||
| 35 | |||
| 36 | export type ExecutorToolMap = Record<string, ExecutorToolInput>; | ||
| 37 | |||
| 38 | type ResolvedExecutorTool = { | ||
| 39 | path: ToolPath; | ||
| 40 | tool: ToolSet[string]; | ||
| 41 | metadata?: ExecutorAiSdkMetadata; | ||
| 42 | }; | ||
| 43 | |||
| 44 | export type SearchHit = { | ||
| 45 | path: ToolPath; | ||
| 46 | score: number; | ||
| 47 | }; | ||
| 48 | |||
| 49 | export interface SearchProvider { | ||
| 50 | search(input: { | ||
| 51 | query: string; | ||
| 52 | limit: number; | ||
| 53 | }): Promise<readonly SearchHit[]>; | ||
| 54 | } | ||
| 55 | |||
| 56 | export interface ToolDirectory { | ||
| 57 | listNamespaces(input: { | ||
| 58 | limit: number; | ||
| 59 | }): Promise<readonly { namespace: string; toolCount: number }[]>; | ||
| 60 | |||
| 61 | listTools(input: { | ||
| 62 | namespace?: string; | ||
| 63 | query?: string; | ||
| 64 | limit: number; | ||
| 65 | }): Promise<readonly { path: ToolPath }[]>; | ||
| 66 | |||
| 67 | getByPath(input: { | ||
| 68 | path: ToolPath; | ||
| 69 | includeSchemas: boolean; | ||
| 70 | }): Promise<ToolDescriptor | null>; | ||
| 71 | |||
| 72 | getByPaths(input: { | ||
| 73 | paths: readonly ToolPath[]; | ||
| 74 | includeSchemas: boolean; | ||
| 75 | }): Promise<readonly ToolDescriptor[]>; | ||
| 76 | } | ||
| 77 | |||
| 78 | export type CatalogPrimitive = { | ||
| 79 | namespaces(input: { | ||
| 80 | limit?: number; | ||
| 81 | }): Promise<{ namespaces: readonly { namespace: string; toolCount: number }[] }>; | ||
| 82 | tools(input: { | ||
| 83 | namespace?: string; | ||
| 84 | query?: string; | ||
| 85 | limit?: number; | ||
| 86 | }): Promise<{ results: readonly { path: ToolPath }[] }>; | ||
| 87 | }; | ||
| 88 | |||
| 89 | export type DescribePrimitive = { | ||
| 90 | tool(input: { | ||
| 91 | path: ToolPath; | ||
| 92 | includeSchemas?: boolean; | ||
| 93 | }): Promise<ToolDescriptor | null>; | ||
| 94 | }; | ||
| 95 | |||
| 96 | export type DiscoverPrimitive = { | ||
| 97 | run(input: { | ||
| 98 | query: string; | ||
| 99 | limit?: number; | ||
| 100 | includeSchemas?: boolean; | ||
| 101 | }): Promise<{ | ||
| 102 | bestPath: ToolPath | null; | ||
| 103 | results: readonly (Record<string, unknown> & { | ||
| 104 | path: ToolPath; | ||
| 105 | score: number; | ||
| 106 | })[]; | ||
| 107 | total: number; | ||
| 108 | }>; | ||
| 109 | }; | ||
| 110 | |||
| 111 | export type DiscoveryPrimitives = { | ||
| 112 | catalog?: CatalogPrimitive; | ||
| 113 | describe?: DescribePrimitive; | ||
| 114 | discover?: DiscoverPrimitive; | ||
| 115 | }; | ||
| 116 | |||
| 117 | const asToolPath = (value: string): ToolPath => value as ToolPath; | ||
| 118 | |||
| 119 | export function wrapTool(input: { | ||
| 120 | tool: ToolSet[string]; | ||
| 121 | metadata?: ExecutorAiSdkMetadata; | ||
| 122 | }): ExecutorToolDefinition { | ||
| 123 | return { | ||
| 124 | tool: input.tool, | ||
| 125 | metadata: input.metadata, | ||
| 126 | }; | ||
| 127 | } | ||
| 128 | |||
| 129 | export const toExecutorTool = wrapTool; | ||
| 130 | |||
| 131 | const isExecutorToolDefinition = ( | ||
| 132 | value: ExecutorToolInput, | ||
| 133 | ): value is ExecutorToolDefinition => | ||
| 134 | typeof value === "object" && value !== null && "tool" in value; | ||
| 135 | |||
| 136 | const stringifySchema = (value: unknown): string | undefined => { | ||
| 137 | if (value === undefined || value === null) { | ||
| 138 | return undefined; | ||
| 139 | } | ||
| 140 | |||
| 141 | try { | ||
| 142 | return JSON.stringify(value); | ||
| 143 | } catch { | ||
| 144 | return undefined; | ||
| 145 | } | ||
| 146 | }; | ||
| 147 | |||
| 148 | const inferHintFromSchemaJson = ( | ||
| 149 | schemaJson: string | undefined, | ||
| 150 | fallback: string, | ||
| 151 | ): string => { | ||
| 152 | if (!schemaJson) { | ||
| 153 | return fallback; | ||
| 154 | } | ||
| 155 | |||
| 156 | try { | ||
| 157 | const parsed = JSON.parse(schemaJson) as Record<string, unknown>; | ||
| 158 | const title = typeof parsed.title === "string" ? parsed.title.trim() : ""; | ||
| 159 | if (title.length > 0) { | ||
| 160 | return title; | ||
| 161 | } | ||
| 162 | |||
| 163 | if (parsed.type === "object") { | ||
| 164 | const properties = | ||
| 165 | parsed.properties && | ||
| 166 | typeof parsed.properties === "object" && | ||
| 167 | !Array.isArray(parsed.properties) | ||
| 168 | ? Object.keys(parsed.properties as Record<string, unknown>) | ||
| 169 | : []; | ||
| 170 | if (properties.length > 0) { | ||
| 171 | const shown = properties.slice(0, 3).join(", "); | ||
| 172 | return properties.length <= 3 | ||
| 173 | ? `object { ${shown} }` | ||
| 174 | : `object { ${shown}, ... }`; | ||
| 175 | } | ||
| 176 | return "object"; | ||
| 177 | } | ||
| 178 | |||
| 179 | if (parsed.type === "array") { | ||
| 180 | return "array"; | ||
| 181 | } | ||
| 182 | |||
| 183 | if (typeof parsed.type === "string") { | ||
| 184 | return parsed.type; | ||
| 185 | } | ||
| 186 | } catch { | ||
| 187 | // Ignore malformed schema and fall back. | ||
| 188 | } | ||
| 189 | |||
| 190 | return fallback; | ||
| 191 | }; | ||
| 192 | |||
| 193 | export function createToolsFromAiSdkTools(input: { | ||
| 194 | tools: AiSdkToolMap; | ||
| 195 | sourceKey?: string; | ||
| 196 | }): ExecutorToolMap { | ||
| 197 | const { tools, sourceKey = "in_memory.ai_sdk" } = input; | ||
| 198 | |||
| 199 | return Object.fromEntries( | ||
| 200 | Object.entries(tools) | ||
| 201 | .sort(([left], [right]) => left.localeCompare(right)) | ||
| 202 | .map(([path, tool]) => [ | ||
| 203 | path, | ||
| 204 | wrapTool({ | ||
| 205 | tool, | ||
| 206 | metadata: { sourceKey }, | ||
| 207 | }), | ||
| 208 | ]), | ||
| 209 | ) as ExecutorToolMap; | ||
| 210 | } | ||
| 211 | |||
| 212 | const resolveToolsFromMap = (input: { | ||
| 213 | tools: ExecutorToolMap; | ||
| 214 | sourceKey?: string; | ||
| 215 | }): ResolvedExecutorTool[] => { | ||
| 216 | const defaultSourceKey = input.sourceKey ?? "in_memory.ai_sdk"; | ||
| 217 | |||
| 218 | return Object.entries(input.tools) | ||
| 219 | .sort(([left], [right]) => left.localeCompare(right)) | ||
| 220 | .map(([path, value]) => { | ||
| 221 | const entry = isExecutorToolDefinition(value) | ||
| 222 | ? value | ||
| 223 | : { tool: value }; | ||
| 224 | const metadata = entry.metadata | ||
| 225 | ? { | ||
| 226 | sourceKey: defaultSourceKey, | ||
| 227 | ...entry.metadata, | ||
| 228 | } | ||
| 229 | : { sourceKey: defaultSourceKey }; | ||
| 230 | |||
| 231 | return { | ||
| 232 | path: asToolPath(path), | ||
| 233 | tool: entry.tool, | ||
| 234 | metadata, | ||
| 235 | } satisfies ResolvedExecutorTool; | ||
| 236 | }); | ||
| 237 | }; | ||
| 238 | |||
| 239 | export function toolDescriptorsFromTools(input: { | ||
| 240 | tools: ExecutorToolMap; | ||
| 241 | sourceKey?: string; | ||
| 242 | }): ToolDescriptor[] { | ||
| 243 | const resolvedTools = resolveToolsFromMap({ | ||
| 244 | tools: input.tools, | ||
| 245 | sourceKey: input.sourceKey, | ||
| 246 | }); | ||
| 247 | |||
| 248 | return resolvedTools.map((entry) => { | ||
| 249 | const metadata = entry.metadata; | ||
| 250 | const definition = entry.tool; | ||
| 251 | const inputSchemaJson = | ||
| 252 | metadata?.inputSchemaJson ?? | ||
| 253 | stringifySchema((definition as { inputSchema?: unknown }).inputSchema) ?? | ||
| 254 | stringifySchema((definition as { parameters?: unknown }).parameters); | ||
| 255 | const outputSchemaJson = | ||
| 256 | metadata?.outputSchemaJson ?? | ||
| 257 | stringifySchema((definition as { outputSchema?: unknown }).outputSchema); | ||
| 258 | |||
| 259 | return { | ||
| 260 | path: entry.path, | ||
| 261 | sourceKey: metadata?.sourceKey ?? "in_memory.ai_sdk", | ||
| 262 | description: (definition as { description?: string }).description, | ||
| 263 | interaction: metadata?.interaction, | ||
| 264 | inputHint: | ||
| 265 | metadata?.inputHint ?? inferHintFromSchemaJson(inputSchemaJson, "input"), | ||
| 266 | outputHint: | ||
| 267 | metadata?.outputHint ?? inferHintFromSchemaJson(outputSchemaJson, "output"), | ||
| 268 | inputSchemaJson, | ||
| 269 | outputSchemaJson, | ||
| 270 | refHintKeys: metadata?.refHintKeys, | ||
| 271 | } satisfies ToolDescriptor; | ||
| 272 | }); | ||
| 273 | } | ||
| 274 | |||
| 275 | export function createStaticDiscoveryFromTools(input: { | ||
| 276 | tools: ExecutorToolMap; | ||
| 277 | sourceKey?: string; | ||
| 278 | }): { | ||
| 279 | preloadedTools: ToolDescriptor[]; | ||
| 280 | primitives: DiscoveryPrimitives; | ||
| 281 | executeDescription: string; | ||
| 282 | } { | ||
| 283 | const preloadedTools = toolDescriptorsFromTools({ | ||
| 284 | tools: input.tools, | ||
| 285 | sourceKey: input.sourceKey, | ||
| 286 | }); | ||
| 287 | const primitives = createDiscoveryPrimitives({}); | ||
| 288 | |||
| 289 | return { | ||
| 290 | preloadedTools, | ||
| 291 | primitives, | ||
| 292 | executeDescription: buildExecuteDescription({ | ||
| 293 | preloadedTools, | ||
| 294 | primitives, | ||
| 295 | }), | ||
| 296 | }; | ||
| 297 | } | ||
| 298 | |||
| 299 | export function createDynamicDiscovery(input: { | ||
| 300 | directory: ToolDirectory; | ||
| 301 | search?: SearchProvider; | ||
| 302 | preloadedTools?: readonly ToolDescriptor[]; | ||
| 303 | }): { | ||
| 304 | preloadedTools: readonly ToolDescriptor[]; | ||
| 305 | primitives: DiscoveryPrimitives; | ||
| 306 | executeDescription: string; | ||
| 307 | } { | ||
| 308 | const preloadedTools = input.preloadedTools ?? []; | ||
| 309 | const primitives = createDiscoveryPrimitives({ | ||
| 310 | directory: input.directory, | ||
| 311 | search: input.search, | ||
| 312 | }); | ||
| 313 | |||
| 314 | return { | ||
| 315 | preloadedTools, | ||
| 316 | primitives, | ||
| 317 | executeDescription: buildExecuteDescription({ | ||
| 318 | preloadedTools, | ||
| 319 | primitives, | ||
| 320 | }), | ||
| 321 | }; | ||
| 322 | } | ||
| 323 | |||
| 324 | export function createDiscoveryPrimitives(input: { | ||
| 325 | directory?: ToolDirectory; | ||
| 326 | search?: SearchProvider; | ||
| 327 | }): DiscoveryPrimitives { | ||
| 328 | const { directory, search } = input; | ||
| 329 | |||
| 330 | const catalog: CatalogPrimitive | undefined = directory | ||
| 331 | ? { | ||
| 332 | namespaces: async ({ limit = 200 }) => ({ | ||
| 333 | namespaces: await directory.listNamespaces({ limit }), | ||
| 334 | }), | ||
| 335 | tools: async ({ namespace, query, limit = 200 }) => ({ | ||
| 336 | results: await directory.listTools({ namespace, query, limit }), | ||
| 337 | }), | ||
| 338 | } | ||
| 339 | : undefined; | ||
| 340 | |||
| 341 | const describe: DescribePrimitive | undefined = directory | ||
| 342 | ? { | ||
| 343 | tool: async ({ path, includeSchemas = false }) => | ||
| 344 | directory.getByPath({ path, includeSchemas }), | ||
| 345 | } | ||
| 346 | : undefined; | ||
| 347 | |||
| 348 | const discover: DiscoverPrimitive | undefined = | ||
| 349 | directory && search | ||
| 350 | ? { | ||
| 351 | run: async ({ query, limit = 12, includeSchemas = false }) => { | ||
| 352 | const hits = await search.search({ query, limit }); | ||
| 353 | const descriptors = await directory.getByPaths({ | ||
| 354 | paths: hits.map((hit) => hit.path), | ||
| 355 | includeSchemas, | ||
| 356 | }); | ||
| 357 | |||
| 358 | const byPath = new Map( | ||
| 359 | descriptors.map((descriptor) => [descriptor.path, descriptor]), | ||
| 360 | ); | ||
| 361 | const hydrated = hits | ||
| 362 | .map((hit) => { | ||
| 363 | const descriptor = byPath.get(hit.path); | ||
| 364 | if (!descriptor) { | ||
| 365 | return null; | ||
| 366 | } | ||
| 367 | |||
| 368 | return { | ||
| 369 | path: descriptor.path, | ||
| 370 | score: hit.score, | ||
| 371 | description: descriptor.description, | ||
| 372 | interaction: descriptor.interaction ?? "auto", | ||
| 373 | inputHint: descriptor.inputHint, | ||
| 374 | outputHint: descriptor.outputHint, | ||
| 375 | ...(includeSchemas | ||
| 376 | ? { | ||
| 377 | inputSchemaJson: descriptor.inputSchemaJson, | ||
| 378 | outputSchemaJson: descriptor.outputSchemaJson, | ||
| 379 | refHintKeys: descriptor.refHintKeys, | ||
| 380 | } | ||
| 381 | : {}), | ||
| 382 | }; | ||
| 383 | }) | ||
| 384 | .filter(Boolean) as Array< | ||
| 385 | Record<string, unknown> & { path: ToolPath; score: number } | ||
| 386 | >; | ||
| 387 | |||
| 388 | return { | ||
| 389 | bestPath: hydrated[0]?.path ?? null, | ||
| 390 | results: hydrated, | ||
| 391 | total: hydrated.length, | ||
| 392 | }; | ||
| 393 | }, | ||
| 394 | } | ||
| 395 | : undefined; | ||
| 396 | |||
| 397 | return { catalog, describe, discover }; | ||
| 398 | } | ||
| 399 | |||
| 400 | export function buildExecuteDescription(input: { | ||
| 401 | preloadedTools: readonly ToolDescriptor[]; | ||
| 402 | primitives: DiscoveryPrimitives; | ||
| 403 | }): string { | ||
| 404 | const { preloadedTools, primitives } = input; | ||
| 405 | const hasCatalog = Boolean(primitives.catalog); | ||
| 406 | const hasDescribe = Boolean(primitives.describe); | ||
| 407 | const hasDiscover = Boolean(primitives.discover); | ||
| 408 | |||
| 409 | if (!hasCatalog && !hasDescribe && !hasDiscover) { | ||
| 410 | return [ | ||
| 411 | "Execute TypeScript in sandbox; call tools directly.", | ||
| 412 | "Available tool paths:", | ||
| 413 | ...preloadedTools.map((tool) => `- ${tool.path}`), | ||
| 414 | "Do not use fetch; use tools.* only.", | ||
| 415 | ].join("\n"); | ||
| 416 | } | ||
| 417 | |||
| 418 | return [ | ||
| 419 | "Execute TypeScript in sandbox; call tools via helper workflow.", | ||
| 420 | "Workflow:", | ||
| 421 | hasCatalog | ||
| 422 | ? "1) const namespaces = await tools.catalog.namespaces({ limit: 200 });" | ||
| 423 | : "", | ||
| 424 | hasDiscover | ||
| 425 | ? '2) const matches = await tools.discover.run({ query: "<intent>", limit: 12 });' | ||
| 426 | : '2) const toolsList = await tools.catalog.tools({ query: "<intent>", limit: 50 });', | ||
| 427 | hasDescribe | ||
| 428 | ? "3) const details = await tools.describe.tool({ path, includeSchemas: true });" | ||
| 429 | : "", | ||
| 430 | "4) Call selected tools.<path>(input).", | ||
| 431 | "Do not use fetch; use tools.* only.", | ||
| 432 | ] | ||
| 433 | .filter(Boolean) | ||
| 434 | .join("\n"); | ||
| 435 | } | ||
| @@ -0,0 +1,15 @@ | |||
| 1 | { | ||
| 2 | "compilerOptions": { | ||
| 3 | "target": "ES2022", | ||
| 4 | "module": "ESNext", | ||
| 5 | "moduleResolution": "Bundler", | ||
| 6 | "strict": true, | ||
| 7 | "skipLibCheck": true, | ||
| 8 | "noEmit": true, | ||
| 9 | "lib": ["ES2022"], | ||
| 10 | "types": ["bun-types", "node"] | ||
| 11 | }, | ||
| 12 | "include": [ | ||
| 13 | "src/**/*.ts" | ||
| 14 | ] | ||
| 15 | } | ||
| @@ -0,0 +1,18 @@ | |||
| 1 | { | ||
| 2 | "name": "@executor-v3/engine", | ||
| 3 | "private": true, | ||
| 4 | "type": "module", | ||
| 5 | "exports": { | ||
| 6 | ".": "./src/index.ts" | ||
| 7 | }, | ||
| 8 | "scripts": { | ||
| 9 | "typecheck": "bunx tsc --noEmit -p tsconfig.json" | ||
| 10 | }, | ||
| 11 | "dependencies": { | ||
| 12 | "effect": "^3.19.8" | ||
| 13 | }, | ||
| 14 | "devDependencies": { | ||
| 15 | "@types/node": "^24.3.1", | ||
| 16 | "bun-types": "^1.2.22" | ||
| 17 | } | ||
| 18 | } | ||
| @@ -0,0 +1,13 @@ | |||
| 1 | export { | ||
| 2 | RuntimeAdapterError, | ||
| 3 | RuntimeAdapterRegistryError, | ||
| 4 | RuntimeAdapterRegistryService, | ||
| 5 | RuntimeAdapterRegistryLive, | ||
| 6 | makeRuntimeAdapterRegistry, | ||
| 7 | type RuntimeAdapter, | ||
| 8 | type RuntimeAdapterKind, | ||
| 9 | type RuntimeAdapterRegistry, | ||
| 10 | type RuntimeExecuteError, | ||
| 11 | type RuntimeExecuteInput, | ||
| 12 | type RuntimeToolCallService, | ||
| 13 | } from "./runtime-adapters"; | ||
| @@ -0,0 +1,149 @@ | |||
| 1 | import * as Context from "effect/Context"; | ||
| 2 | import * as Data from "effect/Data"; | ||
| 3 | import * as Effect from "effect/Effect"; | ||
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | |||
| 6 | export type RuntimeAdapterKind = string; | ||
| 7 | |||
| 8 | export type RuntimeToolCallService = { | ||
| 9 | callTool: (input: { | ||
| 10 | runId: string; | ||
| 11 | callId: string; | ||
| 12 | toolPath: string; | ||
| 13 | input?: Record<string, unknown>; | ||
| 14 | credentialContext?: unknown; | ||
| 15 | }) => Effect.Effect<unknown, RuntimeAdapterError>; | ||
| 16 | }; | ||
| 17 | |||
| 18 | export type RuntimeExecuteInput = { | ||
| 19 | runId: string; | ||
| 20 | code: string; | ||
| 21 | toolCallService?: RuntimeToolCallService; | ||
| 22 | timeoutMs?: number; | ||
| 23 | }; | ||
| 24 | |||
| 25 | export class RuntimeAdapterError extends Data.TaggedError("RuntimeAdapterError")<{ | ||
| 26 | operation: string; | ||
| 27 | runtimeKind: RuntimeAdapterKind; | ||
| 28 | message: string; | ||
| 29 | details: string | null; | ||
| 30 | }> {} | ||
| 31 | |||
| 32 | export type RuntimeExecuteError = RuntimeAdapterError; | ||
| 33 | |||
| 34 | export interface RuntimeAdapter { | ||
| 35 | readonly kind: RuntimeAdapterKind; | ||
| 36 | readonly isAvailable: () => Effect.Effect<boolean>; | ||
| 37 | readonly execute: ( | ||
| 38 | input: RuntimeExecuteInput, | ||
| 39 | ) => Effect.Effect<unknown, RuntimeExecuteError>; | ||
| 40 | } | ||
| 41 | |||
| 42 | export class RuntimeAdapterRegistryError extends Data.TaggedError( | ||
| 43 | "RuntimeAdapterRegistryError", | ||
| 44 | )<{ | ||
| 45 | operation: string; | ||
| 46 | runtimeKind: RuntimeAdapterKind; | ||
| 47 | message: string; | ||
| 48 | }> {} | ||
| 49 | |||
| 50 | export interface RuntimeAdapterRegistry { | ||
| 51 | readonly register: ( | ||
| 52 | adapter: RuntimeAdapter, | ||
| 53 | ) => Effect.Effect<void, RuntimeAdapterRegistryError>; | ||
| 54 | |||
| 55 | readonly registerAll: ( | ||
| 56 | adapters: ReadonlyArray<RuntimeAdapter>, | ||
| 57 | ) => Effect.Effect<void, RuntimeAdapterRegistryError>; | ||
| 58 | |||
| 59 | readonly get: ( | ||
| 60 | runtimeKind: RuntimeAdapterKind, | ||
| 61 | ) => Effect.Effect<RuntimeAdapter, RuntimeAdapterRegistryError>; | ||
| 62 | |||
| 63 | readonly execute: ( | ||
| 64 | input: RuntimeExecuteInput & { runtimeKind: RuntimeAdapterKind }, | ||
| 65 | ) => Effect.Effect<unknown, RuntimeExecuteError | RuntimeAdapterRegistryError>; | ||
| 66 | } | ||
| 67 | |||
| 68 | export class RuntimeAdapterRegistryService extends Context.Tag( | ||
| 69 | "@executor-v3/engine/RuntimeAdapterRegistryService", | ||
| 70 | )<RuntimeAdapterRegistryService, RuntimeAdapterRegistry>() {} | ||
| 71 | |||
| 72 | const duplicateRuntimeAdapterError = ( | ||
| 73 | runtimeKind: RuntimeAdapterKind, | ||
| 74 | ): RuntimeAdapterRegistryError => | ||
| 75 | new RuntimeAdapterRegistryError({ | ||
| 76 | operation: "register", | ||
| 77 | runtimeKind, | ||
| 78 | message: `Runtime adapter already registered: ${runtimeKind}`, | ||
| 79 | }); | ||
| 80 | |||
| 81 | const runtimeAdapterNotFoundError = ( | ||
| 82 | operation: string, | ||
| 83 | runtimeKind: RuntimeAdapterKind, | ||
| 84 | ): RuntimeAdapterRegistryError => | ||
| 85 | new RuntimeAdapterRegistryError({ | ||
| 86 | operation, | ||
| 87 | runtimeKind, | ||
| 88 | message: `No runtime adapter registered for kind: ${runtimeKind}`, | ||
| 89 | }); | ||
| 90 | |||
| 91 | export const makeRuntimeAdapterRegistry = ( | ||
| 92 | initialAdapters: ReadonlyArray<RuntimeAdapter> = [], | ||
| 93 | ): RuntimeAdapterRegistry => { | ||
| 94 | const adaptersByKind = new Map<RuntimeAdapterKind, RuntimeAdapter>(); | ||
| 95 | |||
| 96 | const register = ( | ||
| 97 | adapter: RuntimeAdapter, | ||
| 98 | ): Effect.Effect<void, RuntimeAdapterRegistryError> => | ||
| 99 | Effect.gen(function* () { | ||
| 100 | if (adaptersByKind.has(adapter.kind)) { | ||
| 101 | return yield* duplicateRuntimeAdapterError(adapter.kind); | ||
| 102 | } | ||
| 103 | |||
| 104 | adaptersByKind.set(adapter.kind, adapter); | ||
| 105 | }); | ||
| 106 | |||
| 107 | const registerAll = ( | ||
| 108 | adapters: ReadonlyArray<RuntimeAdapter>, | ||
| 109 | ): Effect.Effect<void, RuntimeAdapterRegistryError> => | ||
| 110 | Effect.forEach(adapters, register, { discard: true }); | ||
| 111 | |||
| 112 | const get = ( | ||
| 113 | runtimeKind: RuntimeAdapterKind, | ||
| 114 | ): Effect.Effect<RuntimeAdapter, RuntimeAdapterRegistryError> => | ||
| 115 | Effect.gen(function* () { | ||
| 116 | const adapter = adaptersByKind.get(runtimeKind); | ||
| 117 | if (!adapter) { | ||
| 118 | return yield* runtimeAdapterNotFoundError("get", runtimeKind); | ||
| 119 | } | ||
| 120 | |||
| 121 | return adapter; | ||
| 122 | }); | ||
| 123 | |||
| 124 | const execute = ( | ||
| 125 | input: RuntimeExecuteInput & { runtimeKind: RuntimeAdapterKind }, | ||
| 126 | ): Effect.Effect<unknown, RuntimeExecuteError | RuntimeAdapterRegistryError> => | ||
| 127 | Effect.gen(function* () { | ||
| 128 | const adapter = yield* get(input.runtimeKind); | ||
| 129 | return yield* adapter.execute(input); | ||
| 130 | }); | ||
| 131 | |||
| 132 | for (const adapter of initialAdapters) { | ||
| 133 | if (!adaptersByKind.has(adapter.kind)) { | ||
| 134 | adaptersByKind.set(adapter.kind, adapter); | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | return { | ||
| 139 | register, | ||
| 140 | registerAll, | ||
| 141 | get, | ||
| 142 | execute, | ||
| 143 | }; | ||
| 144 | }; | ||
| 145 | |||
| 146 | export const RuntimeAdapterRegistryLive = ( | ||
| 147 | initialAdapters: ReadonlyArray<RuntimeAdapter> = [], | ||
| 148 | ): Layer.Layer<RuntimeAdapterRegistryService> => | ||
| 149 | Layer.succeed(RuntimeAdapterRegistryService, makeRuntimeAdapterRegistry(initialAdapters)); | ||
| @@ -0,0 +1,13 @@ | |||
| 1 | { | ||
| 2 | "compilerOptions": { | ||
| 3 | "target": "ES2022", | ||
| 4 | "module": "ESNext", | ||
| 5 | "moduleResolution": "Bundler", | ||
| 6 | "strict": true, | ||
| 7 | "skipLibCheck": true, | ||
| 8 | "noEmit": true, | ||
| 9 | "lib": ["ES2022"], | ||
| 10 | "types": ["bun-types", "node"] | ||
| 11 | }, | ||
| 12 | "include": ["src/**/*.ts"] | ||
| 13 | } | ||
| @@ -0,0 +1,19 @@ | |||
| 1 | { | ||
| 2 | "name": "@executor-v3/runtime-local-inproc", | ||
| 3 | "private": true, | ||
| 4 | "type": "module", | ||
| 5 | "exports": { | ||
| 6 | ".": "./src/index.ts" | ||
| 7 | }, | ||
| 8 | "scripts": { | ||
| 9 | "typecheck": "bunx tsc --noEmit -p tsconfig.json" | ||
| 10 | }, | ||
| 11 | "dependencies": { | ||
| 12 | "@executor-v3/engine": "workspace:*", | ||
| 13 | "effect": "^3.19.8" | ||
| 14 | }, | ||
| 15 | "devDependencies": { | ||
| 16 | "@types/node": "^24.3.1", | ||
| 17 | "bun-types": "^1.2.22" | ||
| 18 | } | ||
| 19 | } | ||
| @@ -0,0 +1,213 @@ | |||
| 1 | import * as Effect from "effect/Effect"; | ||
| 2 | import * as Runtime from "effect/Runtime"; | ||
| 3 | import * as Exit from "effect/Exit"; | ||
| 4 | import * as Cause from "effect/Cause"; | ||
| 5 | import * as Option from "effect/Option"; | ||
| 6 | |||
| 7 | import { | ||
| 8 | RuntimeAdapterError, | ||
| 9 | type RuntimeAdapter, | ||
| 10 | type RuntimeToolCallService, | ||
| 11 | } from "@executor-v3/engine"; | ||
| 12 | |||
| 13 | const runtimeKind = "local-inproc"; | ||
| 14 | const runCallResultCache = new Map<string, unknown>(); | ||
| 15 | |||
| 16 | export type ExecuteJavaScriptInput = { | ||
| 17 | runId: string; | ||
| 18 | code: string; | ||
| 19 | toolCallService?: RuntimeToolCallService; | ||
| 20 | }; | ||
| 21 | |||
| 22 | const runtimeError = ( | ||
| 23 | operation: string, | ||
| 24 | message: string, | ||
| 25 | details: string | null, | ||
| 26 | ): RuntimeAdapterError => | ||
| 27 | new RuntimeAdapterError({ | ||
| 28 | operation, | ||
| 29 | runtimeKind, | ||
| 30 | message, | ||
| 31 | details, | ||
| 32 | }); | ||
| 33 | |||
| 34 | const missingToolCallServiceError = (toolPath: string): RuntimeAdapterError => | ||
| 35 | runtimeError( | ||
| 36 | "call_tool", | ||
| 37 | `No tool call service configured for tool path: ${toolPath}`, | ||
| 38 | null, | ||
| 39 | ); | ||
| 40 | |||
| 41 | const isRuntimeAdapterErrorLike = ( | ||
| 42 | cause: unknown, | ||
| 43 | ): cause is { | ||
| 44 | operation: string; | ||
| 45 | runtimeKind: string; | ||
| 46 | details: string | null; | ||
| 47 | _tag?: string; | ||
| 48 | } => { | ||
| 49 | if (!cause || typeof cause !== "object") { | ||
| 50 | return false; | ||
| 51 | } | ||
| 52 | |||
| 53 | const record = cause as Record<string, unknown>; | ||
| 54 | return (record._tag === "RuntimeAdapterError" || record.name === "RuntimeAdapterError") | ||
| 55 | && typeof record.operation === "string" | ||
| 56 | && typeof record.runtimeKind === "string" | ||
| 57 | && (typeof record.details === "string" || record.details === null || record.details === undefined); | ||
| 58 | }; | ||
| 59 | |||
| 60 | const toExecutionError = (cause: unknown): RuntimeAdapterError => | ||
| 61 | cause instanceof RuntimeAdapterError | ||
| 62 | ? cause | ||
| 63 | : isRuntimeAdapterErrorLike(cause) | ||
| 64 | ? new RuntimeAdapterError({ | ||
| 65 | operation: cause.operation, | ||
| 66 | runtimeKind: cause.runtimeKind, | ||
| 67 | message: cause instanceof Error && cause.message.length > 0 | ||
| 68 | ? cause.message | ||
| 69 | : "Runtime adapter error", | ||
| 70 | details: cause.details ?? null, | ||
| 71 | }) | ||
| 72 | : runtimeError( | ||
| 73 | "execute", | ||
| 74 | "JavaScript execution failed", | ||
| 75 | cause instanceof Error ? cause.stack ?? cause.message : String(cause), | ||
| 76 | ); | ||
| 77 | |||
| 78 | const normalizeToolInput = (args: unknown): Record<string, unknown> | undefined => { | ||
| 79 | if (!args || typeof args !== "object" || Array.isArray(args)) { | ||
| 80 | return undefined; | ||
| 81 | } | ||
| 82 | |||
| 83 | return args as Record<string, unknown>; | ||
| 84 | }; | ||
| 85 | |||
| 86 | const invokeTool = ( | ||
| 87 | runId: string, | ||
| 88 | callId: string, | ||
| 89 | toolPath: string, | ||
| 90 | args: unknown, | ||
| 91 | toolCallService: RuntimeToolCallService | undefined, | ||
| 92 | ): Effect.Effect<unknown, RuntimeAdapterError> => | ||
| 93 | Effect.gen(function* () { | ||
| 94 | if (!toolCallService) { | ||
| 95 | return yield* missingToolCallServiceError(toolPath); | ||
| 96 | } | ||
| 97 | |||
| 98 | const cacheKey = `${runId}:${callId}`; | ||
| 99 | if (runCallResultCache.has(cacheKey)) { | ||
| 100 | return runCallResultCache.get(cacheKey); | ||
| 101 | } | ||
| 102 | |||
| 103 | const result = yield* toolCallService.callTool({ | ||
| 104 | runId, | ||
| 105 | callId, | ||
| 106 | toolPath, | ||
| 107 | input: normalizeToolInput(args), | ||
| 108 | }); | ||
| 109 | |||
| 110 | runCallResultCache.set(cacheKey, result); | ||
| 111 | return result; | ||
| 112 | }); | ||
| 113 | |||
| 114 | const nextDeterministicCallId = (counter: { value: number }): string => { | ||
| 115 | const callId = `call_${String(counter.value).padStart(6, "0")}`; | ||
| 116 | counter.value += 1; | ||
| 117 | return callId; | ||
| 118 | }; | ||
| 119 | |||
| 120 | const runPromiseWithTypedError = ( | ||
| 121 | runtime: Runtime.Runtime<never>, | ||
| 122 | ): (<A, E>(effect: Effect.Effect<A, E>) => Promise<A>) => | ||
| 123 | async <A, E>(effect: Effect.Effect<A, E>): Promise<A> => { | ||
| 124 | const exit = await Runtime.runPromiseExit(runtime)(effect); | ||
| 125 | if (Exit.isSuccess(exit)) { | ||
| 126 | return exit.value; | ||
| 127 | } | ||
| 128 | |||
| 129 | const failure = Cause.failureOption(exit.cause); | ||
| 130 | if (Option.isSome(failure)) { | ||
| 131 | throw failure.value; | ||
| 132 | } | ||
| 133 | |||
| 134 | throw new Error(Cause.pretty(exit.cause)); | ||
| 135 | }; | ||
| 136 | |||
| 137 | const createToolsProxy = ( | ||
| 138 | runId: string, | ||
| 139 | runPromise: <A, E>(effect: Effect.Effect<A, E>) => Promise<A>, | ||
| 140 | toolCallService: RuntimeToolCallService | undefined, | ||
| 141 | callCounter: { value: number }, | ||
| 142 | path: ReadonlyArray<string> = [], | ||
| 143 | ): unknown => { | ||
| 144 | const callable = () => undefined; | ||
| 145 | |||
| 146 | return new Proxy(callable, { | ||
| 147 | get(_target, prop) { | ||
| 148 | if (prop === "then") { | ||
| 149 | return undefined; | ||
| 150 | } | ||
| 151 | |||
| 152 | if (typeof prop !== "string") { | ||
| 153 | return undefined; | ||
| 154 | } | ||
| 155 | |||
| 156 | return createToolsProxy(runId, runPromise, toolCallService, callCounter, [...path, prop]); | ||
| 157 | }, | ||
| 158 | apply(_target, _thisArg, args) { | ||
| 159 | const toolPath = path.join("."); | ||
| 160 | if (!toolPath) { | ||
| 161 | throw new Error("Tool path missing in invocation"); | ||
| 162 | } | ||
| 163 | |||
| 164 | const toolArgs = args.length > 0 ? args[0] : undefined; | ||
| 165 | const callId = nextDeterministicCallId(callCounter); | ||
| 166 | return runPromise(invokeTool(runId, callId, toolPath, toolArgs, toolCallService)); | ||
| 167 | }, | ||
| 168 | }); | ||
| 169 | }; | ||
| 170 | |||
| 171 | const runJavaScript = ( | ||
| 172 | code: string, | ||
| 173 | tools: unknown, | ||
| 174 | ): Effect.Effect<unknown, RuntimeAdapterError> => | ||
| 175 | Effect.tryPromise({ | ||
| 176 | try: async () => { | ||
| 177 | const execute = new Function( | ||
| 178 | "tools", | ||
| 179 | `"use strict"; return (async () => {\n${code}\n})();`, | ||
| 180 | ) as (tools: unknown) => Promise<unknown>; | ||
| 181 | |||
| 182 | return await execute(tools); | ||
| 183 | }, | ||
| 184 | catch: toExecutionError, | ||
| 185 | }); | ||
| 186 | |||
| 187 | export const executeJavaScriptWithTools = ( | ||
| 188 | input: ExecuteJavaScriptInput, | ||
| 189 | ): Effect.Effect<unknown, RuntimeAdapterError> => | ||
| 190 | Effect.gen(function* () { | ||
| 191 | const runtime = yield* Effect.runtime<never>(); | ||
| 192 | const runPromise = runPromiseWithTypedError(runtime); | ||
| 193 | const callCounter = { value: 0 }; | ||
| 194 | const toolsProxy = createToolsProxy( | ||
| 195 | input.runId, | ||
| 196 | runPromise, | ||
| 197 | input.toolCallService, | ||
| 198 | callCounter, | ||
| 199 | ); | ||
| 200 | |||
| 201 | return yield* runJavaScript(input.code, toolsProxy); | ||
| 202 | }); | ||
| 203 | |||
| 204 | export const makeLocalInProcessRuntimeAdapter = (): RuntimeAdapter => ({ | ||
| 205 | kind: runtimeKind, | ||
| 206 | isAvailable: () => Effect.succeed(true), | ||
| 207 | execute: (input) => | ||
| 208 | executeJavaScriptWithTools({ | ||
| 209 | runId: input.runId, | ||
| 210 | code: input.code, | ||
| 211 | toolCallService: input.toolCallService, | ||
| 212 | }), | ||
| 213 | }); | ||
| @@ -0,0 +1,13 @@ | |||
| 1 | { | ||
| 2 | "compilerOptions": { | ||
| 3 | "target": "ES2022", | ||
| 4 | "module": "ESNext", | ||
| 5 | "moduleResolution": "Bundler", | ||
| 6 | "strict": true, | ||
| 7 | "skipLibCheck": true, | ||
| 8 | "noEmit": true, | ||
| 9 | "lib": ["ES2022"], | ||
| 10 | "types": ["bun-types", "node"] | ||
| 11 | }, | ||
| 12 | "include": ["src/**/*.ts"] | ||
| 13 | } | ||