@yolk-sdk/codemode 0.1.0-canary.96 → 0.1.0-canary.98

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/dist/node.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"node.mjs","names":[],"sources":["../src/node.ts"],"sourcesContent":["import * as nodeModule from 'node:module'\nimport { Option, Predicate } from 'effect'\nimport * as Schema from 'effect/Schema'\nimport {\n CodemodeSandbox,\n type CodemodeResult,\n type CodemodeSandboxOptions,\n type CodemodeTool,\n type CodemodeWasmModule\n} from '@earendil-works/pi-codemode'\nimport type {\n CodeModeError,\n CodeModeExecuteOptions,\n CodeModeExecutionResult,\n CodeModeExecutor,\n CodeModeExecutorGlobal,\n CodeModeExecutorTool,\n CodeModeStoreWrites\n} from './executor.ts'\n\n/** Default cap of concurrent script executions per executor. */\nexport const defaultMaxConcurrentExecutions = 4\n\nexport type PiCodeModeExecutorOptions = {\n /**\n * Compiled `quickjs-wasi/quickjs.wasm` (see pi's `loadQuickJSWasm`). Default: the file of the\n * installed `quickjs-wasi` package, compiled once per process.\n */\n readonly wasm?: CodemodeWasmModule | Promise<CodemodeWasmModule>\n /**\n * Worker entry that imports `@earendil-works/pi-codemode/worker`. Default: pi's own worker file,\n * which exists on disk when the package is not bundled (Next `serverExternalPackages`).\n */\n readonly workerUrl?: string | URL\n /** Concurrent executions of this executor; more wait for a free slot. Default 4. */\n readonly maxConcurrentExecutions?: number\n}\n\ntype SlotOutcome = 'acquired' | 'aborted' | 'timeout'\n\ntype Waiter = (outcome: SlotOutcome) => void\n\n/** Abort- and deadline-aware counting semaphore for executions. */\nconst makeExecutionSlots = (max: number) => {\n let active = 0\n const waiting: Array<Waiter> = []\n\n const acquire = (signal: AbortSignal, timeoutMs: number): Promise<SlotOutcome> => {\n if (signal.aborted) return Promise.resolve('aborted')\n\n if (active < max) {\n active++\n\n return Promise.resolve('acquired')\n }\n\n return new Promise(resolve => {\n let timer: ReturnType<typeof setTimeout> | undefined\n\n const settle: Waiter = outcome => {\n const index = waiting.indexOf(settle)\n\n if (index !== -1) waiting.splice(index, 1)\n\n if (timer !== undefined) clearTimeout(timer)\n signal.removeEventListener('abort', onAbort)\n resolve(outcome)\n }\n\n const onAbort = () => settle('aborted')\n\n waiting.push(settle)\n signal.addEventListener('abort', onAbort, { once: true })\n\n if (Number.isFinite(timeoutMs)) {\n timer = setTimeout(() => settle('timeout'), Math.max(0, timeoutMs))\n }\n })\n }\n\n const release = () => {\n const next = waiting[0]\n\n if (next === undefined) {\n active--\n } else {\n // The slot passes straight to the next waiter.\n next('acquired')\n }\n }\n\n return { acquire, release }\n}\n\nconst wrapperPrefix = 'async function __yolkCodeMode() {\\n'\n\nconst wrapperSuffix = '\\n}'\n\nconst ErrorWithCode = Schema.Struct({ code: Schema.String })\n\nconst errorCode = (error: unknown) =>\n Option.getOrUndefined(Option.map(Schema.decodeUnknownOption(ErrorWithCode)(error), e => e.code))\n\nconst syntaxLocation = (error: Error) => {\n const match = /^[^\\n]*:(\\d+)\\n([^\\n]*)\\n([^\\n]*)\\n/.exec(error.stack ?? '')\n\n if (match === null) return ''\n\n const line = Number(match[1]) - 1\n\n return line >= 1 ? ` (line ${line})\\n${match[2] ?? ''}\\n${match[3] ?? ''}` : ''\n}\n\nconst typeStripMessage = (error: unknown) => {\n const message = error instanceof Error ? error.message : String(error)\n\n if (errorCode(error) === 'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX') {\n return `${message}. Write plain JavaScript: type annotations are stripped, but enums, namespaces, and parameter properties are not supported.`\n }\n\n const location = error instanceof Error ? syntaxLocation(error) : ''\n\n return `The script could not be parsed: ${message}${location}`\n}\n\ntype Stripped =\n | { readonly ok: true; readonly code: string }\n | { readonly ok: false; readonly error: CodeModeError }\n\n/**\n * Strips TypeScript annotations with Node's `stripTypeScriptTypes` (Node 22.13+; whitespace\n * replacement keeps line and column numbers). The script is wrapped in an async function so\n * top-level `return` and `await` parse. On Node without the API, code passes through unchanged.\n */\nconst stripTypes = (code: string): Stripped => {\n if (!Predicate.isFunction(nodeModule.stripTypeScriptTypes)) return { ok: true, code }\n\n try {\n const stripped = nodeModule.stripTypeScriptTypes(`${wrapperPrefix}${code}${wrapperSuffix}`, {\n mode: 'strip'\n })\n\n if (!stripped.startsWith(wrapperPrefix) || !stripped.endsWith(wrapperSuffix)) {\n return { ok: false, error: { kind: 'script', message: 'The script could not be parsed.' } }\n }\n\n return {\n ok: true,\n code: stripped.slice(wrapperPrefix.length, stripped.length - wrapperSuffix.length)\n }\n } catch (error) {\n return { ok: false, error: { kind: 'script', message: typeStripMessage(error) } }\n }\n}\n\nconst piTool = (tool: CodeModeExecutorTool): CodemodeTool => {\n const converted: CodemodeTool = {\n name: tool.name,\n execute: (args, context) => tool.execute(args, context)\n }\n\n if (tool.description !== undefined) converted.description = tool.description\n\n if (tool.inputSchema !== undefined) converted.inputSchema = tool.inputSchema\n\n if (tool.outputSchema !== undefined) converted.outputSchema = tool.outputSchema\n\n return converted\n}\n\nconst piGlobal = (global: CodeModeExecutorGlobal): CodemodeTool => {\n const converted = piTool(global)\n\n if (global.spread !== undefined) converted.spread = global.spread\n\n if (global.signature !== undefined) converted.signature = global.signature\n\n return converted\n}\n\nconst failure = (error: CodeModeError): CodeModeExecutionResult => ({\n ok: false,\n error,\n output: []\n})\n\nconst StoreWrites = Schema.Struct({\n set: Schema.Record(Schema.String, Schema.Json),\n delete: Schema.Array(Schema.String)\n})\n\nconst decodeStoreWrites = Schema.decodeUnknownOption(StoreWrites)\n\ntype MutableCodeModeError = {\n kind: CodeModeError['kind']\n message: string\n stack?: string\n}\n\ntype MutableExecutionResult = {\n ok: boolean\n value?: unknown\n error?: CodeModeError\n output: CodeModeExecutionResult['output']\n storeWrites?: CodeModeStoreWrites\n}\n\nconst fromPiResult = (result: CodemodeResult): CodeModeExecutionResult => {\n if (result.ok) {\n const storeWrites = decodeStoreWrites(result.storeWrites)\n\n if (Option.isNone(storeWrites)) {\n return failure({\n kind: 'sandbox',\n message: 'The script reported store writes that are not JSON.'\n })\n }\n\n const converted: MutableExecutionResult = {\n ok: true,\n output: result.output,\n storeWrites: storeWrites.value\n }\n\n if (result.value !== undefined) converted.value = result.value\n\n return converted\n }\n\n const { kind, name, message, stack } = result.error\n const named = name !== undefined && name.length > 0 && !message.startsWith(name)\n\n const error: MutableCodeModeError = {\n kind,\n message: named ? `${name}: ${message}` : message\n }\n\n if (stack !== undefined) error.stack = stack\n\n return { ok: false, error, output: result.output }\n}\n\nconst describeError = (error: unknown) => (error instanceof Error ? error.message : String(error))\n\n/**\n * A `CodeModeExecutor` on `@earendil-works/pi-codemode`: every execution gets a fresh QuickJS VM\n * (WebAssembly) in a fresh worker thread, closed when the execution ends. Applies the timeout,\n * heap cap, abort signal, and store, strips TypeScript annotations first, and caps concurrent\n * executions per executor; create one executor per process (module scope) so the cap is\n * per process.\n *\n * Node only. The worker inherits a copy of `process.env` (scripts cannot read it: the VM has no\n * `process`). Under Next.js, list `@yolk-sdk/codemode`, `@earendil-works/pi-codemode`, and\n * `quickjs-wasi` in `serverExternalPackages` so the worker file and wasm stay on disk.\n */\nexport const makePiCodeModeExecutor = (\n options: PiCodeModeExecutorOptions = {}\n): CodeModeExecutor => {\n const slots = makeExecutionSlots(\n Math.max(1, Math.floor(options.maxConcurrentExecutions ?? defaultMaxConcurrentExecutions))\n )\n\n const run = async (code: string, execution: CodeModeExecuteOptions, queuedAt: number) => {\n const remaining = () => execution.timeoutMs - (Date.now() - queuedAt)\n const stripped = stripTypes(code)\n\n if (!stripped.ok) return failure(stripped.error)\n\n const timeoutMs = remaining()\n\n if (timeoutMs <= 0) {\n return failure({\n kind: 'timeout',\n message: `Execution timed out after ${execution.timeoutMs} ms`\n })\n }\n\n const sandboxOptions: CodemodeSandboxOptions = {\n tools: execution.tools.map(piTool),\n globals: execution.globals.map(piGlobal),\n timeoutMs,\n memoryLimitBytes: execution.memoryLimitBytes\n }\n\n if (options.wasm !== undefined) sandboxOptions.wasm = options.wasm\n\n if (options.workerUrl !== undefined) sandboxOptions.workerUrl = options.workerUrl\n\n let sandbox: CodemodeSandbox\n\n try {\n sandbox = new CodemodeSandbox(sandboxOptions)\n } catch (error) {\n return failure({\n kind: 'sandbox',\n message: `Could not create the sandbox: ${describeError(error)}`\n })\n }\n\n try {\n return fromPiResult(\n await sandbox.execute(stripped.code, {\n signal: execution.signal,\n store: execution.store,\n timeoutMs\n })\n )\n } finally {\n await sandbox.close()\n }\n }\n\n return {\n execute: async (code, execution) => {\n const queuedAt = Date.now()\n const slot = await slots.acquire(execution.signal, execution.timeoutMs)\n\n if (slot === 'aborted') return failure({ kind: 'aborted', message: 'Execution aborted' })\n\n if (slot === 'timeout') {\n return failure({\n kind: 'timeout',\n message: `Execution timed out after ${execution.timeoutMs} ms while waiting for a free execution slot`\n })\n }\n\n try {\n return await run(code, execution, queuedAt)\n } finally {\n slots.release()\n }\n }\n }\n}\n"],"mappings":";;;;;;AAqBA,MAAa,iCAAiC;;AAsB9C,MAAM,sBAAsB,QAAgB;CAC1C,IAAI,SAAS;CACb,MAAM,UAAyB,CAAC;CAEhC,MAAM,WAAW,QAAqB,cAA4C;EAChF,IAAI,OAAO,SAAS,OAAO,QAAQ,QAAQ,SAAS;EAEpD,IAAI,SAAS,KAAK;GAChB;GAEA,OAAO,QAAQ,QAAQ,UAAU;EACnC;EAEA,OAAO,IAAI,SAAQ,YAAW;GAC5B,IAAI;GAEJ,MAAM,UAAiB,YAAW;IAChC,MAAM,QAAQ,QAAQ,QAAQ,MAAM;IAEpC,IAAI,UAAU,IAAI,QAAQ,OAAO,OAAO,CAAC;IAEzC,IAAI,UAAU,KAAA,GAAW,aAAa,KAAK;IAC3C,OAAO,oBAAoB,SAAS,OAAO;IAC3C,QAAQ,OAAO;GACjB;GAEA,MAAM,gBAAgB,OAAO,SAAS;GAEtC,QAAQ,KAAK,MAAM;GACnB,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;GAExD,IAAI,OAAO,SAAS,SAAS,GAC3B,QAAQ,iBAAiB,OAAO,SAAS,GAAG,KAAK,IAAI,GAAG,SAAS,CAAC;EAEtE,CAAC;CACH;CAEA,MAAM,gBAAgB;EACpB,MAAM,OAAO,QAAQ;EAErB,IAAI,SAAS,KAAA,GACX;OAGA,KAAK,UAAU;CAEnB;CAEA,OAAO;EAAE;EAAS;CAAQ;AAC5B;AAEA,MAAM,gBAAgB;AAEtB,MAAM,gBAAgB;AAEtB,MAAM,gBAAgB,OAAO,OAAO,EAAE,MAAM,OAAO,OAAO,CAAC;AAE3D,MAAM,aAAa,UACjB,OAAO,eAAe,OAAO,IAAI,OAAO,oBAAoB,aAAa,EAAE,KAAK,IAAG,MAAK,EAAE,IAAI,CAAC;AAEjG,MAAM,kBAAkB,UAAiB;CACvC,MAAM,QAAQ,sCAAsC,KAAK,MAAM,SAAS,EAAE;CAE1E,IAAI,UAAU,MAAM,OAAO;CAE3B,MAAM,OAAO,OAAO,MAAM,EAAE,IAAI;CAEhC,OAAO,QAAQ,IAAI,UAAU,KAAK,KAAK,MAAM,MAAM,GAAG,IAAI,MAAM,MAAM,OAAO;AAC/E;AAEA,MAAM,oBAAoB,UAAmB;CAC3C,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;CAErE,IAAI,UAAU,KAAK,MAAM,qCACvB,OAAO,GAAG,QAAQ;CAKpB,OAAO,mCAAmC,UAFzB,iBAAiB,QAAQ,eAAe,KAAK,IAAI;AAGpE;;;;;;AAWA,MAAM,cAAc,SAA2B;CAC7C,IAAI,CAAC,UAAU,WAAW,WAAW,oBAAoB,GAAG,OAAO;EAAE,IAAI;EAAM;CAAK;CAEpF,IAAI;EACF,MAAM,WAAW,WAAW,qBAAqB,GAAG,gBAAgB,OAAO,iBAAiB,EAC1F,MAAM,QACR,CAAC;EAED,IAAI,CAAC,SAAS,WAAW,aAAa,KAAK,CAAC,SAAS,SAAS,aAAa,GACzE,OAAO;GAAE,IAAI;GAAO,OAAO;IAAE,MAAM;IAAU,SAAS;GAAkC;EAAE;EAG5F,OAAO;GACL,IAAI;GACJ,MAAM,SAAS,MAAM,IAAsB,SAAS,SAAS,CAAoB;EACnF;CACF,SAAS,OAAO;EACd,OAAO;GAAE,IAAI;GAAO,OAAO;IAAE,MAAM;IAAU,SAAS,iBAAiB,KAAK;GAAE;EAAE;CAClF;AACF;AAEA,MAAM,UAAU,SAA6C;CAC3D,MAAM,YAA0B;EAC9B,MAAM,KAAK;EACX,UAAU,MAAM,YAAY,KAAK,QAAQ,MAAM,OAAO;CACxD;CAEA,IAAI,KAAK,gBAAgB,KAAA,GAAW,UAAU,cAAc,KAAK;CAEjE,IAAI,KAAK,gBAAgB,KAAA,GAAW,UAAU,cAAc,KAAK;CAEjE,IAAI,KAAK,iBAAiB,KAAA,GAAW,UAAU,eAAe,KAAK;CAEnE,OAAO;AACT;AAEA,MAAM,YAAY,WAAiD;CACjE,MAAM,YAAY,OAAO,MAAM;CAE/B,IAAI,OAAO,WAAW,KAAA,GAAW,UAAU,SAAS,OAAO;CAE3D,IAAI,OAAO,cAAc,KAAA,GAAW,UAAU,YAAY,OAAO;CAEjE,OAAO;AACT;AAEA,MAAM,WAAW,WAAmD;CAClE,IAAI;CACJ;CACA,QAAQ,CAAC;AACX;AAEA,MAAM,cAAc,OAAO,OAAO;CAChC,KAAK,OAAO,OAAO,OAAO,QAAQ,OAAO,IAAI;CAC7C,QAAQ,OAAO,MAAM,OAAO,MAAM;AACpC,CAAC;AAED,MAAM,oBAAoB,OAAO,oBAAoB,WAAW;AAgBhE,MAAM,gBAAgB,WAAoD;CACxE,IAAI,OAAO,IAAI;EACb,MAAM,cAAc,kBAAkB,OAAO,WAAW;EAExD,IAAI,OAAO,OAAO,WAAW,GAC3B,OAAO,QAAQ;GACb,MAAM;GACN,SAAS;EACX,CAAC;EAGH,MAAM,YAAoC;GACxC,IAAI;GACJ,QAAQ,OAAO;GACf,aAAa,YAAY;EAC3B;EAEA,IAAI,OAAO,UAAU,KAAA,GAAW,UAAU,QAAQ,OAAO;EAEzD,OAAO;CACT;CAEA,MAAM,EAAE,MAAM,MAAM,SAAS,UAAU,OAAO;CAG9C,MAAM,QAA8B;EAClC;EACA,SAJY,SAAS,KAAA,KAAa,KAAK,SAAS,KAAK,CAAC,QAAQ,WAAW,IAAI,IAI5D,GAAG,KAAK,IAAI,YAAY;CAC3C;CAEA,IAAI,UAAU,KAAA,GAAW,MAAM,QAAQ;CAEvC,OAAO;EAAE,IAAI;EAAO;EAAO,QAAQ,OAAO;CAAO;AACnD;AAEA,MAAM,iBAAiB,UAAoB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;;;;;;;;;;;;AAahG,MAAa,0BACX,UAAqC,CAAC,MACjB;CACrB,MAAM,QAAQ,mBACZ,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,2BAAA,CAAyD,CAAC,CAC3F;CAEA,MAAM,MAAM,OAAO,MAAc,WAAmC,aAAqB;EACvF,MAAM,kBAAkB,UAAU,aAAa,KAAK,IAAI,IAAI;EAC5D,MAAM,WAAW,WAAW,IAAI;EAEhC,IAAI,CAAC,SAAS,IAAI,OAAO,QAAQ,SAAS,KAAK;EAE/C,MAAM,YAAY,UAAU;EAE5B,IAAI,aAAa,GACf,OAAO,QAAQ;GACb,MAAM;GACN,SAAS,6BAA6B,UAAU,UAAU;EAC5D,CAAC;EAGH,MAAM,iBAAyC;GAC7C,OAAO,UAAU,MAAM,IAAI,MAAM;GACjC,SAAS,UAAU,QAAQ,IAAI,QAAQ;GACvC;GACA,kBAAkB,UAAU;EAC9B;EAEA,IAAI,QAAQ,SAAS,KAAA,GAAW,eAAe,OAAO,QAAQ;EAE9D,IAAI,QAAQ,cAAc,KAAA,GAAW,eAAe,YAAY,QAAQ;EAExE,IAAI;EAEJ,IAAI;GACF,UAAU,IAAI,gBAAgB,cAAc;EAC9C,SAAS,OAAO;GACd,OAAO,QAAQ;IACb,MAAM;IACN,SAAS,iCAAiC,cAAc,KAAK;GAC/D,CAAC;EACH;EAEA,IAAI;GACF,OAAO,aACL,MAAM,QAAQ,QAAQ,SAAS,MAAM;IACnC,QAAQ,UAAU;IAClB,OAAO,UAAU;IACjB;GACF,CAAC,CACH;EACF,UAAU;GACR,MAAM,QAAQ,MAAM;EACtB;CACF;CAEA,OAAO,EACL,SAAS,OAAO,MAAM,cAAc;EAClC,MAAM,WAAW,KAAK,IAAI;EAC1B,MAAM,OAAO,MAAM,MAAM,QAAQ,UAAU,QAAQ,UAAU,SAAS;EAEtE,IAAI,SAAS,WAAW,OAAO,QAAQ;GAAE,MAAM;GAAW,SAAS;EAAoB,CAAC;EAExF,IAAI,SAAS,WACX,OAAO,QAAQ;GACb,MAAM;GACN,SAAS,6BAA6B,UAAU,UAAU;EAC5D,CAAC;EAGH,IAAI;GACF,OAAO,MAAM,IAAI,MAAM,WAAW,QAAQ;EAC5C,UAAU;GACR,MAAM,QAAQ;EAChB;CACF,EACF;AACF"}
1
+ {"version":3,"file":"node.mjs","names":[],"sources":["../src/node.ts"],"sourcesContent":["import * as nodeModule from 'node:module'\nimport { Option, Predicate } from 'effect'\nimport * as Schema from 'effect/Schema'\nimport {\n CodemodeSandbox,\n type CodemodeResult,\n type CodemodeSandboxOptions,\n type CodemodeTool,\n type CodemodeWasmModule\n} from '@earendil-works/pi-codemode'\nimport { codeModeCallLabels } from './catalog.ts'\nimport type {\n CodeModeError,\n CodeModeExecuteOptions,\n CodeModeExecutionResult,\n CodeModeExecutor,\n CodeModeExecutorGlobal,\n CodeModeExecutorTool,\n CodeModeStoreWrites\n} from './executor.ts'\n\n/** Default cap of concurrent script executions per executor. */\nexport const defaultMaxConcurrentExecutions = 4\n\nexport type PiCodeModeExecutorOptions = {\n /**\n * Compiled `quickjs-wasi/quickjs.wasm` (see pi's `loadQuickJSWasm`). Default: the file of the\n * installed `quickjs-wasi` package, compiled once per process.\n */\n readonly wasm?: CodemodeWasmModule | Promise<CodemodeWasmModule>\n /**\n * Worker entry that imports `@earendil-works/pi-codemode/worker`. Default: pi's own worker file,\n * which exists on disk when the package is not bundled (Next `serverExternalPackages`).\n */\n readonly workerUrl?: string | URL\n /** Concurrent executions of this executor; more wait for a free slot. Default 4. */\n readonly maxConcurrentExecutions?: number\n}\n\ntype SlotOutcome = 'acquired' | 'aborted' | 'timeout'\n\ntype Waiter = (outcome: SlotOutcome) => void\n\n/** Abort- and deadline-aware counting semaphore for executions. */\nconst makeExecutionSlots = (max: number) => {\n let active = 0\n const waiting: Array<Waiter> = []\n\n const acquire = (signal: AbortSignal, timeoutMs: number): Promise<SlotOutcome> => {\n if (signal.aborted) return Promise.resolve('aborted')\n\n if (active < max) {\n active++\n\n return Promise.resolve('acquired')\n }\n\n return new Promise(resolve => {\n let timer: ReturnType<typeof setTimeout> | undefined\n\n const settle: Waiter = outcome => {\n const index = waiting.indexOf(settle)\n\n if (index !== -1) waiting.splice(index, 1)\n\n if (timer !== undefined) clearTimeout(timer)\n signal.removeEventListener('abort', onAbort)\n resolve(outcome)\n }\n\n const onAbort = () => settle('aborted')\n\n waiting.push(settle)\n signal.addEventListener('abort', onAbort, { once: true })\n\n if (Number.isFinite(timeoutMs)) {\n timer = setTimeout(() => settle('timeout'), Math.max(0, timeoutMs))\n }\n })\n }\n\n const release = () => {\n const next = waiting[0]\n\n if (next === undefined) {\n active--\n } else {\n // The slot passes straight to the next waiter.\n next('acquired')\n }\n }\n\n return { acquire, release }\n}\n\nconst wrapperPrefix = 'async function __yolkCodeMode() {\\n'\n\nconst wrapperSuffix = '\\n}'\n\n/** Most values one argument may hold; a larger argument is rejected. */\nconst maxArgumentValues = 100_000\n\n/** Deepest object/array nesting one argument may have; deeper arguments are rejected. */\nconst maxArgumentDepth = 64\n\n/**\n * Evaluated in the VM before the script runs: `(tools, labels) => guardedTools`, with `labels` the\n * `[name, callLabel]` pairs of the tools in order (`codeModeCallLabels`). Each call walks its first\n * argument once, the way `JSON.stringify` reads it (each own enumerable string key and each array\n * index read once, so getters run once; a callable `toJSON` is called once with the key, `''` at\n * the root, and its result is walked at the same path), and builds a fresh copy (null-prototype\n * objects and arrays) that is what the call sends. It rejects with a `TypeError` naming the call\n * label and path when JSON would change the value instead of carrying it: non-finite numbers,\n * invalid `Date`s, `undefined`/function/symbol array items and holes, function/symbol/bigint\n * values, cycles, and objects that are neither plain (prototype `Object.prototype` or `null`) nor\n * arrays and have no `toJSON`. An argument with more than `maxArgumentValues` values or nested more\n * than `maxArgumentDepth` levels is rejected too, never sent unchecked. `undefined`-valued keys are\n * left out (absent, as JSON drops them); the same object twice (not a cycle) is copied twice. An\n * error thrown by a getter or `toJSON` rejects the call with that error. A rejected call never\n * reaches the host. Every member of pi's `tools` is wrapped; unknown members fall through to pi's\n * proxy (close-match suggestions). Not a security boundary (`globalThis.tools` is unguarded):\n * arguments are still decoded by the host.\n */\nconst toolsGuardSource = `(function (tools, labels) {\n // Captured before the script runs, so a script that changes built-ins cannot change the checks.\n const { apply, getPrototypeOf, setPrototypeOf } = Reflect\n const isFinite = Number.isFinite\n const isArray = Array.isArray\n const { keys, create, defineProperty } = Object\n const getTime = Date.prototype.getTime\n const TypeErrorCtor = TypeError\n const SetCtor = Set\n const setAdd = Set.prototype.add\n const setHas = Set.prototype.has\n const setDelete = Set.prototype.delete\n const ObjectProto = Object.prototype\n const { trunc, min } = Math\n const stringify = JSON.stringify\n const plainJson = 'pass plain JSON (objects, arrays, strings, finite numbers, booleans, null)'\n const maxValues = ${maxArgumentValues}\n const maxDepth = ${maxArgumentDepth}\n class Rejected {\n constructor(message) {\n defineProperty(this, 'message', { value: message })\n }\n }\n const keyPath = (path, key) =>\n /^[A-Za-z_$][\\\\w$]*$/.test(key)\n ? (path === '' ? key : path + '.' + key)\n : path + '[' + stringify(key) + ']'\n const typeName = value => {\n const proto = getPrototypeOf(value)\n const name = proto && typeof proto.constructor === 'function' ? proto.constructor.name : ''\n return name ? (/^[AEIOU]/.test(name) ? 'an ' : 'a ') + name : 'a non-plain object'\n }\n // The JSON copy of a value already read from its holder at key (undefined: absent), or throws\n // Rejected with why JSON would not carry it unchanged.\n const snapshot = (read, key, path, slot, walk) => {\n const at = path === '' ? 'is ' : 'at ' + path + ' is '\n if (++walk.values > maxValues) {\n throw new Rejected('has more than ' + maxValues + ' values; split the work across calls')\n }\n let value = read\n const kind = typeof value\n if ((kind === 'object' && value !== null) || kind === 'function' || kind === 'bigint') {\n // The intrinsic getTime reads a Date's internal time (and throws for anything else).\n let time\n try {\n time = apply(getTime, value, [])\n } catch {\n time = undefined\n }\n if (time !== undefined && !isFinite(time)) {\n throw new Rejected(at + 'an invalid Date; pass a valid Date or an ISO string')\n }\n const toJSON = value.toJSON\n if (typeof toJSON === 'function') value = apply(toJSON, value, [key])\n }\n switch (typeof value) {\n case 'string':\n case 'boolean':\n return value\n case 'number':\n if (isFinite(value)) return value\n throw new Rejected(\n at + value + '; pass a finite number' + (slot === 'key' ? ' or omit the key' : '')\n )\n case 'undefined':\n if (slot === 'item') throw new Rejected(at + 'undefined; arrays cannot hold undefined')\n return undefined\n case 'bigint':\n case 'function':\n case 'symbol':\n throw new Rejected(at + 'a ' + typeof value + '; ' + plainJson)\n }\n if (value === null) return null\n if (apply(setHas, walk.ancestors, [value])) {\n throw new Rejected(at + 'a circular reference; ' + plainJson)\n }\n if (walk.depth >= maxDepth) {\n throw new Rejected('at ' + path + ' is nested more than ' + maxDepth + ' levels deep')\n }\n const array = isArray(value)\n if (!array) {\n const proto = getPrototypeOf(value)\n if (proto !== ObjectProto && proto !== null) {\n throw new Rejected(at + typeName(value) + '; ' + plainJson)\n }\n }\n apply(setAdd, walk.ancestors, [value])\n walk.depth++\n let copy\n if (array) {\n // LengthOfArrayLike, read once, as JSON.stringify does.\n // Unary plus is ToNumber: one coercion, and it throws for a bigint as JSON.stringify does.\n const raw = +value.length\n const length = raw > 0 ? min(trunc(raw), 9007199254740991) : 0\n // No prototype: no inherited toJSON or index setter can change the checked copy when pi\n // serializes it (still an array to JSON.stringify).\n copy = []\n setPrototypeOf(copy, null)\n for (let i = 0; i < length; i++) {\n copy[i] = snapshot(value[i], '' + i, path + '[' + i + ']', 'item', walk)\n }\n } else {\n copy = create(null)\n // Indexed over the fresh key array: no iterator a script could replace.\n const names = keys(value)\n for (let i = 0; i < names.length; i++) {\n const name = names[i]\n const item = snapshot(value[name], name, keyPath(path, name), 'key', walk)\n if (item !== undefined) copy[name] = item\n }\n }\n walk.depth--\n apply(setDelete, walk.ancestors, [value])\n return copy\n }\n const wrap = (label, call) => async (...args) => {\n let json\n try {\n json = snapshot(args[0], '', '', 'root', { values: 0, depth: 0, ancestors: new SetCtor() })\n } catch (error) {\n if (error instanceof Rejected) throw new TypeErrorCtor(label + ': argument ' + error.message)\n throw error\n }\n return call(json)\n }\n // Each tool's function sits at tools[name] unless an earlier tool holds that key, in which case\n // that earlier function is already wrapped.\n const wrappers = new Map()\n for (const [name, label] of labels) {\n const call = name in tools ? tools[name] : undefined\n if (typeof call === 'function' && !wrappers.has(call)) wrappers.set(call, wrap(label, call))\n }\n const guarded = Object.create(null)\n for (const key of Object.keys(tools)) {\n const call = tools[key]\n if (!wrappers.has(call)) wrappers.set(call, wrap('tools[' + JSON.stringify(key) + ']', call))\n guarded[key] = wrappers.get(call)\n }\n return new Proxy(Object.freeze(guarded), {\n get: (target, key) => (key in target ? target[key] : tools[key])\n })\n})`\n\n// pi runs `(async (tools, console) => {<code>\\n})`. The script runs in an inner function of the\n// same shape whose `tools` is guarded; the prefix shares line 1 with the script (only line-1\n// columns shift) and the guard follows the script, so its line numbers are unchanged.\nconst guardPrefix = 'return (async (tools, console) => {'\n\n/** JSON as a JavaScript expression (U+2028/U+2029 escaped for older parsers). */\nconst jsonSource = (value: unknown) =>\n JSON.stringify(value).replace(/[\\u2028\\u2029]/g, char =>\n char === '\\u2028' ? '\\\\u2028' : '\\\\u2029'\n )\n\nconst guardSuffix = (tools: ReadonlyArray<CodeModeExecutorTool>) => {\n const names = tools.map(tool => tool.name)\n const labels = codeModeCallLabels(names)\n\n // `makeCodeModeTool` supplies each label; other callers get pi's binding rule.\n const pairs = tools.map((tool, index) => [\n tool.name,\n tool.callLabel ?? labels[index] ?? `tools[${JSON.stringify(tool.name)}]`\n ])\n\n return `\\n})(${toolsGuardSource}(tools, ${jsonSource(pairs)}), console)`\n}\n\nconst lineTerminators = /\\r\\n?|[\\n\\u2028\\u2029]/g\n\n/** Lines of the script as QuickJS counts them (at least; never fewer). */\nconst scriptLineCount = (code: string) => (code.match(lineTerminators)?.length ?? 0) + 1\n\nconst scriptFrame = /codemode\\.js:(\\d+):\\d+/\n\n/** Drops stack frames of the wrapper and guard, which sit below the script's last line. */\nconst withoutWrapperFrames = (stack: string, scriptLines: number) =>\n stack\n .split('\\n')\n .filter(line => {\n const frame = scriptFrame.exec(line)\n\n return frame === null || Number(frame[1]) <= scriptLines\n })\n .join('\\n')\n\nconst ErrorWithCode = Schema.Struct({ code: Schema.String })\n\nconst errorCode = (error: unknown) =>\n Option.getOrUndefined(Option.map(Schema.decodeUnknownOption(ErrorWithCode)(error), e => e.code))\n\nconst syntaxLocation = (error: Error) => {\n const match = /^[^\\n]*:(\\d+)\\n([^\\n]*)\\n([^\\n]*)\\n/.exec(error.stack ?? '')\n\n if (match === null) return ''\n\n const line = Number(match[1]) - 1\n\n return line >= 1 ? ` (line ${line})\\n${match[2] ?? ''}\\n${match[3] ?? ''}` : ''\n}\n\nconst typeStripMessage = (error: unknown) => {\n const message = error instanceof Error ? error.message : String(error)\n\n if (errorCode(error) === 'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX') {\n return `${message}. Write plain JavaScript: type annotations are stripped, but enums, namespaces, and parameter properties are not supported.`\n }\n\n const location = error instanceof Error ? syntaxLocation(error) : ''\n\n return `The script could not be parsed: ${message}${location}`\n}\n\ntype Stripped =\n | { readonly ok: true; readonly code: string }\n | { readonly ok: false; readonly error: CodeModeError }\n\n/**\n * Strips TypeScript annotations with Node's `stripTypeScriptTypes` (Node 22.13+; whitespace\n * replacement keeps line and column numbers). The script is wrapped in an async function so\n * top-level `return` and `await` parse. On Node without the API, code passes through unchanged.\n */\nconst stripTypes = (code: string): Stripped => {\n if (!Predicate.isFunction(nodeModule.stripTypeScriptTypes)) return { ok: true, code }\n\n try {\n const stripped = nodeModule.stripTypeScriptTypes(`${wrapperPrefix}${code}${wrapperSuffix}`, {\n mode: 'strip'\n })\n\n if (!stripped.startsWith(wrapperPrefix) || !stripped.endsWith(wrapperSuffix)) {\n return { ok: false, error: { kind: 'script', message: 'The script could not be parsed.' } }\n }\n\n return {\n ok: true,\n code: stripped.slice(wrapperPrefix.length, stripped.length - wrapperSuffix.length)\n }\n } catch (error) {\n return { ok: false, error: { kind: 'script', message: typeStripMessage(error) } }\n }\n}\n\nconst piTool = (tool: CodeModeExecutorTool): CodemodeTool => {\n const converted: CodemodeTool = {\n name: tool.name,\n execute: (args, context) => tool.execute(args, context)\n }\n\n if (tool.description !== undefined) converted.description = tool.description\n\n if (tool.inputSchema !== undefined) converted.inputSchema = tool.inputSchema\n\n if (tool.outputSchema !== undefined) converted.outputSchema = tool.outputSchema\n\n return converted\n}\n\nconst piGlobal = (global: CodeModeExecutorGlobal): CodemodeTool => {\n const converted = piTool(global)\n\n if (global.spread !== undefined) converted.spread = global.spread\n\n if (global.signature !== undefined) converted.signature = global.signature\n\n return converted\n}\n\nconst failure = (error: CodeModeError): CodeModeExecutionResult => ({\n ok: false,\n error,\n output: []\n})\n\nconst StoreWrites = Schema.Struct({\n set: Schema.Record(Schema.String, Schema.Json),\n delete: Schema.Array(Schema.String)\n})\n\nconst decodeStoreWrites = Schema.decodeUnknownOption(StoreWrites)\n\ntype MutableCodeModeError = {\n kind: CodeModeError['kind']\n message: string\n stack?: string\n}\n\ntype MutableExecutionResult = {\n ok: boolean\n value?: unknown\n error?: CodeModeError\n output: CodeModeExecutionResult['output']\n storeWrites?: CodeModeStoreWrites\n}\n\nconst fromPiResult = (result: CodemodeResult, scriptLines: number): CodeModeExecutionResult => {\n if (result.ok) {\n const storeWrites = decodeStoreWrites(result.storeWrites)\n\n if (Option.isNone(storeWrites)) {\n return failure({\n kind: 'sandbox',\n message: 'The script reported store writes that are not JSON.'\n })\n }\n\n const converted: MutableExecutionResult = {\n ok: true,\n output: result.output,\n storeWrites: storeWrites.value\n }\n\n if (result.value !== undefined) converted.value = result.value\n\n return converted\n }\n\n const { kind, name, message, stack } = result.error\n const named = name !== undefined && name.length > 0 && !message.startsWith(name)\n\n const error: MutableCodeModeError = {\n kind,\n message: named ? `${name}: ${message}` : message\n }\n\n if (stack !== undefined) error.stack = withoutWrapperFrames(stack, scriptLines)\n\n return { ok: false, error, output: result.output }\n}\n\nconst describeError = (error: unknown) => (error instanceof Error ? error.message : String(error))\n\n/**\n * A `CodeModeExecutor` on `@earendil-works/pi-codemode`: every execution gets a fresh QuickJS VM\n * (WebAssembly) in a fresh worker thread, closed when the execution ends. Applies the timeout,\n * heap cap, abort signal, and store, strips TypeScript annotations first, rejects tool arguments\n * that JSON would silently change inside the script (see `CodeModeExecutorTool`), and caps concurrent\n * executions per executor; create one executor per process (module scope) so the cap is\n * per process.\n *\n * Node only. The worker inherits a copy of `process.env` (scripts cannot read it: the VM has no\n * `process`). Under Next.js, list `@yolk-sdk/codemode`, `@earendil-works/pi-codemode`, and\n * `quickjs-wasi` in `serverExternalPackages` so the worker file and wasm stay on disk.\n */\nexport const makePiCodeModeExecutor = (\n options: PiCodeModeExecutorOptions = {}\n): CodeModeExecutor => {\n const slots = makeExecutionSlots(\n Math.max(1, Math.floor(options.maxConcurrentExecutions ?? defaultMaxConcurrentExecutions))\n )\n\n const run = async (code: string, execution: CodeModeExecuteOptions, queuedAt: number) => {\n const remaining = () => execution.timeoutMs - (Date.now() - queuedAt)\n const stripped = stripTypes(code)\n\n if (!stripped.ok) return failure(stripped.error)\n\n const timeoutMs = remaining()\n\n if (timeoutMs <= 0) {\n return failure({\n kind: 'timeout',\n message: `Execution timed out after ${execution.timeoutMs} ms`\n })\n }\n\n const sandboxOptions: CodemodeSandboxOptions = {\n tools: execution.tools.map(piTool),\n globals: execution.globals.map(piGlobal),\n timeoutMs,\n memoryLimitBytes: execution.memoryLimitBytes\n }\n\n if (options.wasm !== undefined) sandboxOptions.wasm = options.wasm\n\n if (options.workerUrl !== undefined) sandboxOptions.workerUrl = options.workerUrl\n\n let sandbox: CodemodeSandbox\n\n try {\n sandbox = new CodemodeSandbox(sandboxOptions)\n } catch (error) {\n return failure({\n kind: 'sandbox',\n message: `Could not create the sandbox: ${describeError(error)}`\n })\n }\n\n try {\n return fromPiResult(\n await sandbox.execute(`${guardPrefix}${stripped.code}${guardSuffix(execution.tools)}`, {\n signal: execution.signal,\n store: execution.store,\n timeoutMs\n }),\n scriptLineCount(stripped.code)\n )\n } finally {\n await sandbox.close()\n }\n }\n\n return {\n execute: async (code, execution) => {\n const queuedAt = Date.now()\n const slot = await slots.acquire(execution.signal, execution.timeoutMs)\n\n if (slot === 'aborted') return failure({ kind: 'aborted', message: 'Execution aborted' })\n\n if (slot === 'timeout') {\n return failure({\n kind: 'timeout',\n message: `Execution timed out after ${execution.timeoutMs} ms while waiting for a free execution slot`\n })\n }\n\n try {\n return await run(code, execution, queuedAt)\n } finally {\n slots.release()\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"tool.d.mts","names":[],"sources":["../src/tool.ts"],"mappings":";;;;;;;;cA0Da,gBAAA;AAAb;AAAA,KAGY,cAAA;kGAED,SAAA,WALkB;EAAA,SAOlB,gBAAA,WAJe;EAAA,SAMf,cAAA,WANe;EAAA,SAQf,cAAA,WAJA;EAAA,SAMA,SAAA;EAFA;;;EAAA,SAMA,aAAA;AAAA;AAAA,cAGE,qBAAA;EAAA;;;;;;;;cAUA,wBAAA;;cAGA,wBAAA;AAAA,KAED,uBAAA;EAAA,SACD,QAAA,EAAU,gBAAA;WAEV,IAAA,WAR0B;EAAA,SAU1B,YAAA;EAAA,SACA,MAAA,GAAS,cAAA;;;AARiB;AAErC;WAWW,QAAA,IAAY,OAAA,EAAS,OAAA;EAXG;;;;EAAA,SAgBxB,SAAA,IAAa,OAAA,EAAS,OAAA,KAAY,MAAA,CAAO,MAAA,CAAO,aAAA,EAAe,SAAA;EAAf;;;;;;;EAAA,SAQhD,gBAAA,IAAoB,KAAA;IAAA,SAClB,IAAA,EAAM,QAAA;IAAA,SACN,OAAA,EAAS,OAAA;EAAA,MACd,MAAA,CAAO,MAAA;AAAA;;;;;;;;;;;;cA2YF,gBAAA,YACX,OAAA,EAAS,uBAAA,CAAwB,OAAA,MAChC,gBAAA,CAAiB,OAAA"}
1
+ {"version":3,"file":"tool.d.mts","names":[],"sources":["../src/tool.ts"],"mappings":";;;;;;;;cA0Da,gBAAA;AAAb;AAAA,KAGY,cAAA;kGAED,SAAA,WALkB;EAAA,SAOlB,gBAAA,WAJe;EAAA,SAMf,cAAA,WANe;EAAA,SAQf,cAAA,WAJA;EAAA,SAMA,SAAA;EAFA;;;EAAA,SAMA,aAAA;AAAA;AAAA,cAGE,qBAAA;EAAA;;;;;;;;cAUA,wBAAA;;cAGA,wBAAA;AAAA,KAED,uBAAA;EAAA,SACD,QAAA,EAAU,gBAAA;WAEV,IAAA,WAR0B;EAAA,SAU1B,YAAA;EAAA,SACA,MAAA,GAAS,cAAA;;;AARiB;AAErC;WAWW,QAAA,IAAY,OAAA,EAAS,OAAA;EAXG;;;;EAAA,SAgBxB,SAAA,IAAa,OAAA,EAAS,OAAA,KAAY,MAAA,CAAO,MAAA,CAAO,aAAA,EAAe,SAAA;EAAf;;;;;;;EAAA,SAQhD,gBAAA,IAAoB,KAAA;IAAA,SAClB,IAAA,EAAM,QAAA;IAAA,SACN,OAAA,EAAS,OAAA;EAAA,MACd,MAAA,CAAO,MAAA;AAAA;;;;;;;;;;;;cA+YF,gBAAA,YACX,OAAA,EAAS,uBAAA,CAAwB,OAAA,MAChC,gBAAA,CAAiB,OAAA"}
package/dist/tool.mjs CHANGED
@@ -26,7 +26,9 @@ var CodeModeExecutorRejected = class extends Data.TaggedError("CodeModeExecutorR
26
26
  const errorMessage = (error) => error instanceof Error ? error.message : Predicate.isString(error) ? error : "unknown error";
27
27
  const nestedText = (content) => Predicate.isString(content) ? content : content.map(contentPartText).filter((text) => text.length > 0).join("\n");
28
28
  /** What a nested call resolves to: `structuredContent` for tools with an output schema (when
29
- * present), text content otherwise; error results reject with their text.
29
+ * present), text content otherwise; error results reject with their text. A tool that declares an
30
+ * output schema but returns no `structuredContent` resolves to its text: that is a tool bug, kept
31
+ * readable rather than turned into a failure.
30
32
  */
31
33
  const resolveNestedResult = (tool, result) => {
32
34
  const text = nestedText(result.content);
@@ -121,8 +123,8 @@ const runScript = (input) => Effect.gen(function* () {
121
123
  const runFork = yield* FiberSet.runtime(fibers)();
122
124
  const records = [];
123
125
  const callTool = (tool) => (args, { signal }) => {
124
- if (signal.aborted) return Promise.reject(/* @__PURE__ */ new Error(`tools.${tool.identifier} was cancelled.`));
125
- if (records.length >= limits.maxNestedCalls) return Promise.reject(/* @__PURE__ */ new Error(`Nested call limit reached: a script may make at most ${limits.maxNestedCalls} tool calls. Batch the work or return partial results.`));
126
+ if (signal.aborted) return Promise.reject(/* @__PURE__ */ new Error(`${tool.callLabel} was cancelled.`));
127
+ if (records.length >= limits.maxNestedCalls) return Promise.reject(/* @__PURE__ */ new Error(`${tool.callLabel}: Nested call limit reached: a script may make at most ${limits.maxNestedCalls} tool calls. Batch the work or return partial results.`));
126
128
  const params = args === void 0 ? {} : args;
127
129
  const record = {
128
130
  id: `${call.id}/${records.length + 1}`,
@@ -155,12 +157,12 @@ const runScript = (input) => Effect.gen(function* () {
155
157
  return new Promise((resolve, reject) => {
156
158
  runFork(execution, { signal }).addObserver((exit) => {
157
159
  if (Exit.isFailure(exit)) {
158
- reject(/* @__PURE__ */ new Error(Cause.hasInterruptsOnly(exit.cause) ? `tools.${tool.identifier} was cancelled.` : `tools.${tool.identifier} failed unexpectedly.`));
160
+ reject(/* @__PURE__ */ new Error(Cause.hasInterruptsOnly(exit.cause) ? `${tool.callLabel} was cancelled.` : `${tool.callLabel} failed unexpectedly.`));
159
161
  return;
160
162
  }
161
163
  const resolution = resolveNestedResult(tool, exit.value);
162
164
  if (resolution.ok) resolve(resolution.value);
163
- else reject(new Error(resolution.message));
165
+ else reject(/* @__PURE__ */ new Error(`${tool.callLabel}: ${resolution.message}`));
164
166
  });
165
167
  });
166
168
  };
@@ -169,6 +171,7 @@ const runScript = (input) => Effect.gen(function* () {
169
171
  description: tool.description,
170
172
  inputSchema: tool.inputSchema,
171
173
  outputSchema: tool.outputSchema,
174
+ callLabel: tool.callLabel,
172
175
  execute: callTool(tool)
173
176
  }));
174
177
  const result = yield* Effect.tryPromise({
package/dist/tool.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tool.mjs","names":[],"sources":["../src/tool.ts"],"sourcesContent":["import {\n Cause,\n Clock,\n Data,\n Duration,\n Effect,\n Exit,\n Fiber,\n FiberSet,\n Option,\n Predicate,\n Scope\n} from 'effect'\nimport * as Schema from 'effect/Schema'\nimport { ToolError } from '@yolk-sdk/agent/loop'\nimport {\n emptyNestedToolCallRecorder,\n nestedToolCallResultFields,\n recordNestedToolCall,\n contentPartText,\n ToolCall,\n ToolResult,\n type AgentUsage,\n type Content,\n type NestedToolCallInput,\n type NestedToolCallStatus\n} from '@yolk-sdk/agent/protocol'\nimport {\n makeTool,\n type NestedToolExecutor,\n type ToolExecutionInput,\n type ToolRegistration\n} from '@yolk-sdk/agent/tools'\nimport {\n codeModeCatalog,\n defaultCodeModeInlineBudget,\n describeCodeModeTool,\n findCodeModeTool,\n renderCodeModeDescription,\n type CodeModeCatalogTool\n} from './catalog.ts'\nimport type {\n CodeModeExecutionResult,\n CodeModeExecutor,\n CodeModeExecutorGlobal,\n CodeModeExecutorTool,\n CodeModeStore\n} from './executor.ts'\nimport {\n codeModeResultSegments,\n codeModeSegmentsContent,\n defaultCodeModeMaxImageBytes,\n defaultCodeModeMaxImages\n} from './output.ts'\nimport { searchCodeModeTools } from './search.ts'\nimport type { CodeModeStructuredContent } from './store.ts'\n\n/** Default name of the code mode tool. */\nexport const codeModeToolName = 'codemode'\n\n/** Host-owned limits of one script. */\nexport type CodeModeLimits = {\n /** Overall deadline including nested calls. Default 120000 (clamped by `deadline`). */\n readonly timeoutMs?: number\n /** VM heap cap. Default 64 MiB. */\n readonly memoryLimitBytes?: number\n /** Nested tool calls per script; further calls reject with an Error. Default 256. */\n readonly maxNestedCalls?: number\n /** Model-visible result characters, cut head and tail with an omission marker. Default 40000. */\n readonly maxOutputChars?: number\n /** Images kept in the result, in order; later images are dropped with a note. Default 8. */\n readonly maxImages?: number\n /** Base64 characters of images kept in the result in total; an image that would exceed it is\n * dropped with a note. Default 4 MiB.\n */\n readonly maxImageBytes?: number\n}\n\nexport const defaultCodeModeLimits = {\n timeoutMs: 120_000,\n memoryLimitBytes: 64 * 1024 * 1024,\n maxNestedCalls: 256,\n maxOutputChars: 40_000,\n maxImages: defaultCodeModeMaxImages,\n maxImageBytes: defaultCodeModeMaxImageBytes\n} as const satisfies Required<CodeModeLimits>\n\n/** Kept between a host deadline and the script deadline, for result handling. */\nexport const codeModeDeadlineMarginMs = 5_000\n\n/** A deadline never clamps a script below this timeout. */\nexport const codeModeMinimumTimeoutMs = 1_000\n\nexport type MakeCodeModeToolOptions<Context> = {\n readonly executor: CodeModeExecutor\n /** Default `codemode`. */\n readonly name?: string\n /** Estimated tokens (four characters each) for listing nested tools. Default 3000. */\n readonly inlineBudget?: number\n readonly limits?: CodeModeLimits\n /**\n * Epoch milliseconds by which the tool call must end, for example the remaining function budget\n * of a Workflow step. The script timeout is clamped to it minus 5 s, but never below 1 s.\n */\n readonly deadline?: (context: Context) => number | undefined\n /**\n * The store scripts read with `load(key)`, usually rebuilt from the transcript with\n * `codeModeStoreFromToolResults`. Without it every script starts with an empty store.\n */\n readonly loadStore?: (context: Context) => Effect.Effect<CodeModeStore, ToolError>\n /**\n * Runs before each nested call executes, with the nested call (id `<toolCallId>/<seq>`) and the\n * host context. A failure rejects that call in the script with the message and records it as an\n * `error` without executing it. Host decorators around the `ToolExecutor` (outside\n * `ResolvedToolSet.execute`) never see nested calls: put per-call run-authority checks here or in\n * registration-level wrappers.\n */\n readonly beforeNestedCall?: (input: {\n readonly call: ToolCall\n readonly context: Context\n }) => Effect.Effect<void, string>\n}\n\nconst CodeModeParams = Schema.Struct({\n code: Schema.String.annotate({\n description:\n 'Raw JavaScript: the body of an async function. Top-level await and return work. Not a function declaration and not a module.'\n })\n})\n\ntype CodeModeParams = typeof CodeModeParams.Type\n\nclass CodeModeExecutorRejected extends Data.TaggedError('CodeModeExecutorRejected')<{\n readonly message: string\n}> {}\n\nconst errorMessage = (error: unknown) =>\n error instanceof Error ? error.message : Predicate.isString(error) ? error : 'unknown error'\n\nconst nestedText = (content: Content) =>\n Predicate.isString(content)\n ? content\n : content\n .map(contentPartText)\n .filter(text => text.length > 0)\n .join('\\n')\n\ntype NestedResolution =\n | { readonly ok: true; readonly value: unknown }\n | { readonly ok: false; readonly message: string }\n\n/** What a nested call resolves to: `structuredContent` for tools with an output schema (when\n * present), text content otherwise; error results reject with their text.\n */\nconst resolveNestedResult = (tool: CodeModeCatalogTool, result: ToolResult): NestedResolution => {\n const text = nestedText(result.content)\n\n if (result.isError === true) {\n return { ok: false, message: text.length > 0 ? text : `Tool ${tool.name} failed.` }\n }\n\n return tool.structured && result.structuredContent !== undefined\n ? { ok: true, value: result.structuredContent }\n : { ok: true, value: text }\n}\n\ntype CallOutcome = {\n status: NestedToolCallStatus\n durationMs: number\n error?: string\n usage?: AgentUsage\n}\n\ntype CallRecord = {\n readonly name: string\n readonly args: unknown\n readonly id: string\n outcome?: CallOutcome\n}\n\nconst recordedOutcome = (exit: Exit.Exit<ToolResult>, durationMs: number): CallOutcome => {\n if (Exit.isFailure(exit)) {\n return Cause.hasInterruptsOnly(exit.cause)\n ? { status: 'cancelled', durationMs }\n : { status: 'error', durationMs, error: 'The tool call failed unexpectedly.' }\n }\n\n const result = exit.value\n\n const outcome: CallOutcome =\n result.isError === true\n ? { status: 'error', durationMs, error: nestedText(result.content) }\n : { status: 'ok', durationMs }\n\n if (result.usage !== undefined) {\n outcome.usage = result.usage\n }\n\n return outcome\n}\n\n/** What `describeNamespace(name)` resolves to. */\ntype CodeModeNamespaceDescription = {\n readonly name: string\n description?: string\n readonly tools: ReadonlyArray<{ readonly name: string; readonly description: string }>\n}\n\nconst SearchOptions = Schema.Struct({\n limit: Schema.optionalKey(Schema.Finite),\n namespace: Schema.optionalKey(Schema.String)\n})\n\nconst decodeSearchOptions = Schema.decodeUnknownOption(SearchOptions)\n\n/** `searchTools`, `describeTool`, and `describeNamespace` over every nested tool. */\nconst discoveryGlobals = (\n catalog: ReadonlyArray<CodeModeCatalogTool>\n): ReadonlyArray<CodeModeExecutorGlobal> => [\n {\n name: 'searchTools',\n spread: true,\n execute: args => {\n const [query, options] = Array.isArray(args) ? args : []\n\n return Promise.resolve(\n searchCodeModeTools(\n catalog,\n Predicate.isString(query) ? query : '',\n Option.getOrElse(decodeSearchOptions(options ?? {}), () => ({}))\n )\n )\n }\n },\n {\n name: 'describeTool',\n execute: name => {\n const tool = Predicate.isString(name) ? findCodeModeTool(catalog, name) : undefined\n\n return Promise.resolve(tool === undefined ? undefined : describeCodeModeTool(tool))\n }\n },\n {\n name: 'describeNamespace',\n execute: name => {\n const tools = catalog.filter(tool => tool.namespace === name)\n\n if (!Predicate.isString(name) || tools.length === 0) return Promise.resolve(undefined)\n\n const description = tools.find(\n tool => tool.namespaceDescription !== undefined\n )?.namespaceDescription\n\n const namespace: CodeModeNamespaceDescription = {\n name,\n tools: tools.map(tool => ({ name: tool.identifier, description: tool.description }))\n }\n\n if (description !== undefined) namespace.description = description\n\n return Promise.resolve(namespace)\n }\n }\n]\n\nconst hasStoreWrites = (result: CodeModeExecutionResult) =>\n result.storeWrites !== undefined &&\n (Object.keys(result.storeWrites.set).length > 0 || result.storeWrites.delete.length > 0)\n\nconst timeoutFor = (limit: number, deadline: number | undefined, now: number) =>\n deadline === undefined\n ? limit\n : Math.max(codeModeMinimumTimeoutMs, Math.min(limit, deadline - now - codeModeDeadlineMarginMs))\n\n/**\n * Waits at most `codeModeDeadlineMarginMs` for an effect (interrupting nested fibers) to finish; it\n * keeps running detached when a nested call does not respond to interruption in time.\n */\nconst boundedWait = (effect: Effect.Effect<void>) =>\n Effect.forkDetach(effect).pipe(\n Effect.flatMap(fiber =>\n Fiber.await(fiber).pipe(\n Effect.timeoutOption(Duration.millis(codeModeDeadlineMarginMs)),\n Effect.interruptible\n )\n ),\n Effect.asVoid\n )\n\ntype RunInput<Context> = {\n readonly options: MakeCodeModeToolOptions<Context>\n readonly limits: Required<CodeModeLimits>\n readonly call: ToolCall\n readonly context: Context\n readonly code: string\n readonly nested: NestedToolExecutor\n}\n\nconst runScript = <Context>(input: RunInput<Context>): Effect.Effect<ToolResult, ToolError> =>\n Effect.gen(function* () {\n const { call, limits, nested, options } = input\n const store = options.loadStore === undefined ? {} : yield* options.loadStore(input.context)\n const startedAt = yield* Clock.currentTimeMillis\n const timeoutMs = timeoutFor(limits.timeoutMs, options.deadline?.(input.context), startedAt)\n const catalog = codeModeCatalog(nested.tools)\n\n return yield* Effect.acquireUseRelease(\n Scope.make(),\n scope =>\n Effect.gen(function* () {\n // Nested calls run as fibers of this tool call (same services, interrupted with it).\n const fibers = yield* FiberSet.make<ToolResult>().pipe(Scope.provide(scope))\n const runFork = yield* FiberSet.runtime(fibers)<never>()\n const records: Array<CallRecord> = []\n\n const callTool =\n (tool: CodeModeCatalogTool): CodeModeExecutorTool['execute'] =>\n (args, { signal }) => {\n if (signal.aborted) {\n return Promise.reject(new Error(`tools.${tool.identifier} was cancelled.`))\n }\n\n if (records.length >= limits.maxNestedCalls) {\n return Promise.reject(\n new Error(\n `Nested call limit reached: a script may make at most ${limits.maxNestedCalls} tool calls. Batch the work or return partial results.`\n )\n )\n }\n\n const params = args === undefined ? {} : args\n\n const record: CallRecord = {\n id: `${call.id}/${records.length + 1}`,\n name: tool.name,\n args: params\n }\n\n records.push(record)\n\n const nestedCall = ToolCall.make({ id: record.id, name: tool.name, params })\n\n const admitted =\n options.beforeNestedCall === undefined\n ? nested.execute(nestedCall)\n : options.beforeNestedCall({ call: nestedCall, context: input.context }).pipe(\n Effect.matchEffect({\n onFailure: message =>\n Effect.succeed(\n ToolResult.make({\n toolCallId: nestedCall.id,\n content: message,\n isError: true\n })\n ),\n onSuccess: () => nested.execute(nestedCall)\n })\n )\n\n const execution = Effect.gen(function* () {\n const started = yield* Clock.currentTimeMillis\n\n return yield* admitted.pipe(\n Effect.onExit(exit =>\n Effect.map(Clock.currentTimeMillis, finished => {\n record.outcome = recordedOutcome(exit, finished - started)\n })\n )\n )\n })\n\n return new Promise((resolve, reject) => {\n runFork(execution, { signal }).addObserver(exit => {\n if (Exit.isFailure(exit)) {\n reject(\n new Error(\n Cause.hasInterruptsOnly(exit.cause)\n ? `tools.${tool.identifier} was cancelled.`\n : `tools.${tool.identifier} failed unexpectedly.`\n )\n )\n\n return\n }\n\n const resolution = resolveNestedResult(tool, exit.value)\n\n if (resolution.ok) {\n resolve(resolution.value)\n } else {\n reject(new Error(resolution.message))\n }\n })\n })\n }\n\n const tools: ReadonlyArray<CodeModeExecutorTool> = catalog.map(tool => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n outputSchema: tool.outputSchema,\n execute: callTool(tool)\n }))\n\n const result = yield* Effect.tryPromise({\n try: signal =>\n options.executor.execute(input.code, {\n tools,\n globals: discoveryGlobals(catalog),\n timeoutMs,\n memoryLimitBytes: limits.memoryLimitBytes,\n store,\n signal\n }),\n catch: error => new CodeModeExecutorRejected({ message: errorMessage(error) })\n }).pipe(\n Effect.catch(error =>\n Effect.succeed<CodeModeExecutionResult>({\n ok: false,\n error: {\n kind: 'sandbox',\n message: `The code mode executor failed: ${error.message}`\n },\n output: []\n })\n ),\n // Backstop for executors that miss their own deadline: interrupting aborts the signal.\n Effect.timeoutOption(Duration.millis(timeoutMs + codeModeDeadlineMarginMs)),\n Effect.map(\n Option.getOrElse((): CodeModeExecutionResult => ({\n ok: false,\n error: {\n kind: 'timeout',\n message: `Execution timed out after ${timeoutMs} ms (the executor did not stop in time)`\n },\n output: []\n }))\n )\n )\n\n // Calls still running when the script ended are cancelled and recorded as such.\n yield* boundedWait(FiberSet.clear(fibers))\n\n const finishedAt = yield* Clock.currentTimeMillis\n\n const recorded = records.map((record): NestedToolCallInput => ({\n id: record.id,\n name: record.name,\n args: record.args,\n // A call interrupted before it started has no outcome.\n ...(record.outcome ?? { status: 'cancelled' })\n }))\n\n const recorder = recorded.reduce(recordNestedToolCall, emptyNestedToolCallRecorder)\n const { nestedCalls, usage } = nestedToolCallResultFields(recorder)\n\n const content = codeModeSegmentsContent(\n codeModeResultSegments({\n result,\n wallTimeMs: finishedAt - startedAt,\n calls: recorded,\n maxChars: limits.maxOutputChars,\n maxImages: limits.maxImages,\n maxImageBytes: limits.maxImageBytes\n })\n )\n\n const structuredContent: CodeModeStructuredContent = {\n codemode:\n result.ok && result.storeWrites !== undefined && hasStoreWrites(result)\n ? { ok: true, storeWrites: result.storeWrites }\n : { ok: result.ok }\n }\n\n type ResultFields = {\n toolCallId: string\n content: Content\n isError?: boolean\n structuredContent: CodeModeStructuredContent\n nestedCalls: typeof nestedCalls\n usage?: AgentUsage\n }\n\n const fields: ResultFields = {\n toolCallId: call.id,\n content,\n structuredContent,\n nestedCalls\n }\n\n if (!result.ok) {\n fields.isError = true\n }\n\n if (usage !== undefined) {\n fields.usage = usage\n }\n\n return ToolResult.make(fields)\n }),\n (scope, exit) => boundedWait(Scope.close(scope, exit))\n )\n })\n\n/**\n * The code mode tool: one registration (default name `codemode`, input `{ code }`) whose scripts\n * call the other code-mode-callable tools of the same `resolveTools` resolution. Nested calls run\n * through the resolved execute path with the same host context (input decoding, enablement,\n * registration wrappers), get ids `<toolCallId>/<seq>`, and are recorded on the result's\n * `nestedCalls`. The resolved description lists the callable tools (see\n * `renderCodeModeDescription`).\n *\n * Access is `write`: scripts can call any write tool the resolution exposes to code mode; each\n * nested call keeps its own access metadata and host wrappers.\n */\nexport const makeCodeModeTool = <Context>(\n options: MakeCodeModeToolOptions<Context>\n): ToolRegistration<Context> => {\n const name = options.name ?? codeModeToolName\n const limits: Required<CodeModeLimits> = { ...defaultCodeModeLimits, ...options.limits }\n const inlineBudget = options.inlineBudget ?? defaultCodeModeInlineBudget\n const store = options.loadStore !== undefined\n\n return makeTool<Context, typeof CodeModeParams>({\n name,\n description: renderCodeModeDescription({ tools: [], inlineBudget, store }),\n parameters: CodeModeParams,\n access: 'write',\n nestedToolAccess: true,\n describe: ({ tools }) => renderCodeModeDescription({ tools, inlineBudget, store }),\n execute: ({\n call,\n context,\n params,\n nested\n }: ToolExecutionInput<Context> & { readonly params: CodeModeParams }) =>\n nested === undefined\n ? Effect.fail(\n new ToolError({\n tool: name,\n cause: 'unavailable',\n message: `${name} requires nested tool access; resolve it with resolveTools.`\n })\n )\n : runScript({ options, limits, call, context, code: params.code, nested })\n })\n}\n"],"mappings":";;;;;;;;;;AA0DA,MAAa,mBAAmB;AAoBhC,MAAa,wBAAwB;CACnC,WAAW;CACX,kBAAkB,KAAK,OAAO;CAC9B,gBAAgB;CAChB,gBAAgB;CAChB,WAAA;CACA,eAAe;AACjB;;AAGA,MAAa,2BAA2B;;AAGxC,MAAa,2BAA2B;AAgCxC,MAAM,iBAAiB,OAAO,OAAO,EACnC,MAAM,OAAO,OAAO,SAAS,EAC3B,aACE,+HACJ,CAAC,EACH,CAAC;AAID,IAAM,2BAAN,cAAuC,KAAK,YAAY,0BAA0B,EAE/E,CAAC;AAEJ,MAAM,gBAAgB,UACpB,iBAAiB,QAAQ,MAAM,UAAU,UAAU,SAAS,KAAK,IAAI,QAAQ;AAE/E,MAAM,cAAc,YAClB,UAAU,SAAS,OAAO,IACtB,UACA,QACG,IAAI,eAAe,EACnB,QAAO,SAAQ,KAAK,SAAS,CAAC,EAC9B,KAAK,IAAI;;;;AASlB,MAAM,uBAAuB,MAA2B,WAAyC;CAC/F,MAAM,OAAO,WAAW,OAAO,OAAO;CAEtC,IAAI,OAAO,YAAY,MACrB,OAAO;EAAE,IAAI;EAAO,SAAS,KAAK,SAAS,IAAI,OAAO,QAAQ,KAAK,KAAK;CAAU;CAGpF,OAAO,KAAK,cAAc,OAAO,sBAAsB,KAAA,IACnD;EAAE,IAAI;EAAM,OAAO,OAAO;CAAkB,IAC5C;EAAE,IAAI;EAAM,OAAO;CAAK;AAC9B;AAgBA,MAAM,mBAAmB,MAA6B,eAAoC;CACxF,IAAI,KAAK,UAAU,IAAI,GACrB,OAAO,MAAM,kBAAkB,KAAK,KAAK,IACrC;EAAE,QAAQ;EAAa;CAAW,IAClC;EAAE,QAAQ;EAAS;EAAY,OAAO;CAAqC;CAGjF,MAAM,SAAS,KAAK;CAEpB,MAAM,UACJ,OAAO,YAAY,OACf;EAAE,QAAQ;EAAS;EAAY,OAAO,WAAW,OAAO,OAAO;CAAE,IACjE;EAAE,QAAQ;EAAM;CAAW;CAEjC,IAAI,OAAO,UAAU,KAAA,GACnB,QAAQ,QAAQ,OAAO;CAGzB,OAAO;AACT;AASA,MAAM,gBAAgB,OAAO,OAAO;CAClC,OAAO,OAAO,YAAY,OAAO,MAAM;CACvC,WAAW,OAAO,YAAY,OAAO,MAAM;AAC7C,CAAC;AAED,MAAM,sBAAsB,OAAO,oBAAoB,aAAa;;AAGpE,MAAM,oBACJ,YAC0C;CAC1C;EACE,MAAM;EACN,QAAQ;EACR,UAAS,SAAQ;GACf,MAAM,CAAC,OAAO,WAAW,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC;GAEvD,OAAO,QAAQ,QACb,oBACE,SACA,UAAU,SAAS,KAAK,IAAI,QAAQ,IACpC,OAAO,UAAU,oBAAoB,WAAW,CAAC,CAAC,UAAU,CAAC,EAAE,CACjE,CACF;EACF;CACF;CACA;EACE,MAAM;EACN,UAAS,SAAQ;GACf,MAAM,OAAO,UAAU,SAAS,IAAI,IAAI,iBAAiB,SAAS,IAAI,IAAI,KAAA;GAE1E,OAAO,QAAQ,QAAQ,SAAS,KAAA,IAAY,KAAA,IAAY,qBAAqB,IAAI,CAAC;EACpF;CACF;CACA;EACE,MAAM;EACN,UAAS,SAAQ;GACf,MAAM,QAAQ,QAAQ,QAAO,SAAQ,KAAK,cAAc,IAAI;GAE5D,IAAI,CAAC,UAAU,SAAS,IAAI,KAAK,MAAM,WAAW,GAAG,OAAO,QAAQ,QAAQ,KAAA,CAAS;GAErF,MAAM,cAAc,MAAM,MACxB,SAAQ,KAAK,yBAAyB,KAAA,CACxC,GAAG;GAEH,MAAM,YAA0C;IAC9C;IACA,OAAO,MAAM,KAAI,UAAS;KAAE,MAAM,KAAK;KAAY,aAAa,KAAK;IAAY,EAAE;GACrF;GAEA,IAAI,gBAAgB,KAAA,GAAW,UAAU,cAAc;GAEvD,OAAO,QAAQ,QAAQ,SAAS;EAClC;CACF;AACF;AAEA,MAAM,kBAAkB,WACtB,OAAO,gBAAgB,KAAA,MACtB,OAAO,KAAK,OAAO,YAAY,GAAG,EAAE,SAAS,KAAK,OAAO,YAAY,OAAO,SAAS;AAExF,MAAM,cAAc,OAAe,UAA8B,QAC/D,aAAa,KAAA,IACT,QACA,KAAK,IAAI,0BAA0B,KAAK,IAAI,OAAO,WAAW,MAAM,wBAAwB,CAAC;;;;;AAMnG,MAAM,eAAe,WACnB,OAAO,WAAW,MAAM,EAAE,KACxB,OAAO,SAAQ,UACb,MAAM,MAAM,KAAK,EAAE,KACjB,OAAO,cAAc,SAAS,OAAO,wBAAwB,CAAC,GAC9D,OAAO,aACT,CACF,GACA,OAAO,MACT;AAWF,MAAM,aAAsB,UAC1B,OAAO,IAAI,aAAa;CACtB,MAAM,EAAE,MAAM,QAAQ,QAAQ,YAAY;CAC1C,MAAM,QAAQ,QAAQ,cAAc,KAAA,IAAY,CAAC,IAAI,OAAO,QAAQ,UAAU,MAAM,OAAO;CAC3F,MAAM,YAAY,OAAO,MAAM;CAC/B,MAAM,YAAY,WAAW,OAAO,WAAW,QAAQ,WAAW,MAAM,OAAO,GAAG,SAAS;CAC3F,MAAM,UAAU,gBAAgB,OAAO,KAAK;CAE5C,OAAO,OAAO,OAAO,kBACnB,MAAM,KAAK,IACX,UACE,OAAO,IAAI,aAAa;EAEtB,MAAM,SAAS,OAAO,SAAS,KAAiB,EAAE,KAAK,MAAM,QAAQ,KAAK,CAAC;EAC3E,MAAM,UAAU,OAAO,SAAS,QAAQ,MAAM,EAAS;EACvD,MAAM,UAA6B,CAAC;EAEpC,MAAM,YACH,UACA,MAAM,EAAE,aAAa;GACpB,IAAI,OAAO,SACT,OAAO,QAAQ,uBAAO,IAAI,MAAM,SAAS,KAAK,WAAW,gBAAgB,CAAC;GAG5E,IAAI,QAAQ,UAAU,OAAO,gBAC3B,OAAO,QAAQ,uBACb,IAAI,MACF,wDAAwD,OAAO,eAAe,uDAChF,CACF;GAGF,MAAM,SAAS,SAAS,KAAA,IAAY,CAAC,IAAI;GAEzC,MAAM,SAAqB;IACzB,IAAI,GAAG,KAAK,GAAG,GAAG,QAAQ,SAAS;IACnC,MAAM,KAAK;IACX,MAAM;GACR;GAEA,QAAQ,KAAK,MAAM;GAEnB,MAAM,aAAa,SAAS,KAAK;IAAE,IAAI,OAAO;IAAI,MAAM,KAAK;IAAM;GAAO,CAAC;GAE3E,MAAM,WACJ,QAAQ,qBAAqB,KAAA,IACzB,OAAO,QAAQ,UAAU,IACzB,QAAQ,iBAAiB;IAAE,MAAM;IAAY,SAAS,MAAM;GAAQ,CAAC,EAAE,KACrE,OAAO,YAAY;IACjB,YAAW,YACT,OAAO,QACL,WAAW,KAAK;KACd,YAAY,WAAW;KACvB,SAAS;KACT,SAAS;IACX,CAAC,CACH;IACF,iBAAiB,OAAO,QAAQ,UAAU;GAC5C,CAAC,CACH;GAEN,MAAM,YAAY,OAAO,IAAI,aAAa;IACxC,MAAM,UAAU,OAAO,MAAM;IAE7B,OAAO,OAAO,SAAS,KACrB,OAAO,QAAO,SACZ,OAAO,IAAI,MAAM,oBAAmB,aAAY;KAC9C,OAAO,UAAU,gBAAgB,MAAM,WAAW,OAAO;IAC3D,CAAC,CACH,CACF;GACF,CAAC;GAED,OAAO,IAAI,SAAS,SAAS,WAAW;IACtC,QAAQ,WAAW,EAAE,OAAO,CAAC,EAAE,aAAY,SAAQ;KACjD,IAAI,KAAK,UAAU,IAAI,GAAG;MACxB,uBACE,IAAI,MACF,MAAM,kBAAkB,KAAK,KAAK,IAC9B,SAAS,KAAK,WAAW,mBACzB,SAAS,KAAK,WAAW,sBAC/B,CACF;MAEA;KACF;KAEA,MAAM,aAAa,oBAAoB,MAAM,KAAK,KAAK;KAEvD,IAAI,WAAW,IACb,QAAQ,WAAW,KAAK;UAExB,OAAO,IAAI,MAAM,WAAW,OAAO,CAAC;IAExC,CAAC;GACH,CAAC;EACH;EAEF,MAAM,QAA6C,QAAQ,KAAI,UAAS;GACtE,MAAM,KAAK;GACX,aAAa,KAAK;GAClB,aAAa,KAAK;GAClB,cAAc,KAAK;GACnB,SAAS,SAAS,IAAI;EACxB,EAAE;EAEF,MAAM,SAAS,OAAO,OAAO,WAAW;GACtC,MAAK,WACH,QAAQ,SAAS,QAAQ,MAAM,MAAM;IACnC;IACA,SAAS,iBAAiB,OAAO;IACjC;IACA,kBAAkB,OAAO;IACzB;IACA;GACF,CAAC;GACH,QAAO,UAAS,IAAI,yBAAyB,EAAE,SAAS,aAAa,KAAK,EAAE,CAAC;EAC/E,CAAC,EAAE,KACD,OAAO,OAAM,UACX,OAAO,QAAiC;GACtC,IAAI;GACJ,OAAO;IACL,MAAM;IACN,SAAS,kCAAkC,MAAM;GACnD;GACA,QAAQ,CAAC;EACX,CAAC,CACH,GAEA,OAAO,cAAc,SAAS,OAAO,YAAY,wBAAwB,CAAC,GAC1E,OAAO,IACL,OAAO,iBAA0C;GAC/C,IAAI;GACJ,OAAO;IACL,MAAM;IACN,SAAS,6BAA6B,UAAU;GAClD;GACA,QAAQ,CAAC;EACX,EAAE,CACJ,CACF;EAGA,OAAO,YAAY,SAAS,MAAM,MAAM,CAAC;EAEzC,MAAM,aAAa,OAAO,MAAM;EAEhC,MAAM,WAAW,QAAQ,KAAK,YAAiC;GAC7D,IAAI,OAAO;GACX,MAAM,OAAO;GACb,MAAM,OAAO;GAEb,GAAI,OAAO,WAAW,EAAE,QAAQ,YAAY;EAC9C,EAAE;EAGF,MAAM,EAAE,aAAa,UAAU,2BADd,SAAS,OAAO,sBAAsB,2BACU,CAAC;EAElE,MAAM,UAAU,wBACd,uBAAuB;GACrB;GACA,YAAY,aAAa;GACzB,OAAO;GACP,UAAU,OAAO;GACjB,WAAW,OAAO;GAClB,eAAe,OAAO;EACxB,CAAC,CACH;EAEA,MAAM,oBAA+C,EACnD,UACE,OAAO,MAAM,OAAO,gBAAgB,KAAA,KAAa,eAAe,MAAM,IAClE;GAAE,IAAI;GAAM,aAAa,OAAO;EAAY,IAC5C,EAAE,IAAI,OAAO,GAAG,EACxB;EAWA,MAAM,SAAuB;GAC3B,YAAY,KAAK;GACjB;GACA;GACA;EACF;EAEA,IAAI,CAAC,OAAO,IACV,OAAO,UAAU;EAGnB,IAAI,UAAU,KAAA,GACZ,OAAO,QAAQ;EAGjB,OAAO,WAAW,KAAK,MAAM;CAC/B,CAAC,IACF,OAAO,SAAS,YAAY,MAAM,MAAM,OAAO,IAAI,CAAC,CACvD;AACF,CAAC;;;;;;;;;;;;AAaH,MAAa,oBACX,YAC8B;CAC9B,MAAM,OAAO,QAAQ,QAAA;CACrB,MAAM,SAAmC;EAAE,GAAG;EAAuB,GAAG,QAAQ;CAAO;CACvF,MAAM,eAAe,QAAQ,gBAAA;CAC7B,MAAM,QAAQ,QAAQ,cAAc,KAAA;CAEpC,OAAO,SAAyC;EAC9C;EACA,aAAa,0BAA0B;GAAE,OAAO,CAAC;GAAG;GAAc;EAAM,CAAC;EACzE,YAAY;EACZ,QAAQ;EACR,kBAAkB;EAClB,WAAW,EAAE,YAAY,0BAA0B;GAAE;GAAO;GAAc;EAAM,CAAC;EACjF,UAAU,EACR,MACA,SACA,QACA,aAEA,WAAW,KAAA,IACP,OAAO,KACL,IAAI,UAAU;GACZ,MAAM;GACN,OAAO;GACP,SAAS,GAAG,KAAK;EACnB,CAAC,CACH,IACA,UAAU;GAAE;GAAS;GAAQ;GAAM;GAAS,MAAM,OAAO;GAAM;EAAO,CAAC;CAC/E,CAAC;AACH"}
1
+ {"version":3,"file":"tool.mjs","names":[],"sources":["../src/tool.ts"],"sourcesContent":["import {\n Cause,\n Clock,\n Data,\n Duration,\n Effect,\n Exit,\n Fiber,\n FiberSet,\n Option,\n Predicate,\n Scope\n} from 'effect'\nimport * as Schema from 'effect/Schema'\nimport { ToolError } from '@yolk-sdk/agent/loop'\nimport {\n emptyNestedToolCallRecorder,\n nestedToolCallResultFields,\n recordNestedToolCall,\n contentPartText,\n ToolCall,\n ToolResult,\n type AgentUsage,\n type Content,\n type NestedToolCallInput,\n type NestedToolCallStatus\n} from '@yolk-sdk/agent/protocol'\nimport {\n makeTool,\n type NestedToolExecutor,\n type ToolExecutionInput,\n type ToolRegistration\n} from '@yolk-sdk/agent/tools'\nimport {\n codeModeCatalog,\n defaultCodeModeInlineBudget,\n describeCodeModeTool,\n findCodeModeTool,\n renderCodeModeDescription,\n type CodeModeCatalogTool\n} from './catalog.ts'\nimport type {\n CodeModeExecutionResult,\n CodeModeExecutor,\n CodeModeExecutorGlobal,\n CodeModeExecutorTool,\n CodeModeStore\n} from './executor.ts'\nimport {\n codeModeResultSegments,\n codeModeSegmentsContent,\n defaultCodeModeMaxImageBytes,\n defaultCodeModeMaxImages\n} from './output.ts'\nimport { searchCodeModeTools } from './search.ts'\nimport type { CodeModeStructuredContent } from './store.ts'\n\n/** Default name of the code mode tool. */\nexport const codeModeToolName = 'codemode'\n\n/** Host-owned limits of one script. */\nexport type CodeModeLimits = {\n /** Overall deadline including nested calls. Default 120000 (clamped by `deadline`). */\n readonly timeoutMs?: number\n /** VM heap cap. Default 64 MiB. */\n readonly memoryLimitBytes?: number\n /** Nested tool calls per script; further calls reject with an Error. Default 256. */\n readonly maxNestedCalls?: number\n /** Model-visible result characters, cut head and tail with an omission marker. Default 40000. */\n readonly maxOutputChars?: number\n /** Images kept in the result, in order; later images are dropped with a note. Default 8. */\n readonly maxImages?: number\n /** Base64 characters of images kept in the result in total; an image that would exceed it is\n * dropped with a note. Default 4 MiB.\n */\n readonly maxImageBytes?: number\n}\n\nexport const defaultCodeModeLimits = {\n timeoutMs: 120_000,\n memoryLimitBytes: 64 * 1024 * 1024,\n maxNestedCalls: 256,\n maxOutputChars: 40_000,\n maxImages: defaultCodeModeMaxImages,\n maxImageBytes: defaultCodeModeMaxImageBytes\n} as const satisfies Required<CodeModeLimits>\n\n/** Kept between a host deadline and the script deadline, for result handling. */\nexport const codeModeDeadlineMarginMs = 5_000\n\n/** A deadline never clamps a script below this timeout. */\nexport const codeModeMinimumTimeoutMs = 1_000\n\nexport type MakeCodeModeToolOptions<Context> = {\n readonly executor: CodeModeExecutor\n /** Default `codemode`. */\n readonly name?: string\n /** Estimated tokens (four characters each) for listing nested tools. Default 3000. */\n readonly inlineBudget?: number\n readonly limits?: CodeModeLimits\n /**\n * Epoch milliseconds by which the tool call must end, for example the remaining function budget\n * of a Workflow step. The script timeout is clamped to it minus 5 s, but never below 1 s.\n */\n readonly deadline?: (context: Context) => number | undefined\n /**\n * The store scripts read with `load(key)`, usually rebuilt from the transcript with\n * `codeModeStoreFromToolResults`. Without it every script starts with an empty store.\n */\n readonly loadStore?: (context: Context) => Effect.Effect<CodeModeStore, ToolError>\n /**\n * Runs before each nested call executes, with the nested call (id `<toolCallId>/<seq>`) and the\n * host context. A failure rejects that call in the script with the message and records it as an\n * `error` without executing it. Host decorators around the `ToolExecutor` (outside\n * `ResolvedToolSet.execute`) never see nested calls: put per-call run-authority checks here or in\n * registration-level wrappers.\n */\n readonly beforeNestedCall?: (input: {\n readonly call: ToolCall\n readonly context: Context\n }) => Effect.Effect<void, string>\n}\n\nconst CodeModeParams = Schema.Struct({\n code: Schema.String.annotate({\n description:\n 'Raw JavaScript: the body of an async function. Top-level await and return work. Not a function declaration and not a module.'\n })\n})\n\ntype CodeModeParams = typeof CodeModeParams.Type\n\nclass CodeModeExecutorRejected extends Data.TaggedError('CodeModeExecutorRejected')<{\n readonly message: string\n}> {}\n\nconst errorMessage = (error: unknown) =>\n error instanceof Error ? error.message : Predicate.isString(error) ? error : 'unknown error'\n\nconst nestedText = (content: Content) =>\n Predicate.isString(content)\n ? content\n : content\n .map(contentPartText)\n .filter(text => text.length > 0)\n .join('\\n')\n\ntype NestedResolution =\n | { readonly ok: true; readonly value: unknown }\n | { readonly ok: false; readonly message: string }\n\n/** What a nested call resolves to: `structuredContent` for tools with an output schema (when\n * present), text content otherwise; error results reject with their text. A tool that declares an\n * output schema but returns no `structuredContent` resolves to its text: that is a tool bug, kept\n * readable rather than turned into a failure.\n */\nconst resolveNestedResult = (tool: CodeModeCatalogTool, result: ToolResult): NestedResolution => {\n const text = nestedText(result.content)\n\n if (result.isError === true) {\n return { ok: false, message: text.length > 0 ? text : `Tool ${tool.name} failed.` }\n }\n\n return tool.structured && result.structuredContent !== undefined\n ? { ok: true, value: result.structuredContent }\n : { ok: true, value: text }\n}\n\ntype CallOutcome = {\n status: NestedToolCallStatus\n durationMs: number\n error?: string\n usage?: AgentUsage\n}\n\ntype CallRecord = {\n readonly name: string\n readonly args: unknown\n readonly id: string\n outcome?: CallOutcome\n}\n\nconst recordedOutcome = (exit: Exit.Exit<ToolResult>, durationMs: number): CallOutcome => {\n if (Exit.isFailure(exit)) {\n return Cause.hasInterruptsOnly(exit.cause)\n ? { status: 'cancelled', durationMs }\n : { status: 'error', durationMs, error: 'The tool call failed unexpectedly.' }\n }\n\n const result = exit.value\n\n const outcome: CallOutcome =\n result.isError === true\n ? { status: 'error', durationMs, error: nestedText(result.content) }\n : { status: 'ok', durationMs }\n\n if (result.usage !== undefined) {\n outcome.usage = result.usage\n }\n\n return outcome\n}\n\n/** What `describeNamespace(name)` resolves to. */\ntype CodeModeNamespaceDescription = {\n readonly name: string\n description?: string\n readonly tools: ReadonlyArray<{ readonly name: string; readonly description: string }>\n}\n\nconst SearchOptions = Schema.Struct({\n limit: Schema.optionalKey(Schema.Finite),\n namespace: Schema.optionalKey(Schema.String)\n})\n\nconst decodeSearchOptions = Schema.decodeUnknownOption(SearchOptions)\n\n/** `searchTools`, `describeTool`, and `describeNamespace` over every nested tool. */\nconst discoveryGlobals = (\n catalog: ReadonlyArray<CodeModeCatalogTool>\n): ReadonlyArray<CodeModeExecutorGlobal> => [\n {\n name: 'searchTools',\n spread: true,\n execute: args => {\n const [query, options] = Array.isArray(args) ? args : []\n\n return Promise.resolve(\n searchCodeModeTools(\n catalog,\n Predicate.isString(query) ? query : '',\n Option.getOrElse(decodeSearchOptions(options ?? {}), () => ({}))\n )\n )\n }\n },\n {\n name: 'describeTool',\n execute: name => {\n const tool = Predicate.isString(name) ? findCodeModeTool(catalog, name) : undefined\n\n return Promise.resolve(tool === undefined ? undefined : describeCodeModeTool(tool))\n }\n },\n {\n name: 'describeNamespace',\n execute: name => {\n const tools = catalog.filter(tool => tool.namespace === name)\n\n if (!Predicate.isString(name) || tools.length === 0) return Promise.resolve(undefined)\n\n const description = tools.find(\n tool => tool.namespaceDescription !== undefined\n )?.namespaceDescription\n\n const namespace: CodeModeNamespaceDescription = {\n name,\n tools: tools.map(tool => ({ name: tool.identifier, description: tool.description }))\n }\n\n if (description !== undefined) namespace.description = description\n\n return Promise.resolve(namespace)\n }\n }\n]\n\nconst hasStoreWrites = (result: CodeModeExecutionResult) =>\n result.storeWrites !== undefined &&\n (Object.keys(result.storeWrites.set).length > 0 || result.storeWrites.delete.length > 0)\n\nconst timeoutFor = (limit: number, deadline: number | undefined, now: number) =>\n deadline === undefined\n ? limit\n : Math.max(codeModeMinimumTimeoutMs, Math.min(limit, deadline - now - codeModeDeadlineMarginMs))\n\n/**\n * Waits at most `codeModeDeadlineMarginMs` for an effect (interrupting nested fibers) to finish; it\n * keeps running detached when a nested call does not respond to interruption in time.\n */\nconst boundedWait = (effect: Effect.Effect<void>) =>\n Effect.forkDetach(effect).pipe(\n Effect.flatMap(fiber =>\n Fiber.await(fiber).pipe(\n Effect.timeoutOption(Duration.millis(codeModeDeadlineMarginMs)),\n Effect.interruptible\n )\n ),\n Effect.asVoid\n )\n\ntype RunInput<Context> = {\n readonly options: MakeCodeModeToolOptions<Context>\n readonly limits: Required<CodeModeLimits>\n readonly call: ToolCall\n readonly context: Context\n readonly code: string\n readonly nested: NestedToolExecutor\n}\n\nconst runScript = <Context>(input: RunInput<Context>): Effect.Effect<ToolResult, ToolError> =>\n Effect.gen(function* () {\n const { call, limits, nested, options } = input\n const store = options.loadStore === undefined ? {} : yield* options.loadStore(input.context)\n const startedAt = yield* Clock.currentTimeMillis\n const timeoutMs = timeoutFor(limits.timeoutMs, options.deadline?.(input.context), startedAt)\n const catalog = codeModeCatalog(nested.tools)\n\n return yield* Effect.acquireUseRelease(\n Scope.make(),\n scope =>\n Effect.gen(function* () {\n // Nested calls run as fibers of this tool call (same services, interrupted with it).\n const fibers = yield* FiberSet.make<ToolResult>().pipe(Scope.provide(scope))\n const runFork = yield* FiberSet.runtime(fibers)<never>()\n const records: Array<CallRecord> = []\n\n const callTool =\n (tool: CodeModeCatalogTool): CodeModeExecutorTool['execute'] =>\n (args, { signal }) => {\n if (signal.aborted) {\n return Promise.reject(new Error(`${tool.callLabel} was cancelled.`))\n }\n\n if (records.length >= limits.maxNestedCalls) {\n return Promise.reject(\n new Error(\n `${tool.callLabel}: Nested call limit reached: a script may make at most ${limits.maxNestedCalls} tool calls. Batch the work or return partial results.`\n )\n )\n }\n\n const params = args === undefined ? {} : args\n\n const record: CallRecord = {\n id: `${call.id}/${records.length + 1}`,\n name: tool.name,\n args: params\n }\n\n records.push(record)\n\n const nestedCall = ToolCall.make({ id: record.id, name: tool.name, params })\n\n const admitted =\n options.beforeNestedCall === undefined\n ? nested.execute(nestedCall)\n : options.beforeNestedCall({ call: nestedCall, context: input.context }).pipe(\n Effect.matchEffect({\n onFailure: message =>\n Effect.succeed(\n ToolResult.make({\n toolCallId: nestedCall.id,\n content: message,\n isError: true\n })\n ),\n onSuccess: () => nested.execute(nestedCall)\n })\n )\n\n const execution = Effect.gen(function* () {\n const started = yield* Clock.currentTimeMillis\n\n return yield* admitted.pipe(\n Effect.onExit(exit =>\n Effect.map(Clock.currentTimeMillis, finished => {\n record.outcome = recordedOutcome(exit, finished - started)\n })\n )\n )\n })\n\n return new Promise((resolve, reject) => {\n runFork(execution, { signal }).addObserver(exit => {\n if (Exit.isFailure(exit)) {\n reject(\n new Error(\n Cause.hasInterruptsOnly(exit.cause)\n ? `${tool.callLabel} was cancelled.`\n : `${tool.callLabel} failed unexpectedly.`\n )\n )\n\n return\n }\n\n const resolution = resolveNestedResult(tool, exit.value)\n\n if (resolution.ok) {\n resolve(resolution.value)\n } else {\n // The script sees which call failed; the nested-call record keeps the raw text.\n reject(new Error(`${tool.callLabel}: ${resolution.message}`))\n }\n })\n })\n }\n\n const tools: ReadonlyArray<CodeModeExecutorTool> = catalog.map(tool => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n outputSchema: tool.outputSchema,\n callLabel: tool.callLabel,\n execute: callTool(tool)\n }))\n\n const result = yield* Effect.tryPromise({\n try: signal =>\n options.executor.execute(input.code, {\n tools,\n globals: discoveryGlobals(catalog),\n timeoutMs,\n memoryLimitBytes: limits.memoryLimitBytes,\n store,\n signal\n }),\n catch: error => new CodeModeExecutorRejected({ message: errorMessage(error) })\n }).pipe(\n Effect.catch(error =>\n Effect.succeed<CodeModeExecutionResult>({\n ok: false,\n error: {\n kind: 'sandbox',\n message: `The code mode executor failed: ${error.message}`\n },\n output: []\n })\n ),\n // Backstop for executors that miss their own deadline: interrupting aborts the signal.\n Effect.timeoutOption(Duration.millis(timeoutMs + codeModeDeadlineMarginMs)),\n Effect.map(\n Option.getOrElse((): CodeModeExecutionResult => ({\n ok: false,\n error: {\n kind: 'timeout',\n message: `Execution timed out after ${timeoutMs} ms (the executor did not stop in time)`\n },\n output: []\n }))\n )\n )\n\n // Calls still running when the script ended are cancelled and recorded as such.\n yield* boundedWait(FiberSet.clear(fibers))\n\n const finishedAt = yield* Clock.currentTimeMillis\n\n const recorded = records.map((record): NestedToolCallInput => ({\n id: record.id,\n name: record.name,\n args: record.args,\n // A call interrupted before it started has no outcome.\n ...(record.outcome ?? { status: 'cancelled' })\n }))\n\n const recorder = recorded.reduce(recordNestedToolCall, emptyNestedToolCallRecorder)\n const { nestedCalls, usage } = nestedToolCallResultFields(recorder)\n\n const content = codeModeSegmentsContent(\n codeModeResultSegments({\n result,\n wallTimeMs: finishedAt - startedAt,\n calls: recorded,\n maxChars: limits.maxOutputChars,\n maxImages: limits.maxImages,\n maxImageBytes: limits.maxImageBytes\n })\n )\n\n const structuredContent: CodeModeStructuredContent = {\n codemode:\n result.ok && result.storeWrites !== undefined && hasStoreWrites(result)\n ? { ok: true, storeWrites: result.storeWrites }\n : { ok: result.ok }\n }\n\n type ResultFields = {\n toolCallId: string\n content: Content\n isError?: boolean\n structuredContent: CodeModeStructuredContent\n nestedCalls: typeof nestedCalls\n usage?: AgentUsage\n }\n\n const fields: ResultFields = {\n toolCallId: call.id,\n content,\n structuredContent,\n nestedCalls\n }\n\n if (!result.ok) {\n fields.isError = true\n }\n\n if (usage !== undefined) {\n fields.usage = usage\n }\n\n return ToolResult.make(fields)\n }),\n (scope, exit) => boundedWait(Scope.close(scope, exit))\n )\n })\n\n/**\n * The code mode tool: one registration (default name `codemode`, input `{ code }`) whose scripts\n * call the other code-mode-callable tools of the same `resolveTools` resolution. Nested calls run\n * through the resolved execute path with the same host context (input decoding, enablement,\n * registration wrappers), get ids `<toolCallId>/<seq>`, and are recorded on the result's\n * `nestedCalls`. The resolved description lists the callable tools (see\n * `renderCodeModeDescription`).\n *\n * Access is `write`: scripts can call any write tool the resolution exposes to code mode; each\n * nested call keeps its own access metadata and host wrappers.\n */\nexport const makeCodeModeTool = <Context>(\n options: MakeCodeModeToolOptions<Context>\n): ToolRegistration<Context> => {\n const name = options.name ?? codeModeToolName\n const limits: Required<CodeModeLimits> = { ...defaultCodeModeLimits, ...options.limits }\n const inlineBudget = options.inlineBudget ?? defaultCodeModeInlineBudget\n const store = options.loadStore !== undefined\n\n return makeTool<Context, typeof CodeModeParams>({\n name,\n description: renderCodeModeDescription({ tools: [], inlineBudget, store }),\n parameters: CodeModeParams,\n access: 'write',\n nestedToolAccess: true,\n describe: ({ tools }) => renderCodeModeDescription({ tools, inlineBudget, store }),\n execute: ({\n call,\n context,\n params,\n nested\n }: ToolExecutionInput<Context> & { readonly params: CodeModeParams }) =>\n nested === undefined\n ? Effect.fail(\n new ToolError({\n tool: name,\n cause: 'unavailable',\n message: `${name} requires nested tool access; resolve it with resolveTools.`\n })\n )\n : runScript({ options, limits, call, context, code: params.code, nested })\n })\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yolk-sdk/codemode",
3
- "version": "0.1.0-canary.96",
3
+ "version": "0.1.0-canary.98",
4
4
  "description": "Code mode for Yolk agents: one tool that runs model-written JavaScript calling the host's resolved tools, plus a Node executor on a confined QuickJS sandbox.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -51,8 +51,8 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "@earendil-works/pi-codemode": "1.0.0",
54
- "effect": "4.0.0-rc.115",
55
- "@yolk-sdk/agent": "^0.1.0-canary.96"
54
+ "effect": "4.0.0",
55
+ "@yolk-sdk/agent": "^0.1.0-canary.98"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "tsdown",
package/src/catalog.ts CHANGED
@@ -20,6 +20,12 @@ export type CodeModeCatalogTool = {
20
20
  readonly name: string
21
21
  /** Identifier for `tools.<identifier>(args)` (invalid identifier characters become `_`). */
22
22
  readonly identifier: string
23
+ /**
24
+ * How scripts call this tool, used to prefix its rejections: `tools.<identifier>`, or
25
+ * `tools["<name>"]` when an earlier tool already has the identifier (see `codeModeCallLabels`;
26
+ * when the name is taken too, the tool is unreachable and the label only names it).
27
+ */
28
+ readonly callLabel: string
23
29
  /** The tool's `ToolModule.id`. */
24
30
  readonly namespace: string
25
31
  /** The module's `ToolModule.description`, when set. */
@@ -35,14 +41,38 @@ export type CodeModeCatalogTool = {
35
41
 
36
42
  const textOutputSchema: CodeModeJsonSchema = { type: 'string' }
37
43
 
44
+ /**
45
+ * The call label of each tool name, in order, as pi binds `tools`: each tool takes
46
+ * `tools[<identifier>]` unless an earlier tool holds that key (first tool wins), then
47
+ * `tools[<name>]` if still free. The label is `tools.<identifier>` for the tool that holds its
48
+ * identifier, otherwise `tools["<name>"]`. The executor guard and the host rejection prefix share
49
+ * it, so both name a call the same way.
50
+ */
51
+ export const codeModeCallLabels = (names: ReadonlyArray<string>): ReadonlyArray<string> => {
52
+ const bound = new Set<string>()
53
+
54
+ return names.map(name => {
55
+ const identifier = toCodemodeIdentifier(name)
56
+ const ownsIdentifier = !bound.has(identifier)
57
+
58
+ bound.add(identifier)
59
+ bound.add(name)
60
+
61
+ return ownsIdentifier ? `tools.${identifier}` : `tools[${JSON.stringify(name)}]`
62
+ })
63
+ }
64
+
38
65
  /** Catalog of nested tools in resolution order. */
39
66
  export const codeModeCatalog = (
40
67
  tools: ReadonlyArray<NestedTool>
41
- ): ReadonlyArray<CodeModeCatalogTool> =>
42
- tools.map(({ def, moduleId, moduleDescription }) => {
68
+ ): ReadonlyArray<CodeModeCatalogTool> => {
69
+ const labels = codeModeCallLabels(tools.map(({ def }) => def.name))
70
+
71
+ return tools.map(({ def, moduleId, moduleDescription }, index) => {
43
72
  const tool: CodeModeCatalogTool = {
44
73
  name: def.name,
45
74
  identifier: toCodemodeIdentifier(def.name),
75
+ callLabel: labels[index] ?? `tools[${JSON.stringify(def.name)}]`,
46
76
  namespace: moduleId,
47
77
  description: def.description,
48
78
  inputSchema: def.parameters,
@@ -55,6 +85,7 @@ export const codeModeCatalog = (
55
85
  ? tool
56
86
  : { ...tool, namespaceDescription: moduleDescription }
57
87
  })
88
+ }
58
89
 
59
90
  /** Default inline budget of the nested tool listing, in estimated tokens. */
60
91
  export const defaultCodeModeInlineBudget = 3000
@@ -186,7 +217,7 @@ export const selectCodeModeListing = (
186
217
  const intro = [
187
218
  'Run a JavaScript script that calls tools and returns only what matters.',
188
219
  '`code` is the body of an async function: top-level `await` and `return` work. Write plain JavaScript; simple TypeScript annotations are stripped. There is no Node.js, network, filesystem, `require`, `fetch`, or timers.',
189
- 'Call tools as `await tools.<id>(args)`. A call resolves to the declared result type and rejects with an Error when the tool fails. Calls still running when the script ends are cancelled, so await every call (use `Promise.all` for parallel calls).',
220
+ 'Call tools as `await tools.<id>(args)`. Arguments must be plain JSON (no `NaN`, `undefined` array items, `Map`, or functions). A call resolves to the declared result type and rejects with an Error whose message starts with `tools.<id>:` when the tool fails. Calls still running when the script ends are cancelled, so await every call (use `Promise.all` for parallel calls).',
190
221
  'Only the script output and its return value come back to you: filter and aggregate inside the script and return a small JSON value.'
191
222
  ].join('\n')
192
223
 
package/src/executor.ts CHANGED
@@ -16,13 +16,31 @@ export type CodeModeToolContext = {
16
16
  * A function a script can call. Tools are called as `tools.<identifier>(args)` and
17
17
  * `tools["<name>"](args)`; the identifier replaces characters that are not valid in a JavaScript
18
18
  * identifier with `_`. Arguments and results make a JSON round trip; a rejection surfaces in the
19
- * script as an `Error` with the same message.
19
+ * script as an `Error` with the same message (`makeCodeModeTool` rejects with
20
+ * `<call label>: <message>`, the label being `tools.<identifier>` or `tools["<name>"]`).
21
+ *
22
+ * `args` is the JSON value of the script's first argument (`undefined` when there is none), read
23
+ * once the way `JSON.stringify` reads it (getters and `toJSON` run once); the checked value is the
24
+ * one sent. Executors reject, inside the script and without calling `execute`, values that JSON
25
+ * would silently change instead of coercing them: non-finite numbers, invalid `Date`s,
26
+ * `undefined`/function/symbol array items and holes, function/symbol/bigint values, cycles, and
27
+ * non-plain objects without `toJSON` (`Map`, `Set`, `RegExp`, `Error`, class instances), including
28
+ * inside `toJSON` results. `undefined`-valued object keys count as absent, as JSON drops them; a
29
+ * valid `Date` passes as its ISO string. An argument too large or deep to check completely is
30
+ * rejected, never sent unchecked (the pi executor: more than 100000 values or 64 levels). A
31
+ * rejection names the call the way scripts reach it, as `makeCodeModeTool` does: the first tool
32
+ * with an identifier is `tools.<identifier>`, a later one `tools["<name>"]`.
20
33
  */
21
34
  export type CodeModeExecutorTool = {
22
35
  readonly name: string
23
36
  readonly description?: string
24
37
  readonly inputSchema?: CodeModeJsonSchema
25
38
  readonly outputSchema?: CodeModeJsonSchema
39
+ /** Diagnostic call label for rejection messages: `tools.<identifier>`, or `tools["<name>"]`
40
+ * when an earlier tool holds the identifier (a tool whose name is taken too is unreachable and
41
+ * the label only names it). `makeCodeModeTool` sets it; executors fall back to pi's binding rule.
42
+ */
43
+ readonly callLabel?: string
26
44
  readonly execute: (args: unknown, context: CodeModeToolContext) => Promise<unknown>
27
45
  }
28
46
 
@@ -30,7 +48,7 @@ export type CodeModeExecutorTool = {
30
48
  * A top-level function (`name`) or namespace member (`namespace.member`) for scripts. Globals are
31
49
  * host helpers: they are not tools and are not recorded as nested calls.
32
50
  */
33
- export type CodeModeExecutorGlobal = CodeModeExecutorTool & {
51
+ export type CodeModeExecutorGlobal = Omit<CodeModeExecutorTool, 'callLabel'> & {
34
52
  /** `execute` receives every call argument as an array instead of the first one. */
35
53
  readonly spread?: boolean
36
54
  /** TypeScript parameter list and return type for declarations. */
@@ -90,7 +108,8 @@ export type CodeModeExecuteOptions = {
90
108
  * may carry TypeScript annotations: executors strip them or report a `script` error. The promise
91
109
  * resolves for every script outcome, including failures, timeouts, and aborts; a rejection is
92
110
  * treated as a `sandbox` failure. Running tool calls must be cancelled through their signal when
93
- * the script ends.
111
+ * the script ends. Tool arguments follow the JSON rules of `CodeModeExecutorTool`: pass their JSON
112
+ * value and reject what JSON would silently change.
94
113
  *
95
114
  * `@yolk-sdk/codemode/node` provides `makePiCodeModeExecutor`; hosts on other runtimes supply their
96
115
  * own engine behind this interface.