History

decent ai sdk

Rhys Sullivan committed 8 months ago

Commit 46a39bf1 parent 1aa43db17 files · +1090 −0Browse files

Unified · Split

17 files
  1. AGENTS.md +0 −0
  2. bun.lock +54 −0
  3. package.json +15 −0
  4. packages/ai-sdk-adapter/package.json +23 −0
  5. packages/ai-sdk-adapter/src/ai.ts +12 −0
  6. packages/ai-sdk-adapter/src/example-dynamic.ts +54 −0
  7. packages/ai-sdk-adapter/src/example-static.ts +42 −0
  8. packages/ai-sdk-adapter/src/example.ts +2 −0
  9. packages/ai-sdk-adapter/src/index.ts +435 −0
  10. packages/ai-sdk-adapter/tsconfig.json +15 −0
  11. packages/engine/package.json +18 −0
  12. packages/engine/src/index.ts +13 −0
  13. packages/engine/src/runtime-adapters.ts +149 −0
  14. packages/engine/tsconfig.json +13 −0
  15. packages/runtime-local-inproc/package.json +19 −0
  16. packages/runtime-local-inproc/src/index.ts +213 −0
  17. packages/runtime-local-inproc/tsconfig.json +13 −0
AGENTS.mdrenamed from legacy2/AGENTS.md+0−0

File renamed without changes.

bun.lock+54−0
@@ -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}
package.json+15−0
@@ -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}
packages/ai-sdk-adapter/package.json+23−0
@@ -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}
packages/ai-sdk-adapter/src/ai.ts+12−0
@@ -0,0 +1,12 @@
1export {
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";
packages/ai-sdk-adapter/src/example-dynamic.ts+54−0
@@ -0,0 +1,54 @@
1import {
2 createDynamicDiscovery,
3 type SearchProvider,
4 type ToolDirectory,
5 type ToolPath,
6} from "./index";
7
8const asToolPath = (value: string): ToolPath => value as ToolPath;
9
10const 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
40const 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
49export const dynamicDemo = createDynamicDiscovery({
50 directory,
51 search,
52});
53
54console.log(dynamicDemo)
packages/ai-sdk-adapter/src/example-static.ts+42−0
@@ -0,0 +1,42 @@
1import { convertFileListToFileUIParts, tool } from "ai";
2import { z } from "zod";
3
4import { createStaticDiscoveryFromTools, toExecutorTool } from "./index";
5
6const 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
14const 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
29const wrappedTools = {
30 "github.issues.list": listIssues,
31 "github.issues.create": toExecutorTool({
32 tool: createIssue,
33 metadata: {
34 interaction: "required",
35 },
36 }),
37};
38
39export const staticDemo = createStaticDiscoveryFromTools({
40 tools: wrappedTools,
41 sourceKey: "api.github",
42});
packages/ai-sdk-adapter/src/example.ts+2−0
@@ -0,0 +1,2 @@
1export { staticDemo } from "./example-static";
2export { dynamicDemo } from "./example-dynamic";
packages/ai-sdk-adapter/src/index.ts+435−0
@@ -0,0 +1,435 @@
1import type { ToolSet } from "ai";
2
3export type ToolPath = string & { readonly __toolPath: unique symbol };
4
5export 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
17export 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
27export type AiSdkToolMap = ToolSet;
28
29export type ExecutorToolDefinition = {
30 tool: ToolSet[string];
31 metadata?: ExecutorAiSdkMetadata;
32};
33
34export type ExecutorToolInput = ToolSet[string] | ExecutorToolDefinition;
35
36export type ExecutorToolMap = Record<string, ExecutorToolInput>;
37
38type ResolvedExecutorTool = {
39 path: ToolPath;
40 tool: ToolSet[string];
41 metadata?: ExecutorAiSdkMetadata;
42};
43
44export type SearchHit = {
45 path: ToolPath;
46 score: number;
47};
48
49export interface SearchProvider {
50 search(input: {
51 query: string;
52 limit: number;
53 }): Promise<readonly SearchHit[]>;
54}
55
56export 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
78export 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
89export type DescribePrimitive = {
90 tool(input: {
91 path: ToolPath;
92 includeSchemas?: boolean;
93 }): Promise<ToolDescriptor | null>;
94};
95
96export 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
111export type DiscoveryPrimitives = {
112 catalog?: CatalogPrimitive;
113 describe?: DescribePrimitive;
114 discover?: DiscoverPrimitive;
115};
116
117const asToolPath = (value: string): ToolPath => value as ToolPath;
118
119export 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
129export const toExecutorTool = wrapTool;
130
131const isExecutorToolDefinition = (
132 value: ExecutorToolInput,
133): value is ExecutorToolDefinition =>
134 typeof value === "object" && value !== null && "tool" in value;
135
136const 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
148const 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
193export 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
212const 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
239export 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
275export 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
299export 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
324export 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
400export 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}
packages/ai-sdk-adapter/tsconfig.json+15−0
@@ -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}
packages/engine/package.json+18−0
@@ -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}
packages/engine/src/index.ts+13−0
@@ -0,0 +1,13 @@
1export {
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";
packages/engine/src/runtime-adapters.ts+149−0
@@ -0,0 +1,149 @@
1import * as Context from "effect/Context";
2import * as Data from "effect/Data";
3import * as Effect from "effect/Effect";
4import * as Layer from "effect/Layer";
5
6export type RuntimeAdapterKind = string;
7
8export 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
18export type RuntimeExecuteInput = {
19 runId: string;
20 code: string;
21 toolCallService?: RuntimeToolCallService;
22 timeoutMs?: number;
23};
24
25export class RuntimeAdapterError extends Data.TaggedError("RuntimeAdapterError")<{
26 operation: string;
27 runtimeKind: RuntimeAdapterKind;
28 message: string;
29 details: string | null;
30}> {}
31
32export type RuntimeExecuteError = RuntimeAdapterError;
33
34export 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
42export class RuntimeAdapterRegistryError extends Data.TaggedError(
43 "RuntimeAdapterRegistryError",
44)<{
45 operation: string;
46 runtimeKind: RuntimeAdapterKind;
47 message: string;
48}> {}
49
50export 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
68export class RuntimeAdapterRegistryService extends Context.Tag(
69 "@executor-v3/engine/RuntimeAdapterRegistryService",
70)<RuntimeAdapterRegistryService, RuntimeAdapterRegistry>() {}
71
72const duplicateRuntimeAdapterError = (
73 runtimeKind: RuntimeAdapterKind,
74): RuntimeAdapterRegistryError =>
75 new RuntimeAdapterRegistryError({
76 operation: "register",
77 runtimeKind,
78 message: `Runtime adapter already registered: ${runtimeKind}`,
79 });
80
81const 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
91export 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
146export const RuntimeAdapterRegistryLive = (
147 initialAdapters: ReadonlyArray<RuntimeAdapter> = [],
148): Layer.Layer<RuntimeAdapterRegistryService> =>
149 Layer.succeed(RuntimeAdapterRegistryService, makeRuntimeAdapterRegistry(initialAdapters));
packages/engine/tsconfig.json+13−0
@@ -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}
packages/runtime-local-inproc/package.json+19−0
@@ -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}
packages/runtime-local-inproc/src/index.ts+213−0
@@ -0,0 +1,213 @@
1import * as Effect from "effect/Effect";
2import * as Runtime from "effect/Runtime";
3import * as Exit from "effect/Exit";
4import * as Cause from "effect/Cause";
5import * as Option from "effect/Option";
6
7import {
8 RuntimeAdapterError,
9 type RuntimeAdapter,
10 type RuntimeToolCallService,
11} from "@executor-v3/engine";
12
13const runtimeKind = "local-inproc";
14const runCallResultCache = new Map<string, unknown>();
15
16export type ExecuteJavaScriptInput = {
17 runId: string;
18 code: string;
19 toolCallService?: RuntimeToolCallService;
20};
21
22const 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
34const missingToolCallServiceError = (toolPath: string): RuntimeAdapterError =>
35 runtimeError(
36 "call_tool",
37 `No tool call service configured for tool path: ${toolPath}`,
38 null,
39 );
40
41const 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
60const 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
78const 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
86const 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
114const nextDeterministicCallId = (counter: { value: number }): string => {
115 const callId = `call_${String(counter.value).padStart(6, "0")}`;
116 counter.value += 1;
117 return callId;
118};
119
120const 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
137const 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
171const 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
187export 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
204export 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});
packages/runtime-local-inproc/tsconfig.json+13−0
@@ -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}