@yolk-sdk/codemode 0.1.0-canary.124 → 0.1.0-canary.126
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -8
- package/dist/catalog.d.mts.map +1 -1
- package/dist/catalog.mjs +55 -8
- package/dist/catalog.mjs.map +1 -1
- package/dist/executor.d.mts +3 -2
- package/dist/executor.d.mts.map +1 -1
- package/dist/node.d.mts.map +1 -1
- package/dist/node.mjs +48 -6
- package/dist/node.mjs.map +1 -1
- package/dist/tool.d.mts.map +1 -1
- package/dist/tool.mjs +38 -34
- package/dist/tool.mjs.map +1 -1
- package/package.json +2 -2
- package/src/catalog.ts +102 -8
- package/src/executor.ts +3 -2
- package/src/node.ts +66 -8
- package/src/tool.ts +46 -26
package/README.md
CHANGED
|
@@ -58,14 +58,20 @@ const program = Effect.gen(function* () {
|
|
|
58
58
|
truncated error, and usage. The record keeps up to `maxNestedCalls` calls, at most 4096
|
|
59
59
|
(`nestedToolCallMaxRecordedCalls`; argument byte budgets still apply), and per-status `counts`
|
|
60
60
|
over every call. Nested results never reach the model; only the script output and return value do.
|
|
61
|
-
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
`
|
|
68
|
-
`
|
|
61
|
+
- Typed results ([ADR 0013](https://github.com/magoz/yolk-sdk/blob/main/docs/adr/0013-typed-tool-results.md)):
|
|
62
|
+
a tool with an output schema resolves to its value as declared (arrays and primitives included;
|
|
63
|
+
file fields are `FileRef | FileUnavailable`); a typed result without a value rejects
|
|
64
|
+
`tools.<id>: returned an invalid result`. A tool without one resolves to its text parts joined
|
|
65
|
+
with newlines (`Promise<string>`); its files reach the model, not the script. Script values
|
|
66
|
+
never contain file labels. Error results, `beforeNestedCall` failures, and calls past
|
|
67
|
+
`maxNestedCalls` reject with an `Error` whose message is `tools.<id>: <text>`, or
|
|
68
|
+
`tools["<name>"]: <text>` for a tool whose identifier an earlier tool already uses (the
|
|
69
|
+
nested-call record keeps the raw text), with `error.code` when the tool reports a code
|
|
70
|
+
(`ModelVisibleToolError.code`, a connector failure's code). Calls still running when the script
|
|
71
|
+
ends are cancelled and recorded as `cancelled`.
|
|
72
|
+
- `type FileRef = { fileId, name, mimeType, size }` and
|
|
73
|
+
`type FileUnavailable = { unavailable, name, mimeType }` are declared once in the description;
|
|
74
|
+
signatures show file fields by name. Scripts pass `fileId`s to other tools; file bytes never
|
|
69
75
|
enter the script.
|
|
70
76
|
- Arguments make a JSON round trip: `undefined` keys and `null` optionals are absent, unknown keys
|
|
71
77
|
reject with a hint naming the allowed keys, and values JSON would silently change (`NaN`,
|
package/dist/catalog.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalog.d.mts","names":[],"sources":["../src/catalog.ts"],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"catalog.d.mts","names":[],"sources":["../src/catalog.ts"],"mappings":";;;;;;;;YAuBY;;YAGA;;WAED;;WAEA;;;;;;WAMA;;WAEA;;WAEA;WACA;WACA,aAAa;;WAEb,cAAc;;WAEd;WACA,UAAU;;;;;;;;;qBAYR,qBAAkB,OAAW,0BAAwB;;qBAerD,kBAAe,OACnB,cAAc,gBACpB,cAAc;;;;;;;qBA8BJ,2BAAwB,WACxB,cAAc,gBACxB,cAAc;;qBAoBJ;;qBAGA,yBAAsB;YAoJvB;;;;WAID,QAAQ,cAAc;;WAEtB,oBAAoB;;;;;;;;;qBAUlB,wBAAqB,SACvB,cAAc,sBAAoB,mBAE1C;;YAsIS;WACD,OAAO,cAAc;WACrB;;YAGC;WACD,OAAO,cAAc;WACrB;;WAEA;;WAEA,UAAU;;;;;;;;;;;;qBAaR,4BAAyB,OAAW;;qBAmCpC;;;;;;qBAkCA,wBAAqB,MAAU;;;;;qBAa/B,uBAAoB,MAAU;;;;;;qBAc9B,mBAAgB,SAClB,cAAc,sBAAoB,iBAE1C;;qBAMU;;qBAcA,wBAAsB,OAAA;;;;;;;;;;;;;;;;;;YAUvB,gCAAgC,uBAAuB;;;;;;qBAqBtD,2BAAwB;WAC1B,OAAO,cAAc;WACrB,UAAU;WACV;MACP;;qBA6CS,wBAAqB,SAAa;;;;;qBAelC,6BAA0B;WAC5B,OAAO,cAAc;WACrB,UAAU;WACV;MACP;;;;;qBAuBS,sCAAmC;WACrC;WACA,UAAU"}
|
package/dist/catalog.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Option, Predicate } from "effect";
|
|
2
2
|
import * as Schema from "effect/Schema";
|
|
3
3
|
import { renderDeclarations, renderToolOutputType, renderToolSample, schemaToType, toCodemodeIdentifier } from "@earendil-works/pi-codemode/declarations";
|
|
4
|
-
import { isDeferredTool, toolDiscovery } from "@yolk-sdk/agent/protocol";
|
|
4
|
+
import { interactionJsonEquals, isDeferredTool, toolDiscovery, toolFileJsonSchemaDefinitions } from "@yolk-sdk/agent/protocol";
|
|
5
5
|
// src/catalog.ts
|
|
6
6
|
const textOutputSchema = { type: "string" };
|
|
7
7
|
/**
|
|
@@ -69,9 +69,55 @@ const codeModeStageOnlyCatalog = (stageable) => stageable.map(({ def, moduleId,
|
|
|
69
69
|
const defaultCodeModeInlineBudget = 3e3;
|
|
70
70
|
/** Estimated tokens of a text: four characters per token, rounded up. */
|
|
71
71
|
const estimateCodeModeTokens = (text) => Math.ceil(text.length / 4);
|
|
72
|
+
const fileTypeNames = {
|
|
73
|
+
FileRef: "FileRef",
|
|
74
|
+
FileUnavailable: "FileUnavailable"
|
|
75
|
+
};
|
|
76
|
+
const fileTypeSentinel = (name) => `\u0000yolk:${name}`;
|
|
77
|
+
const isJsonObject = (value) => Predicate.isObject(value) && !Array.isArray(value);
|
|
78
|
+
const fileDefinitions = (schema) => {
|
|
79
|
+
const defs = isJsonObject(schema) ? schema["$defs"] : void 0;
|
|
80
|
+
if (!isJsonObject(defs)) return /* @__PURE__ */ new Set();
|
|
81
|
+
return new Set(["FileRef", "FileUnavailable"].filter((name) => interactionJsonEquals(defs[name], toolFileJsonSchemaDefinitions[name])));
|
|
82
|
+
};
|
|
83
|
+
const dataKeywords = /* @__PURE__ */ new Set([
|
|
84
|
+
"const",
|
|
85
|
+
"enum",
|
|
86
|
+
"default",
|
|
87
|
+
"examples"
|
|
88
|
+
]);
|
|
89
|
+
const schemaMapKeywords = /* @__PURE__ */ new Set([
|
|
90
|
+
"properties",
|
|
91
|
+
"patternProperties",
|
|
92
|
+
"$defs",
|
|
93
|
+
"definitions",
|
|
94
|
+
"dependentSchemas"
|
|
95
|
+
]);
|
|
96
|
+
const mapValues = (value, f) => Object.fromEntries(Object.entries(value).map(([key, item]) => [key, f(key, item)]));
|
|
97
|
+
const withFileSentinels = (value, names) => {
|
|
98
|
+
if (Array.isArray(value)) return value.map((item) => withFileSentinels(item, names));
|
|
99
|
+
if (!isJsonObject(value)) return value;
|
|
100
|
+
const ref = value["$ref"];
|
|
101
|
+
const named = ["FileRef", "FileUnavailable"].find((name) => names.has(name) && ref === `#/$defs/${name}`);
|
|
102
|
+
if (named !== void 0) return { const: fileTypeSentinel(named) };
|
|
103
|
+
return mapValues(value, (key, item) => {
|
|
104
|
+
if (dataKeywords.has(key)) return item;
|
|
105
|
+
if (schemaMapKeywords.has(key) && isJsonObject(item)) return mapValues(item, (_name, schema) => withFileSentinels(schema, names));
|
|
106
|
+
return withFileSentinels(item, names);
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
/** An output schema whose file fields render as `FileRef` / `FileUnavailable`. */
|
|
110
|
+
const renderableOutput = (schema) => {
|
|
111
|
+
const names = fileDefinitions(schema);
|
|
112
|
+
if (names.size === 0) return schema;
|
|
113
|
+
const replaced = withFileSentinels(schema, names);
|
|
114
|
+
return isJsonObject(replaced) ? replaced : schema;
|
|
115
|
+
};
|
|
116
|
+
const withFileTypeNames = (rendered) => ["FileRef", "FileUnavailable"].reduce((text, name) => text.replaceAll(JSON.stringify(fileTypeSentinel(name)), fileTypeNames[name]), rendered);
|
|
117
|
+
const renderOutputType = (schema) => withFileTypeNames(renderToolOutputType(renderableOutput(schema)));
|
|
72
118
|
const maxShortTypeChars = 60;
|
|
73
119
|
const shortType = (schema) => {
|
|
74
|
-
const rendered =
|
|
120
|
+
const rendered = renderOutputType(schema);
|
|
75
121
|
if (rendered.length <= maxShortTypeChars) return rendered;
|
|
76
122
|
if (!Predicate.isBoolean(schema)) {
|
|
77
123
|
if (schema.type === "array") return "Array<unknown>";
|
|
@@ -83,11 +129,11 @@ const declarationTool = (tool) => ({
|
|
|
83
129
|
name: tool.name,
|
|
84
130
|
description: tool.description,
|
|
85
131
|
inputSchema: tool.inputSchema,
|
|
86
|
-
outputSchema: tool.outputSchema,
|
|
132
|
+
outputSchema: renderableOutput(tool.outputSchema),
|
|
87
133
|
execute: () => void 0
|
|
88
134
|
});
|
|
89
135
|
const directToolLine = (tool) => `- \`tools.${tool.identifier}(args)\` takes the arguments of the \`${tool.name}\` tool and resolves to \`${shortType(tool.outputSchema)}\`.`;
|
|
90
|
-
const declarationMembers = (tools) => renderDeclarations({ tools: tools.map(declarationTool) });
|
|
136
|
+
const declarationMembers = (tools) => withFileTypeNames(renderDeclarations({ tools: tools.map(declarationTool) }));
|
|
91
137
|
const candidateCost = (tool) => estimateCodeModeTokens(declarationMembers([tool]));
|
|
92
138
|
const groupByNamespace = (items) => {
|
|
93
139
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -134,8 +180,9 @@ const selectCodeModeListing = (catalog, budget) => {
|
|
|
134
180
|
const intro = [
|
|
135
181
|
"Run a JavaScript script that calls tools and returns only what matters.",
|
|
136
182
|
"`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.",
|
|
137
|
-
"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).",
|
|
138
|
-
"
|
|
183
|
+
"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 (`error.code` is set when the tool reports one). Calls still running when the script ends are cancelled, so await every call (use `Promise.all` for parallel calls).",
|
|
184
|
+
"`string` results are text to read; parse JSON only when the tool says it returns JSON.",
|
|
185
|
+
"Files are typed fields of results: `type FileRef = { fileId: string; name: string; mimeType: string; size: number }`; `type FileUnavailable = { unavailable: string; name: string; mimeType: string }` (`unavailable` says why the file was not stored). Pass `fileId`s to other tools; file bytes never enter the script.",
|
|
139
186
|
"Only the script output and its return value come back to you: filter and aggregate inside the script and return a small JSON value."
|
|
140
187
|
].join("\n");
|
|
141
188
|
const stageLine = (reviewToolName) => `- \`await stage(name, args)\`: record a call of a stageable tool for review instead of running it; resolves to \`{ staged: true, key, index }\`. Nothing runs until a person approves the plan through the \`${reviewToolName}\` tool, which the result tells you how to call. Once a script stages, it may only run read tools; a script that already ran a write tool cannot stage. \`searchTools\` marks approval-gated tools \`stageOnly: true\`: they have no \`tools\` binding, only \`stage()\` them.`;
|
|
@@ -222,7 +269,7 @@ const maxCodeModeSearchSignatureChars = 800;
|
|
|
222
269
|
const signatureCutMarker = "… (describeTool for the full declaration)";
|
|
223
270
|
const compactText = (rendered) => rendered.split("\n").map((line) => line.trim()).filter((line) => !line.startsWith("//")).join(" ");
|
|
224
271
|
const compactResultType = (schema) => {
|
|
225
|
-
const rendered = compactText(
|
|
272
|
+
const rendered = compactText(renderOutputType(schema));
|
|
226
273
|
return rendered.length <= maxShortTypeChars ? rendered : shortType(schema);
|
|
227
274
|
};
|
|
228
275
|
const cutSignature = (signature) => {
|
|
@@ -247,7 +294,7 @@ const describeCodeModeTool = (tool) => tool.exposure === "stage" ? [
|
|
|
247
294
|
tool.description,
|
|
248
295
|
`Stage-only (approval-gated): record a call with \`await stage(${JSON.stringify(tool.name)}, args)\`; it runs only after a person approves the plan.`,
|
|
249
296
|
`args: ${schemaToType(tool.inputSchema)}`
|
|
250
|
-
].join("\n\n") : renderToolSample(declarationTool(tool));
|
|
297
|
+
].join("\n\n") : withFileTypeNames(renderToolSample(declarationTool(tool)));
|
|
251
298
|
/**
|
|
252
299
|
* Finds a tool by identifier or raw name. A stageable tool's exact raw name wins first: it has no
|
|
253
300
|
* binding, so a callable tool sharing its identifier (`archive-resource` vs `archive_resource`)
|
package/dist/catalog.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalog.mjs","names":[],"sources":["../src/catalog.ts"],"sourcesContent":["import { Option, Predicate } from 'effect'\nimport * as Schema from 'effect/Schema'\nimport {\n renderDeclarations,\n renderToolOutputType,\n renderToolSample,\n schemaToType,\n toCodemodeIdentifier\n} from '@earendil-works/pi-codemode/declarations'\nimport { isDeferredTool, toolDiscovery } from '@yolk-sdk/agent/protocol'\nimport type { RunInstruction } from '@yolk-sdk/agent/loop'\nimport type { NestedTool } from '@yolk-sdk/agent/tools'\nimport type { CodeModeJsonSchema } from './executor.ts'\n\n/** How a nested tool reaches scripts: `all` tools are also direct model tools; `listed` and\n * `search` are the two discovery modes of `codemode`-only tools; `stage` marks a stageable tool\n * (approval-gated, ADR 0007): found by search, never callable, only passed to `stage()`.\n */\nexport type CodeModeToolExposure = 'all' | 'listed' | 'search' | 'pinned' | 'stage'\n\n/** One nested tool as scripts see it. */\nexport type CodeModeCatalogTool = {\n /** Raw tool name; scripts may call `tools[\"<name>\"](args)`. */\n readonly name: string\n /** Identifier for `tools.<identifier>(args)` (invalid identifier characters become `_`). */\n readonly identifier: string\n /**\n * How scripts call this tool, used to prefix its rejections: `tools.<identifier>`, or\n * `tools[\"<name>\"]` when an earlier tool already has the identifier (see `codeModeCallLabels`;\n * when the name is taken too, the tool is unreachable and the label only names it).\n */\n readonly callLabel: string\n /** The tool's `ToolModule.id`. */\n readonly namespace: string\n /** The module's `ToolModule.description`, when set. */\n readonly namespaceDescription?: string\n readonly description: string\n readonly inputSchema: CodeModeJsonSchema\n /** The declared output schema, or `{ type: 'string' }` for text results. */\n readonly outputSchema: CodeModeJsonSchema\n /** True when the tool declares an output schema (calls resolve to `structuredContent`). */\n readonly structured: boolean\n readonly exposure: CodeModeToolExposure\n}\n\nconst textOutputSchema: CodeModeJsonSchema = { type: 'string' }\n\n/**\n * The call label of each tool name, in order, as pi binds `tools`: each tool takes\n * `tools[<identifier>]` unless an earlier tool holds that key (first tool wins), then\n * `tools[<name>]` if still free. The label is `tools.<identifier>` for the tool that holds its\n * identifier, otherwise `tools[\"<name>\"]`. The executor guard and the host rejection prefix share\n * it, so both name a call the same way.\n */\nexport const codeModeCallLabels = (names: ReadonlyArray<string>): ReadonlyArray<string> => {\n const bound = new Set<string>()\n\n return names.map(name => {\n const identifier = toCodemodeIdentifier(name)\n const ownsIdentifier = !bound.has(identifier)\n\n bound.add(identifier)\n bound.add(name)\n\n return ownsIdentifier ? `tools.${identifier}` : `tools[${JSON.stringify(name)}]`\n })\n}\n\n/** Catalog of nested tools in resolution order. */\nexport const codeModeCatalog = (\n tools: ReadonlyArray<NestedTool>\n): ReadonlyArray<CodeModeCatalogTool> => {\n const labels = codeModeCallLabels(tools.map(({ def }) => def.name))\n\n return tools.map(({ def, moduleId, moduleDescription }, index) => {\n const tool: CodeModeCatalogTool = {\n name: def.name,\n identifier: toCodemodeIdentifier(def.name),\n callLabel: labels[index] ?? `tools[${JSON.stringify(def.name)}]`,\n namespace: moduleId,\n description: def.description,\n inputSchema: def.parameters,\n outputSchema: def.outputSchema ?? textOutputSchema,\n structured: def.outputSchema !== undefined,\n // A deferred tool's declaration is not in model context until activated: scripts find it\n // by search like a codemode-only `search` tool (ADR 0007).\n exposure: toolDiscovery(def) ?? (isDeferredTool(def) ? 'search' : 'all')\n }\n\n return moduleDescription === undefined\n ? tool\n : { ...tool, namespaceDescription: moduleDescription }\n })\n}\n\n/**\n * Catalog entries of the stageable tools (ADR 0007). Stageable tools are approval-gated, so scripts\n * can only `stage()` them whatever their `callableBy` (stage-only `codemode` ones are also never\n * declared to the model): searchable and describable, never callable (no `tools` binding), so\n * their call label is the `stage()` call that records them.\n */\nexport const codeModeStageOnlyCatalog = (\n stageable: ReadonlyArray<NestedTool>\n): ReadonlyArray<CodeModeCatalogTool> =>\n stageable.map(({ def, moduleId, moduleDescription }) => {\n const tool: CodeModeCatalogTool = {\n name: def.name,\n identifier: toCodemodeIdentifier(def.name),\n callLabel: `stage(${JSON.stringify(def.name)})`,\n namespace: moduleId,\n description: def.description,\n inputSchema: def.parameters,\n outputSchema: def.outputSchema ?? textOutputSchema,\n structured: def.outputSchema !== undefined,\n exposure: 'stage'\n }\n\n return moduleDescription === undefined\n ? tool\n : { ...tool, namespaceDescription: moduleDescription }\n })\n\n/** Default inline budget of the nested tool listing, in estimated tokens. */\nexport const defaultCodeModeInlineBudget = 3000\n\n/** Estimated tokens of a text: four characters per token, rounded up. */\nexport const estimateCodeModeTokens = (text: string): number => Math.ceil(text.length / 4)\n\nconst maxShortTypeChars = 60\n\nconst shortType = (schema: CodeModeJsonSchema): string => {\n const rendered = renderToolOutputType(schema)\n\n if (rendered.length <= maxShortTypeChars) return rendered\n\n if (!Predicate.isBoolean(schema)) {\n if (schema.type === 'array') return 'Array<unknown>'\n\n if (schema.type === 'object') return 'object'\n }\n\n return 'unknown'\n}\n\n// Declarations only read names, descriptions, and schemas; `execute` is never called.\nconst declarationTool = (tool: CodeModeCatalogTool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n outputSchema: tool.outputSchema,\n execute: () => undefined\n})\n\nconst directToolLine = (tool: CodeModeCatalogTool) =>\n `- \\`tools.${tool.identifier}(args)\\` takes the arguments of the \\`${tool.name}\\` tool and resolves to \\`${shortType(tool.outputSchema)}\\`.`\n\nconst declarationMembers = (tools: ReadonlyArray<CodeModeCatalogTool>) =>\n renderDeclarations({ tools: tools.map(declarationTool) })\n\ntype Candidate = {\n readonly tool: CodeModeCatalogTool\n readonly index: number\n readonly cost: number\n}\n\nconst candidateCost = (tool: CodeModeCatalogTool) =>\n estimateCodeModeTokens(declarationMembers([tool]))\n\nconst groupByNamespace = <A extends { readonly tool: CodeModeCatalogTool }>(\n items: ReadonlyArray<A>\n): ReadonlyMap<string, ReadonlyArray<A>> => {\n const groups = new Map<string, Array<A>>()\n\n for (const item of items) {\n const group = groups.get(item.tool.namespace)\n\n if (group === undefined) {\n groups.set(item.tool.namespace, [item])\n } else {\n group.push(item)\n }\n }\n\n return groups\n}\n\nexport type CodeModeListing = {\n /** Tools placed in the description, in catalog order: every `all` tool, and the `listed` tools\n * that fit the budget.\n */\n readonly listed: ReadonlyArray<CodeModeCatalogTool>\n /** Namespaces (catalog order) with callable tools the description does not list. */\n readonly unlistedNamespaces: ReadonlyArray<string>\n}\n\n/**\n * Chooses the tools the description lists. `all` tools are always placed, outside the budget\n * (the model already has their declarations), and so are `pinned` tools. `listed` tools fill\n * `budget` estimated tokens fairly across namespaces: each round, every namespace still in play\n * places its cheapest remaining tool; a namespace whose next tool does not fit drops out. `search`\n * tools are never candidates.\n */\nexport const selectCodeModeListing = (\n catalog: ReadonlyArray<CodeModeCatalogTool>,\n budget: number\n): CodeModeListing => {\n const candidates = catalog.flatMap((tool, index): ReadonlyArray<Candidate> =>\n tool.exposure === 'listed' ? [{ tool, index, cost: candidateCost(tool) }] : []\n )\n\n const queues = new Map(\n [...groupByNamespace(candidates)].map(([namespace, group]) => [\n namespace,\n [...group].sort((left, right) => left.cost - right.cost || left.index - right.index)\n ])\n )\n\n const placed = new Set(\n catalog.flatMap((tool, index) =>\n tool.exposure === 'all' || tool.exposure === 'pinned' ? [index] : []\n )\n )\n\n const inPlay = new Set(queues.keys())\n let remaining = budget\n\n while (inPlay.size > 0) {\n for (const [namespace, queue] of queues) {\n if (!inPlay.has(namespace)) continue\n\n const next = queue.shift()\n\n if (next === undefined || next.cost > remaining) {\n inPlay.delete(namespace)\n continue\n }\n\n placed.add(next.index)\n remaining -= next.cost\n }\n }\n\n const unlisted = new Set(\n catalog.flatMap((tool, index) => (placed.has(index) ? [] : [tool.namespace]))\n )\n\n return {\n listed: catalog.filter((_, index) => placed.has(index)),\n unlistedNamespaces: [...new Set(catalog.map(tool => tool.namespace))].filter(namespace =>\n unlisted.has(namespace)\n )\n }\n}\n\nconst intro = [\n 'Run a JavaScript script that calls tools and returns only what matters.',\n '`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.',\n '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).',\n 'A call that yields files also resolves `files: [{ fileId, name, mimeType, size }]`, added to an object result or as `{ text, files }`. Pass `fileId`s to other tools; file bytes never enter the script.',\n 'Only the script output and its return value come back to you: filter and aggregate inside the script and return a small JSON value.'\n].join('\\n')\n\nconst stageLine = (reviewToolName: string) =>\n `- \\`await stage(name, args)\\`: record a call of a stageable tool for review instead of running it; resolves to \\`{ staged: true, key, index }\\`. Nothing runs until a person approves the plan through the \\`${reviewToolName}\\` tool, which the result tells you how to call. Once a script stages, it may only run read tools; a script that already ran a write tool cannot stage. \\`searchTools\\` marks approval-gated tools \\`stageOnly: true\\`: they have no \\`tools\\` binding, only \\`stage()\\` them.`\n\n/** Stageable tool names listed in the description; the rest are summarized with a count. */\nconst maxListedStageableTools = 20\n\n/**\n * Stageable tools the description names: `pinned` ones first, never `search` or deferred ones\n * (whose declaration is not in model context; scripts find them by search, ADR 0007).\n */\ntype StageableNames = {\n readonly pinned: ReadonlyArray<string>\n readonly listed: ReadonlyArray<string>\n}\n\nconst listedStageableNames = (staging: CodeModeStagingDescription): StageableNames => ({\n pinned: staging.tools.flatMap(({ def }) => (toolDiscovery(def) === 'pinned' ? [def.name] : [])),\n listed: staging.tools.flatMap(({ def }) => {\n const discovery = toolDiscovery(def)\n\n return discovery === 'search' || discovery === 'pinned' || isDeferredTool(def) ? [] : [def.name]\n })\n})\n\nconst hasStageableNames = (names: StageableNames) =>\n names.pinned.length > 0 || names.listed.length > 0\n\n/** `pinned` names are always shown; the cap applies to the others. */\nconst stageableSection = (names: StageableNames) => {\n const room = Math.max(0, maxListedStageableTools - names.pinned.length)\n const hidden = names.listed.length - room\n\n return [\n '## Stageable tools',\n 'Approval-gated tools a script cannot call through `tools`, only stage with `await stage(\"<name>\", args)` (arguments as for the tool of the same name):',\n ...[...names.pinned, ...names.listed.slice(0, room)].map(name => `- \\`${name}\\``),\n ...(hidden > 0 ? [`- … and ${hidden} more (find them with \\`searchTools\\`).`] : [])\n ].join('\\n')\n}\n\nconst globalLines = (store: boolean) => [\n '- `text(value)` and `console.log(...values)`: append text to the output.',\n '- `image(dataUrl)`: append a base64 image (a `data:` URL or `{ type: \"image\", data, mimeType }`).',\n '- `exit()`: end the script successfully.',\n '- `ALL_TOOLS`: `{ name, description }` of every callable tool.',\n '- `await searchTools(query, { limit?, namespace? })`: find tools by topic; resolves to `Array<{ name: string; description: string; signature: string }>` (default limit 8). `signature` is the call with its argument and result types. When you already know the exact name of a tool (listed in this description or the catalog, returned by an earlier `searchTools`, or called earlier in this conversation), call it as `tools.<id>(args)` without searching. Otherwise search, return the hits, and call the tool you choose in your next script. Never call a tool in the same script that found it. If a call fails on its arguments, read its `signature` (or `describeTool`) and retry.',\n '- `await describeTool(name)`: the full description and documented TypeScript declaration of a tool, or `undefined`; needed only when a signature is cut or unclear.',\n '- `await describeNamespace(name)`: `{ name, description?, tools: Array<{ name, description }> }` for a namespace, or `undefined`.',\n ...(store\n ? [\n '- `store(key, value)` and `load(key)`: keep JSON values (up to 256 KiB each) for later scripts; writes are saved only when the script succeeds. When fetched data is large, store it and return a summary, then filter it with `load()` in a later script instead of calling the tools again.'\n ]\n : [])\n]\n\nconst namespaceSection = (namespace: string, tools: ReadonlyArray<CodeModeCatalogTool>) => {\n const declared = tools.filter(tool => tool.exposure !== 'all')\n const direct = tools.filter(tool => tool.exposure === 'all')\n\n const description = tools.find(\n tool => tool.namespaceDescription !== undefined\n )?.namespaceDescription\n\n return [\n `### ${namespace}`,\n ...(description === undefined ? [] : [description]),\n ...(declared.length > 0 ? ['```ts', declarationMembers(declared), '```'] : []),\n ...direct.map(directToolLine)\n ].join('\\n')\n}\n\n// Fixed text: never derived from `search` tools or from which tools did not fit the budget.\nconst unlistedToolsLine =\n 'More tools may be callable than are listed here: find them with `await searchTools(query, { namespace? })` (each hit has its call signature) or `await describeNamespace(name)`; call the tool you choose in your next script, and call a tool whose exact name you already know directly, without searching.'\n\n/** Staged tool plans as the description shows them (`NestedToolStagingDescription`). */\nexport type CodeModeStagingDescription = {\n readonly tools: ReadonlyArray<NestedTool>\n readonly reviewToolName: string\n}\n\nexport type CodeModeDescriptionInput = {\n readonly tools: ReadonlyArray<NestedTool>\n readonly inlineBudget?: number\n /** Mention `store()`/`load()` persistence. */\n readonly store?: boolean\n /** Document `stage()` and list the stageable tools that are neither `search` nor deferred. */\n readonly staging?: CodeModeStagingDescription\n}\n\n/**\n * The code mode tool description: intro, globals one per line, then nested tools grouped by\n * namespace, then one fixed line pointing to `searchTools` and `describeNamespace`.\n * `all` tools get one line each outside the budget; `listed` tools are declared within the inline\n * budget; `search` tools never contribute, so adding or removing them (even whole namespaces of\n * them) leaves the text byte-identical. With `staging` (and stageable tools), `stage()` is listed\n * with the globals and the stageable tool names follow the nested tools; `search` and deferred\n * stageable tools are never named, so they leave the text byte-identical too once any stageable\n * tool exists.\n */\nexport const renderCodeModeDescription = (input: CodeModeDescriptionInput): string => {\n const catalog = codeModeCatalog(input.tools)\n\n const listing = selectCodeModeListing(catalog, input.inlineBudget ?? defaultCodeModeInlineBudget)\n\n const sections = [...groupByNamespace(listing.listed.map(tool => ({ tool })))].map(\n ([namespace, items]) =>\n namespaceSection(\n namespace,\n items.map(item => item.tool)\n )\n )\n\n const staging =\n input.staging !== undefined && input.staging.tools.length > 0 ? input.staging : undefined\n\n const stageableNames =\n staging === undefined ? { pinned: [], listed: [] } : listedStageableNames(staging)\n\n return [\n intro,\n [\n 'Globals:',\n ...globalLines(input.store === true),\n ...(staging === undefined ? [] : [stageLine(staging.reviewToolName)])\n ].join('\\n'),\n sections.length === 0\n ? 'Nested tools: none are listed here.'\n : ['## Nested tools by namespace', ...sections].join('\\n\\n'),\n ...(hasStageableNames(stageableNames) ? [stageableSection(stageableNames)] : []),\n unlistedToolsLine\n ].join('\\n\\n')\n}\n\n/** Longest `searchTools` signature, cut marker included (`describeTool` has the whole declaration). */\nexport const maxCodeModeSearchSignatureChars = 800\n\nconst signatureCutMarker = '… (describeTool for the full declaration)'\n\n// A rendered type on one line, without its description comments (`describeTool` keeps them). pi\n// puts every description line on its own `//` line and JSON-quotes keys and literals, so no other\n// line starts with `//`.\nconst compactText = (rendered: string) =>\n rendered\n .split('\\n')\n .map(line => line.trim())\n .filter(line => !line.startsWith('//'))\n .join(' ')\n\nconst compactResultType = (schema: CodeModeJsonSchema) => {\n const rendered = compactText(renderToolOutputType(schema))\n\n return rendered.length <= maxShortTypeChars ? rendered : shortType(schema)\n}\n\n// Cuts at a code point boundary: never leaves half of a surrogate pair.\nconst cutSignature = (signature: string) => {\n const end = maxCodeModeSearchSignatureChars - signatureCutMarker.length\n const kept = signature.slice(0, end)\n const last = kept.charCodeAt(kept.length - 1)\n\n return `${last >= 0xd800 && last <= 0xdbff ? kept.slice(0, -1) : kept}${signatureCutMarker}`\n}\n\n/**\n * The call a `searchTools` hit shows, enough to call the tool without `describeTool`:\n * `tools.<id>(args): Promise<result>`, or `stage(\"<name>\", args)` for a stageable tool. Bounded:\n * a long one ends with a pointer to `describeTool`.\n */\nexport const codeModeToolSignature = (tool: CodeModeCatalogTool): string => {\n const signature =\n tool.exposure === 'stage'\n ? `stage(${JSON.stringify(tool.name)}, ${compactText(schemaToType(tool.inputSchema))})`\n : `${tool.callLabel}(${compactText(schemaToType(tool.inputSchema))}): Promise<${compactResultType(tool.outputSchema)}>`\n\n return signature.length <= maxCodeModeSearchSignatureChars ? signature : cutSignature(signature)\n}\n\n/**\n * `describeTool` text: the description and TypeScript declaration of one tool. A stage-only tool\n * has no `tools` binding, so it is described as the `stage()` call that records it.\n */\nexport const describeCodeModeTool = (tool: CodeModeCatalogTool): string =>\n tool.exposure === 'stage'\n ? [\n tool.description,\n `Stage-only (approval-gated): record a call with \\`await stage(${JSON.stringify(tool.name)}, args)\\`; it runs only after a person approves the plan.`,\n `args: ${schemaToType(tool.inputSchema)}`\n ].join('\\n\\n')\n : renderToolSample(declarationTool(tool))\n\n/**\n * Finds a tool by identifier or raw name. A stageable tool's exact raw name wins first: it has no\n * binding, so a callable tool sharing its identifier (`archive-resource` vs `archive_resource`)\n * must not answer for it; callable lookups keep identifier precedence, as their bindings do.\n */\nexport const findCodeModeTool = (\n catalog: ReadonlyArray<CodeModeCatalogTool>,\n name: string\n): CodeModeCatalogTool | undefined =>\n catalog.find(tool => tool.exposure === 'stage' && tool.name === name) ??\n catalog.find(tool => tool.identifier === name) ??\n catalog.find(tool => tool.name === name)\n\n/** The instruction key of the code mode catalog (`catalog: 'instructions'`, ADR 0007). */\nexport const codeModeCatalogInstructionKey = 'yolk.codemode.catalog'\n\nconst CodeModeCatalogNamespace = Schema.Struct({\n name: Schema.String,\n description: Schema.optional(Schema.String),\n /** Every tool scripts reach in the namespace: callable and stage-only. */\n count: Schema.Number,\n /** Tools the listing shows. */\n shown: Schema.Number,\n /** The rendered declarations and direct tool lines of the shown tools. */\n listing: Schema.String\n})\n\n/** The catalog an instruction message records as its state (JSON, never model-visible). */\nexport const CodeModeCatalogSummary = Schema.Struct({\n namespaces: Schema.Array(CodeModeCatalogNamespace),\n /** Stageable tool names the catalog lists: `pinned` ones, then the others (never `search` or\n * deferred ones). */\n stageable: Schema.Struct({\n pinned: Schema.Array(Schema.String),\n listed: Schema.Array(Schema.String)\n })\n})\n\nexport type CodeModeCatalogSummary = typeof CodeModeCatalogSummary.Type\n\nconst decodeCatalogSummary = Schema.decodeUnknownOption(CodeModeCatalogSummary)\n\nconst encodeCatalogSummary = Schema.encodeOption(CodeModeCatalogSummary)\n\nconst namespaceListing = (tools: ReadonlyArray<CodeModeCatalogTool>) => {\n const declared = tools.filter(tool => tool.exposure !== 'all')\n const direct = tools.filter(tool => tool.exposure === 'all')\n\n return [\n ...(declared.length > 0 ? ['```ts', declarationMembers(declared), '```'] : []),\n ...direct.map(directToolLine)\n ].join('\\n')\n}\n\n/**\n * The catalog of one resolution: every namespace with its tool count and description, and the\n * listing of the tools the inline budget (or `pinned`) places. `search` and stage-only tools are\n * counted, never listed.\n */\nexport const summarizeCodeModeCatalog = (input: {\n readonly tools: ReadonlyArray<NestedTool>\n readonly staging?: CodeModeStagingDescription\n readonly inlineBudget?: number\n}): CodeModeCatalogSummary => {\n const catalog = codeModeCatalog(input.tools)\n const stageOnly = input.staging === undefined ? [] : codeModeStageOnlyCatalog(input.staging.tools)\n const listing = selectCodeModeListing(catalog, input.inlineBudget ?? defaultCodeModeInlineBudget)\n const listedByNamespace = groupByNamespace(listing.listed.map(tool => ({ tool })))\n\n const namespaces = [...groupByNamespace([...catalog, ...stageOnly].map(tool => ({ tool })))].map(\n ([name, items]) => {\n const shown = (listedByNamespace.get(name) ?? []).map(item => item.tool)\n\n const description = items.find(item => item.tool.namespaceDescription !== undefined)?.tool\n .namespaceDescription\n\n const namespace = {\n name,\n count: items.length,\n shown: shown.length,\n listing: namespaceListing(shown)\n }\n\n return description === undefined ? namespace : { ...namespace, description }\n }\n )\n\n return {\n namespaces,\n stageable:\n input.staging === undefined ? { pinned: [], listed: [] } : listedStageableNames(input.staging)\n }\n}\n\nconst namespaceLine = (namespace: CodeModeCatalogSummary['namespaces'][number]) => {\n const count = namespace.count === 1 ? '1 tool' : `${namespace.count} tools`\n\n const label =\n namespace.shown === namespace.count\n ? count\n : namespace.shown === 0\n ? `${count}, none listed`\n : `${count}, ${namespace.shown} listed`\n\n return `- \\`${namespace.name}\\` (${label})${namespace.description === undefined ? '' : `: ${namespace.description}`}`\n}\n\n/** The full catalog text of an instruction message. */\nexport const renderCodeModeCatalog = (summary: CodeModeCatalogSummary): string =>\n [\n '# Code mode catalog',\n 'Tools `codemode` scripts can call, by namespace; the latest catalog in the conversation replaces any earlier one. Call a listed tool as `await tools.<id>(args)`; find the others with `await searchTools(query, { namespace })` (each hit has its call signature); call the tool you choose in your next script, and call a tool whose exact name you already know directly, without searching.',\n ['## Namespaces', ...summary.namespaces.map(namespaceLine)].join('\\n'),\n ...summary.namespaces.flatMap(namespace =>\n namespace.listing.length === 0 ? [] : [`### ${namespace.name}\\n${namespace.listing}`]\n ),\n ...(hasStageableNames(summary.stageable) ? [stageableSection(summary.stageable)] : [])\n ].join('\\n\\n')\n\n/**\n * The host instruction of the code mode catalog: its state is the encoded summary, and its render\n * the full catalog (every change renders it whole, see `RunInstruction.render`) or a removal notice.\n */\nexport const codeModeCatalogInstruction = (input: {\n readonly tools: ReadonlyArray<NestedTool>\n readonly staging?: CodeModeStagingDescription\n readonly inlineBudget?: number\n}): RunInstruction => {\n const summary = summarizeCodeModeCatalog(input)\n\n return {\n key: codeModeCatalogInstructionKey,\n state:\n summary.namespaces.length === 0\n ? undefined\n : Option.getOrUndefined(encodeCatalogSummary(summary)),\n render: current => {\n const decoded = current === undefined ? Option.none() : decodeCatalogSummary(current)\n\n return Option.isNone(decoded)\n ? 'Code mode tools are no longer available: do not call previously listed code mode tools.'\n : renderCodeModeCatalog(decoded.value)\n }\n }\n}\n\n/**\n * The code mode description when the catalog is delivered as an instruction: invariant across\n * catalog changes (only staging availability adds the `stage()` line).\n */\nexport const renderCodeModeInstructedDescription = (input: {\n readonly store?: boolean\n readonly staging?: CodeModeStagingDescription\n}): string =>\n [\n intro,\n [\n 'Globals:',\n ...globalLines(input.store === true),\n ...(input.staging === undefined ? [] : [stageLine(input.staging.reviewToolName)])\n ].join('\\n'),\n 'The tools scripts can call are in the \"Code mode catalog\" (namespaces, tool counts and listed tools; the latest one applies). Find the others with `await searchTools(query, { namespace? })` (each hit has its call signature); call the tool you choose in your next script, and call a tool whose exact name you already know directly, without searching.'\n ].join('\\n\\n')\n"],"mappings":";;;;;AA6CA,MAAM,mBAAuC,EAAE,MAAM,SAAS;;;;;;;;AAS9D,MAAa,sBAAsB,UAAwD;CACzF,MAAM,wBAAQ,IAAI,IAAY;CAE9B,OAAO,MAAM,KAAI,SAAQ;EACvB,MAAM,aAAa,qBAAqB,IAAI;EAC5C,MAAM,iBAAiB,CAAC,MAAM,IAAI,UAAU;EAE5C,MAAM,IAAI,UAAU;EACpB,MAAM,IAAI,IAAI;EAEd,OAAO,iBAAiB,SAAS,eAAe,SAAS,KAAK,UAAU,IAAI,EAAE;CAChF,CAAC;AACH;;AAGA,MAAa,mBACX,UACuC;CACvC,MAAM,SAAS,mBAAmB,MAAM,KAAK,EAAE,UAAU,IAAI,IAAI,CAAC;CAElE,OAAO,MAAM,KAAK,EAAE,KAAK,UAAU,qBAAqB,UAAU;EAChE,MAAM,OAA4B;GAChC,MAAM,IAAI;GACV,YAAY,qBAAqB,IAAI,IAAI;GACzC,WAAW,OAAO,UAAU,SAAS,KAAK,UAAU,IAAI,IAAI,EAAE;GAC9D,WAAW;GACX,aAAa,IAAI;GACjB,aAAa,IAAI;GACjB,cAAc,IAAI,gBAAgB;GAClC,YAAY,IAAI,iBAAiB,KAAA;GAGjC,UAAU,cAAc,GAAG,MAAM,eAAe,GAAG,IAAI,WAAW;EACpE;EAEA,OAAO,sBAAsB,KAAA,IACzB,OACA;GAAE,GAAG;GAAM,sBAAsB;EAAkB;CACzD,CAAC;AACH;;;;;;;AAQA,MAAa,4BACX,cAEA,UAAU,KAAK,EAAE,KAAK,UAAU,wBAAwB;CACtD,MAAM,OAA4B;EAChC,MAAM,IAAI;EACV,YAAY,qBAAqB,IAAI,IAAI;EACzC,WAAW,SAAS,KAAK,UAAU,IAAI,IAAI,EAAE;EAC7C,WAAW;EACX,aAAa,IAAI;EACjB,aAAa,IAAI;EACjB,cAAc,IAAI,gBAAgB;EAClC,YAAY,IAAI,iBAAiB,KAAA;EACjC,UAAU;CACZ;CAEA,OAAO,sBAAsB,KAAA,IACzB,OACA;EAAE,GAAG;EAAM,sBAAsB;CAAkB;AACzD,CAAC;;AAGH,MAAa,8BAA8B;;AAG3C,MAAa,0BAA0B,SAAyB,KAAK,KAAK,KAAK,SAAS,CAAC;AAEzF,MAAM,oBAAoB;AAE1B,MAAM,aAAa,WAAuC;CACxD,MAAM,WAAW,qBAAqB,MAAM;CAE5C,IAAI,SAAS,UAAU,mBAAmB,OAAO;CAEjD,IAAI,CAAC,UAAU,UAAU,MAAM,GAAG;EAChC,IAAI,OAAO,SAAS,SAAS,OAAO;EAEpC,IAAI,OAAO,SAAS,UAAU,OAAO;CACvC;CAEA,OAAO;AACT;AAGA,MAAM,mBAAmB,UAA+B;CACtD,MAAM,KAAK;CACX,aAAa,KAAK;CAClB,aAAa,KAAK;CAClB,cAAc,KAAK;CACnB,eAAe,KAAA;AACjB;AAEA,MAAM,kBAAkB,SACtB,aAAa,KAAK,WAAW,wCAAwC,KAAK,KAAK,4BAA4B,UAAU,KAAK,YAAY,EAAE;AAE1I,MAAM,sBAAsB,UAC1B,mBAAmB,EAAE,OAAO,MAAM,IAAI,eAAe,EAAE,CAAC;AAQ1D,MAAM,iBAAiB,SACrB,uBAAuB,mBAAmB,CAAC,IAAI,CAAC,CAAC;AAEnD,MAAM,oBACJ,UAC0C;CAC1C,MAAM,yBAAS,IAAI,IAAsB;CAEzC,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,QAAQ,OAAO,IAAI,KAAK,KAAK,SAAS;EAE5C,IAAI,UAAU,KAAA,GACZ,OAAO,IAAI,KAAK,KAAK,WAAW,CAAC,IAAI,CAAC;OAEtC,MAAM,KAAK,IAAI;CAEnB;CAEA,OAAO;AACT;;;;;;;;AAkBA,MAAa,yBACX,SACA,WACoB;CACpB,MAAM,aAAa,QAAQ,SAAS,MAAM,UACxC,KAAK,aAAa,WAAW,CAAC;EAAE;EAAM;EAAO,MAAM,cAAc,IAAI;CAAE,CAAC,IAAI,CAAC,CAC/E;CAEA,MAAM,SAAS,IAAI,IACjB,CAAC,GAAG,iBAAiB,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,WAAW,CAC5D,WACA,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,MAAM,UAAU,KAAK,OAAO,MAAM,QAAQ,KAAK,QAAQ,MAAM,KAAK,CACrF,CAAC,CACH;CAEA,MAAM,SAAS,IAAI,IACjB,QAAQ,SAAS,MAAM,UACrB,KAAK,aAAa,SAAS,KAAK,aAAa,WAAW,CAAC,KAAK,IAAI,CAAC,CACrE,CACF;CAEA,MAAM,SAAS,IAAI,IAAI,OAAO,KAAK,CAAC;CACpC,IAAI,YAAY;CAEhB,OAAO,OAAO,OAAO,GACnB,KAAK,MAAM,CAAC,WAAW,UAAU,QAAQ;EACvC,IAAI,CAAC,OAAO,IAAI,SAAS,GAAG;EAE5B,MAAM,OAAO,MAAM,MAAM;EAEzB,IAAI,SAAS,KAAA,KAAa,KAAK,OAAO,WAAW;GAC/C,OAAO,OAAO,SAAS;GACvB;EACF;EAEA,OAAO,IAAI,KAAK,KAAK;EACrB,aAAa,KAAK;CACpB;CAGF,MAAM,WAAW,IAAI,IACnB,QAAQ,SAAS,MAAM,UAAW,OAAO,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,KAAK,SAAS,CAAE,CAC9E;CAEA,OAAO;EACL,QAAQ,QAAQ,QAAQ,GAAG,UAAU,OAAO,IAAI,KAAK,CAAC;EACtD,oBAAoB,CAAC,GAAG,IAAI,IAAI,QAAQ,KAAI,SAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,QAAO,cAC3E,SAAS,IAAI,SAAS,CACxB;CACF;AACF;AAEA,MAAM,QAAQ;CACZ;CACA;CACA;CACA;CACA;AACF,CAAC,CAAC,KAAK,IAAI;AAEX,MAAM,aAAa,mBACjB,gNAAgN,eAAe;;AAGjO,MAAM,0BAA0B;AAWhC,MAAM,wBAAwB,aAAyD;CACrF,QAAQ,QAAQ,MAAM,SAAS,EAAE,UAAW,cAAc,GAAG,MAAM,WAAW,CAAC,IAAI,IAAI,IAAI,CAAC,CAAE;CAC9F,QAAQ,QAAQ,MAAM,SAAS,EAAE,UAAU;EACzC,MAAM,YAAY,cAAc,GAAG;EAEnC,OAAO,cAAc,YAAY,cAAc,YAAY,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI;CACjG,CAAC;AACH;AAEA,MAAM,qBAAqB,UACzB,MAAM,OAAO,SAAS,KAAK,MAAM,OAAO,SAAS;;AAGnD,MAAM,oBAAoB,UAA0B;CAClD,MAAM,OAAO,KAAK,IAAI,GAAG,0BAA0B,MAAM,OAAO,MAAM;CACtE,MAAM,SAAS,MAAM,OAAO,SAAS;CAErC,OAAO;EACL;EACA;EACA,GAAG,CAAC,GAAG,MAAM,QAAQ,GAAG,MAAM,OAAO,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,KAAI,SAAQ,OAAO,KAAK,GAAG;EAChF,GAAI,SAAS,IAAI,CAAC,WAAW,OAAO,wCAAwC,IAAI,CAAC;CACnF,CAAC,CAAC,KAAK,IAAI;AACb;AAEA,MAAM,eAAe,UAAmB;CACtC;CACA;CACA;CACA;CACA;CACA;CACA;CACA,GAAI,QACA,CACE,+RACF,IACA,CAAC;AACP;AAEA,MAAM,oBAAoB,WAAmB,UAA8C;CACzF,MAAM,WAAW,MAAM,QAAO,SAAQ,KAAK,aAAa,KAAK;CAC7D,MAAM,SAAS,MAAM,QAAO,SAAQ,KAAK,aAAa,KAAK;CAE3D,MAAM,cAAc,MAAM,MACxB,SAAQ,KAAK,yBAAyB,KAAA,CACxC,CAAC,EAAE;CAEH,OAAO;EACL,OAAO;EACP,GAAI,gBAAgB,KAAA,IAAY,CAAC,IAAI,CAAC,WAAW;EACjD,GAAI,SAAS,SAAS,IAAI;GAAC;GAAS,mBAAmB,QAAQ;GAAG;EAAK,IAAI,CAAC;EAC5E,GAAG,OAAO,IAAI,cAAc;CAC9B,CAAC,CAAC,KAAK,IAAI;AACb;AAGA,MAAM,oBACJ;;;;;;;;;;;AA2BF,MAAa,6BAA6B,UAA4C;CACpF,MAAM,UAAU,gBAAgB,MAAM,KAAK;CAE3C,MAAM,UAAU,sBAAsB,SAAS,MAAM,gBAAA,GAA2C;CAEhG,MAAM,WAAW,CAAC,GAAG,iBAAiB,QAAQ,OAAO,KAAI,UAAS,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,KAC5E,CAAC,WAAW,WACX,iBACE,WACA,MAAM,KAAI,SAAQ,KAAK,IAAI,CAC7B,CACJ;CAEA,MAAM,UACJ,MAAM,YAAY,KAAA,KAAa,MAAM,QAAQ,MAAM,SAAS,IAAI,MAAM,UAAU,KAAA;CAElF,MAAM,iBACJ,YAAY,KAAA,IAAY;EAAE,QAAQ,CAAC;EAAG,QAAQ,CAAC;CAAE,IAAI,qBAAqB,OAAO;CAEnF,OAAO;EACL;EACA;GACE;GACA,GAAG,YAAY,MAAM,UAAU,IAAI;GACnC,GAAI,YAAY,KAAA,IAAY,CAAC,IAAI,CAAC,UAAU,QAAQ,cAAc,CAAC;EACrE,CAAC,CAAC,KAAK,IAAI;EACX,SAAS,WAAW,IAChB,wCACA,CAAC,gCAAgC,GAAG,QAAQ,CAAC,CAAC,KAAK,MAAM;EAC7D,GAAI,kBAAkB,cAAc,IAAI,CAAC,iBAAiB,cAAc,CAAC,IAAI,CAAC;EAC9E;CACF,CAAC,CAAC,KAAK,MAAM;AACf;;AAGA,MAAa,kCAAkC;AAE/C,MAAM,qBAAqB;AAK3B,MAAM,eAAe,aACnB,SACG,MAAM,IAAI,CAAC,CACX,KAAI,SAAQ,KAAK,KAAK,CAAC,CAAC,CACxB,QAAO,SAAQ,CAAC,KAAK,WAAW,IAAI,CAAC,CAAC,CACtC,KAAK,GAAG;AAEb,MAAM,qBAAqB,WAA+B;CACxD,MAAM,WAAW,YAAY,qBAAqB,MAAM,CAAC;CAEzD,OAAO,SAAS,UAAU,oBAAoB,WAAW,UAAU,MAAM;AAC3E;AAGA,MAAM,gBAAgB,cAAsB;CAE1C,MAAM,OAAO,UAAU,MAAM,GAAG,GAAG;CACnC,MAAM,OAAO,KAAK,WAAW,KAAK,SAAS,CAAC;CAE5C,OAAO,GAAG,QAAQ,SAAU,QAAQ,QAAS,KAAK,MAAM,GAAG,EAAE,IAAI,OAAO;AAC1E;;;;;;AAOA,MAAa,yBAAyB,SAAsC;CAC1E,MAAM,YACJ,KAAK,aAAa,UACd,SAAS,KAAK,UAAU,KAAK,IAAI,EAAE,IAAI,YAAY,aAAa,KAAK,WAAW,CAAC,EAAE,KACnF,GAAG,KAAK,UAAU,GAAG,YAAY,aAAa,KAAK,WAAW,CAAC,EAAE,aAAa,kBAAkB,KAAK,YAAY,EAAE;CAEzH,OAAO,UAAU,UAAA,MAA4C,YAAY,aAAa,SAAS;AACjG;;;;;AAMA,MAAa,wBAAwB,SACnC,KAAK,aAAa,UACd;CACE,KAAK;CACL,iEAAiE,KAAK,UAAU,KAAK,IAAI,EAAE;CAC3F,SAAS,aAAa,KAAK,WAAW;AACxC,CAAC,CAAC,KAAK,MAAM,IACb,iBAAiB,gBAAgB,IAAI,CAAC;;;;;;AAO5C,MAAa,oBACX,SACA,SAEA,QAAQ,MAAK,SAAQ,KAAK,aAAa,WAAW,KAAK,SAAS,IAAI,KACpE,QAAQ,MAAK,SAAQ,KAAK,eAAe,IAAI,KAC7C,QAAQ,MAAK,SAAQ,KAAK,SAAS,IAAI;;AAGzC,MAAa,gCAAgC;AAE7C,MAAM,2BAA2B,OAAO,OAAO;CAC7C,MAAM,OAAO;CACb,aAAa,OAAO,SAAS,OAAO,MAAM;;CAE1C,OAAO,OAAO;;CAEd,OAAO,OAAO;;CAEd,SAAS,OAAO;AAClB,CAAC;;AAGD,MAAa,yBAAyB,OAAO,OAAO;CAClD,YAAY,OAAO,MAAM,wBAAwB;;;CAGjD,WAAW,OAAO,OAAO;EACvB,QAAQ,OAAO,MAAM,OAAO,MAAM;EAClC,QAAQ,OAAO,MAAM,OAAO,MAAM;CACpC,CAAC;AACH,CAAC;AAID,MAAM,uBAAuB,OAAO,oBAAoB,sBAAsB;AAE9E,MAAM,uBAAuB,OAAO,aAAa,sBAAsB;AAEvE,MAAM,oBAAoB,UAA8C;CACtE,MAAM,WAAW,MAAM,QAAO,SAAQ,KAAK,aAAa,KAAK;CAC7D,MAAM,SAAS,MAAM,QAAO,SAAQ,KAAK,aAAa,KAAK;CAE3D,OAAO,CACL,GAAI,SAAS,SAAS,IAAI;EAAC;EAAS,mBAAmB,QAAQ;EAAG;CAAK,IAAI,CAAC,GAC5E,GAAG,OAAO,IAAI,cAAc,CAC9B,CAAC,CAAC,KAAK,IAAI;AACb;;;;;;AAOA,MAAa,4BAA4B,UAIX;CAC5B,MAAM,UAAU,gBAAgB,MAAM,KAAK;CAC3C,MAAM,YAAY,MAAM,YAAY,KAAA,IAAY,CAAC,IAAI,yBAAyB,MAAM,QAAQ,KAAK;CACjG,MAAM,UAAU,sBAAsB,SAAS,MAAM,gBAAA,GAA2C;CAChG,MAAM,oBAAoB,iBAAiB,QAAQ,OAAO,KAAI,UAAS,EAAE,KAAK,EAAE,CAAC;CAoBjF,OAAO;EACL,YAnBiB,CAAC,GAAG,iBAAiB,CAAC,GAAG,SAAS,GAAG,SAAS,CAAC,CAAC,KAAI,UAAS,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,KAC1F,CAAC,MAAM,WAAW;GACjB,MAAM,SAAS,kBAAkB,IAAI,IAAI,KAAK,CAAC,EAAA,CAAG,KAAI,SAAQ,KAAK,IAAI;GAEvE,MAAM,cAAc,MAAM,MAAK,SAAQ,KAAK,KAAK,yBAAyB,KAAA,CAAS,CAAC,EAAE,KACnF;GAEH,MAAM,YAAY;IAChB;IACA,OAAO,MAAM;IACb,OAAO,MAAM;IACb,SAAS,iBAAiB,KAAK;GACjC;GAEA,OAAO,gBAAgB,KAAA,IAAY,YAAY;IAAE,GAAG;IAAW;GAAY;EAC7E,CAIS;EACT,WACE,MAAM,YAAY,KAAA,IAAY;GAAE,QAAQ,CAAC;GAAG,QAAQ,CAAC;EAAE,IAAI,qBAAqB,MAAM,OAAO;CACjG;AACF;AAEA,MAAM,iBAAiB,cAA4D;CACjF,MAAM,QAAQ,UAAU,UAAU,IAAI,WAAW,GAAG,UAAU,MAAM;CAEpE,MAAM,QACJ,UAAU,UAAU,UAAU,QAC1B,QACA,UAAU,UAAU,IAClB,GAAG,MAAM,iBACT,GAAG,MAAM,IAAI,UAAU,MAAM;CAErC,OAAO,OAAO,UAAU,KAAK,MAAM,MAAM,GAAG,UAAU,gBAAgB,KAAA,IAAY,KAAK,KAAK,UAAU;AACxG;;AAGA,MAAa,yBAAyB,YACpC;CACE;CACA;CACA,CAAC,iBAAiB,GAAG,QAAQ,WAAW,IAAI,aAAa,CAAC,CAAC,CAAC,KAAK,IAAI;CACrE,GAAG,QAAQ,WAAW,SAAQ,cAC5B,UAAU,QAAQ,WAAW,IAAI,CAAC,IAAI,CAAC,OAAO,UAAU,KAAK,IAAI,UAAU,SAAS,CACtF;CACA,GAAI,kBAAkB,QAAQ,SAAS,IAAI,CAAC,iBAAiB,QAAQ,SAAS,CAAC,IAAI,CAAC;AACtF,CAAC,CAAC,KAAK,MAAM;;;;;AAMf,MAAa,8BAA8B,UAIrB;CACpB,MAAM,UAAU,yBAAyB,KAAK;CAE9C,OAAO;EACL,KAAK;EACL,OACE,QAAQ,WAAW,WAAW,IAC1B,KAAA,IACA,OAAO,eAAe,qBAAqB,OAAO,CAAC;EACzD,SAAQ,YAAW;GACjB,MAAM,UAAU,YAAY,KAAA,IAAY,OAAO,KAAK,IAAI,qBAAqB,OAAO;GAEpF,OAAO,OAAO,OAAO,OAAO,IACxB,4FACA,sBAAsB,QAAQ,KAAK;EACzC;CACF;AACF;;;;;AAMA,MAAa,uCAAuC,UAIlD;CACE;CACA;EACE;EACA,GAAG,YAAY,MAAM,UAAU,IAAI;EACnC,GAAI,MAAM,YAAY,KAAA,IAAY,CAAC,IAAI,CAAC,UAAU,MAAM,QAAQ,cAAc,CAAC;CACjF,CAAC,CAAC,KAAK,IAAI;CACX;AACF,CAAC,CAAC,KAAK,MAAM"}
|
|
1
|
+
{"version":3,"file":"catalog.mjs","names":[],"sources":["../src/catalog.ts"],"sourcesContent":["import { Option, Predicate } from 'effect'\nimport * as Schema from 'effect/Schema'\nimport {\n renderDeclarations,\n renderToolOutputType,\n renderToolSample,\n schemaToType,\n toCodemodeIdentifier\n} from '@earendil-works/pi-codemode/declarations'\nimport {\n interactionJsonEquals,\n isDeferredTool,\n toolDiscovery,\n toolFileJsonSchemaDefinitions\n} from '@yolk-sdk/agent/protocol'\nimport type { RunInstruction } from '@yolk-sdk/agent/loop'\nimport type { NestedTool } from '@yolk-sdk/agent/tools'\nimport type { CodeModeJsonSchema } from './executor.ts'\n\n/** How a nested tool reaches scripts: `all` tools are also direct model tools; `listed` and\n * `search` are the two discovery modes of `codemode`-only tools; `stage` marks a stageable tool\n * (approval-gated, ADR 0007): found by search, never callable, only passed to `stage()`.\n */\nexport type CodeModeToolExposure = 'all' | 'listed' | 'search' | 'pinned' | 'stage'\n\n/** One nested tool as scripts see it. */\nexport type CodeModeCatalogTool = {\n /** Raw tool name; scripts may call `tools[\"<name>\"](args)`. */\n readonly name: string\n /** Identifier for `tools.<identifier>(args)` (invalid identifier characters become `_`). */\n readonly identifier: string\n /**\n * How scripts call this tool, used to prefix its rejections: `tools.<identifier>`, or\n * `tools[\"<name>\"]` when an earlier tool already has the identifier (see `codeModeCallLabels`;\n * when the name is taken too, the tool is unreachable and the label only names it).\n */\n readonly callLabel: string\n /** The tool's `ToolModule.id`. */\n readonly namespace: string\n /** The module's `ToolModule.description`, when set. */\n readonly namespaceDescription?: string\n readonly description: string\n readonly inputSchema: CodeModeJsonSchema\n /** The declared output schema, or `{ type: 'string' }` for text results. */\n readonly outputSchema: CodeModeJsonSchema\n /** True when the tool declares an output schema (calls resolve to `structuredContent`). */\n readonly structured: boolean\n readonly exposure: CodeModeToolExposure\n}\n\nconst textOutputSchema: CodeModeJsonSchema = { type: 'string' }\n\n/**\n * The call label of each tool name, in order, as pi binds `tools`: each tool takes\n * `tools[<identifier>]` unless an earlier tool holds that key (first tool wins), then\n * `tools[<name>]` if still free. The label is `tools.<identifier>` for the tool that holds its\n * identifier, otherwise `tools[\"<name>\"]`. The executor guard and the host rejection prefix share\n * it, so both name a call the same way.\n */\nexport const codeModeCallLabels = (names: ReadonlyArray<string>): ReadonlyArray<string> => {\n const bound = new Set<string>()\n\n return names.map(name => {\n const identifier = toCodemodeIdentifier(name)\n const ownsIdentifier = !bound.has(identifier)\n\n bound.add(identifier)\n bound.add(name)\n\n return ownsIdentifier ? `tools.${identifier}` : `tools[${JSON.stringify(name)}]`\n })\n}\n\n/** Catalog of nested tools in resolution order. */\nexport const codeModeCatalog = (\n tools: ReadonlyArray<NestedTool>\n): ReadonlyArray<CodeModeCatalogTool> => {\n const labels = codeModeCallLabels(tools.map(({ def }) => def.name))\n\n return tools.map(({ def, moduleId, moduleDescription }, index) => {\n const tool: CodeModeCatalogTool = {\n name: def.name,\n identifier: toCodemodeIdentifier(def.name),\n callLabel: labels[index] ?? `tools[${JSON.stringify(def.name)}]`,\n namespace: moduleId,\n description: def.description,\n inputSchema: def.parameters,\n outputSchema: def.outputSchema ?? textOutputSchema,\n structured: def.outputSchema !== undefined,\n // A deferred tool's declaration is not in model context until activated: scripts find it\n // by search like a codemode-only `search` tool (ADR 0007).\n exposure: toolDiscovery(def) ?? (isDeferredTool(def) ? 'search' : 'all')\n }\n\n return moduleDescription === undefined\n ? tool\n : { ...tool, namespaceDescription: moduleDescription }\n })\n}\n\n/**\n * Catalog entries of the stageable tools (ADR 0007). Stageable tools are approval-gated, so scripts\n * can only `stage()` them whatever their `callableBy` (stage-only `codemode` ones are also never\n * declared to the model): searchable and describable, never callable (no `tools` binding), so\n * their call label is the `stage()` call that records them.\n */\nexport const codeModeStageOnlyCatalog = (\n stageable: ReadonlyArray<NestedTool>\n): ReadonlyArray<CodeModeCatalogTool> =>\n stageable.map(({ def, moduleId, moduleDescription }) => {\n const tool: CodeModeCatalogTool = {\n name: def.name,\n identifier: toCodemodeIdentifier(def.name),\n callLabel: `stage(${JSON.stringify(def.name)})`,\n namespace: moduleId,\n description: def.description,\n inputSchema: def.parameters,\n outputSchema: def.outputSchema ?? textOutputSchema,\n structured: def.outputSchema !== undefined,\n exposure: 'stage'\n }\n\n return moduleDescription === undefined\n ? tool\n : { ...tool, namespaceDescription: moduleDescription }\n })\n\n/** Default inline budget of the nested tool listing, in estimated tokens. */\nexport const defaultCodeModeInlineBudget = 3000\n\n/** Estimated tokens of a text: four characters per token, rounded up. */\nexport const estimateCodeModeTokens = (text: string): number => Math.ceil(text.length / 4)\n\n// File fields render by name (`FileRef | FileUnavailable`, declared once in the intro): their\n// `$ref`s become sentinel constants the renderer quotes, and the quoted sentinels become the names.\nconst fileTypeNames = { FileRef: 'FileRef', FileUnavailable: 'FileUnavailable' } as const\n\ntype FileTypeName = keyof typeof fileTypeNames\n\nconst fileTypeSentinel = (name: FileTypeName) => `\\u0000yolk:${name}`\n\nconst isJsonObject = (value: Schema.Json | undefined): value is Schema.JsonObject =>\n Predicate.isObject(value) && !Array.isArray(value)\n\n// The file definitions an output schema holds exactly as tool output schemas lower them.\nconst fileDefinitions = (schema: CodeModeJsonSchema): ReadonlySet<FileTypeName> => {\n const defs = isJsonObject(schema) ? schema['$defs'] : undefined\n\n if (!isJsonObject(defs)) return new Set()\n\n return new Set(\n (['FileRef', 'FileUnavailable'] as const).filter(name =>\n interactionJsonEquals(defs[name], toolFileJsonSchemaDefinitions[name])\n )\n )\n}\n\n// Keywords whose values are data, not schemas: a `$ref`-shaped object there is a literal value.\nconst dataKeywords: ReadonlySet<string> = new Set(['const', 'enum', 'default', 'examples'])\n\n// Keywords whose values map author-chosen names to schemas: their keys are names, never keywords.\nconst schemaMapKeywords: ReadonlySet<string> = new Set([\n 'properties',\n 'patternProperties',\n '$defs',\n 'definitions',\n 'dependentSchemas'\n])\n\nconst mapValues = (\n value: Schema.JsonObject,\n f: (key: string, item: Schema.Json) => Schema.Json\n): Schema.Json =>\n Object.fromEntries(Object.entries(value).map(([key, item]) => [key, f(key, item)]))\n\n// `value` sits at a schema position (or holds schemas, as an array of `anyOf` members does).\nconst withFileSentinels = (value: Schema.Json, names: ReadonlySet<FileTypeName>): Schema.Json => {\n if (Array.isArray(value)) return value.map((item: Schema.Json) => withFileSentinels(item, names))\n\n if (!isJsonObject(value)) return value\n\n const ref = value['$ref']\n\n const named = (['FileRef', 'FileUnavailable'] as const).find(\n name => names.has(name) && ref === `#/$defs/${name}`\n )\n\n if (named !== undefined) return { const: fileTypeSentinel(named) }\n\n return mapValues(value, (key, item) => {\n if (dataKeywords.has(key)) return item\n\n // A named map's values are schemas, whatever their names (a property may be called `enum`).\n if (schemaMapKeywords.has(key) && isJsonObject(item)) {\n return mapValues(item, (_name, schema) => withFileSentinels(schema, names))\n }\n\n return withFileSentinels(item, names)\n })\n}\n\n/** An output schema whose file fields render as `FileRef` / `FileUnavailable`. */\nconst renderableOutput = (schema: CodeModeJsonSchema): CodeModeJsonSchema => {\n const names = fileDefinitions(schema)\n\n if (names.size === 0) return schema\n\n const replaced = withFileSentinels(schema, names)\n\n return isJsonObject(replaced) ? replaced : schema\n}\n\nconst withFileTypeNames = (rendered: string) =>\n (['FileRef', 'FileUnavailable'] as const).reduce(\n (text, name) => text.replaceAll(JSON.stringify(fileTypeSentinel(name)), fileTypeNames[name]),\n rendered\n )\n\nconst renderOutputType = (schema: CodeModeJsonSchema) =>\n withFileTypeNames(renderToolOutputType(renderableOutput(schema)))\n\nconst maxShortTypeChars = 60\n\nconst shortType = (schema: CodeModeJsonSchema): string => {\n const rendered = renderOutputType(schema)\n\n if (rendered.length <= maxShortTypeChars) return rendered\n\n if (!Predicate.isBoolean(schema)) {\n if (schema.type === 'array') return 'Array<unknown>'\n\n if (schema.type === 'object') return 'object'\n }\n\n return 'unknown'\n}\n\n// Declarations only read names, descriptions, and schemas; `execute` is never called.\nconst declarationTool = (tool: CodeModeCatalogTool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n outputSchema: renderableOutput(tool.outputSchema),\n execute: () => undefined\n})\n\nconst directToolLine = (tool: CodeModeCatalogTool) =>\n `- \\`tools.${tool.identifier}(args)\\` takes the arguments of the \\`${tool.name}\\` tool and resolves to \\`${shortType(tool.outputSchema)}\\`.`\n\nconst declarationMembers = (tools: ReadonlyArray<CodeModeCatalogTool>) =>\n withFileTypeNames(renderDeclarations({ tools: tools.map(declarationTool) }))\n\ntype Candidate = {\n readonly tool: CodeModeCatalogTool\n readonly index: number\n readonly cost: number\n}\n\nconst candidateCost = (tool: CodeModeCatalogTool) =>\n estimateCodeModeTokens(declarationMembers([tool]))\n\nconst groupByNamespace = <A extends { readonly tool: CodeModeCatalogTool }>(\n items: ReadonlyArray<A>\n): ReadonlyMap<string, ReadonlyArray<A>> => {\n const groups = new Map<string, Array<A>>()\n\n for (const item of items) {\n const group = groups.get(item.tool.namespace)\n\n if (group === undefined) {\n groups.set(item.tool.namespace, [item])\n } else {\n group.push(item)\n }\n }\n\n return groups\n}\n\nexport type CodeModeListing = {\n /** Tools placed in the description, in catalog order: every `all` tool, and the `listed` tools\n * that fit the budget.\n */\n readonly listed: ReadonlyArray<CodeModeCatalogTool>\n /** Namespaces (catalog order) with callable tools the description does not list. */\n readonly unlistedNamespaces: ReadonlyArray<string>\n}\n\n/**\n * Chooses the tools the description lists. `all` tools are always placed, outside the budget\n * (the model already has their declarations), and so are `pinned` tools. `listed` tools fill\n * `budget` estimated tokens fairly across namespaces: each round, every namespace still in play\n * places its cheapest remaining tool; a namespace whose next tool does not fit drops out. `search`\n * tools are never candidates.\n */\nexport const selectCodeModeListing = (\n catalog: ReadonlyArray<CodeModeCatalogTool>,\n budget: number\n): CodeModeListing => {\n const candidates = catalog.flatMap((tool, index): ReadonlyArray<Candidate> =>\n tool.exposure === 'listed' ? [{ tool, index, cost: candidateCost(tool) }] : []\n )\n\n const queues = new Map(\n [...groupByNamespace(candidates)].map(([namespace, group]) => [\n namespace,\n [...group].sort((left, right) => left.cost - right.cost || left.index - right.index)\n ])\n )\n\n const placed = new Set(\n catalog.flatMap((tool, index) =>\n tool.exposure === 'all' || tool.exposure === 'pinned' ? [index] : []\n )\n )\n\n const inPlay = new Set(queues.keys())\n let remaining = budget\n\n while (inPlay.size > 0) {\n for (const [namespace, queue] of queues) {\n if (!inPlay.has(namespace)) continue\n\n const next = queue.shift()\n\n if (next === undefined || next.cost > remaining) {\n inPlay.delete(namespace)\n continue\n }\n\n placed.add(next.index)\n remaining -= next.cost\n }\n }\n\n const unlisted = new Set(\n catalog.flatMap((tool, index) => (placed.has(index) ? [] : [tool.namespace]))\n )\n\n return {\n listed: catalog.filter((_, index) => placed.has(index)),\n unlistedNamespaces: [...new Set(catalog.map(tool => tool.namespace))].filter(namespace =>\n unlisted.has(namespace)\n )\n }\n}\n\nconst intro = [\n 'Run a JavaScript script that calls tools and returns only what matters.',\n '`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.',\n '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 (`error.code` is set when the tool reports one). Calls still running when the script ends are cancelled, so await every call (use `Promise.all` for parallel calls).',\n '`string` results are text to read; parse JSON only when the tool says it returns JSON.',\n 'Files are typed fields of results: `type FileRef = { fileId: string; name: string; mimeType: string; size: number }`; `type FileUnavailable = { unavailable: string; name: string; mimeType: string }` (`unavailable` says why the file was not stored). Pass `fileId`s to other tools; file bytes never enter the script.',\n 'Only the script output and its return value come back to you: filter and aggregate inside the script and return a small JSON value.'\n].join('\\n')\n\nconst stageLine = (reviewToolName: string) =>\n `- \\`await stage(name, args)\\`: record a call of a stageable tool for review instead of running it; resolves to \\`{ staged: true, key, index }\\`. Nothing runs until a person approves the plan through the \\`${reviewToolName}\\` tool, which the result tells you how to call. Once a script stages, it may only run read tools; a script that already ran a write tool cannot stage. \\`searchTools\\` marks approval-gated tools \\`stageOnly: true\\`: they have no \\`tools\\` binding, only \\`stage()\\` them.`\n\n/** Stageable tool names listed in the description; the rest are summarized with a count. */\nconst maxListedStageableTools = 20\n\n/**\n * Stageable tools the description names: `pinned` ones first, never `search` or deferred ones\n * (whose declaration is not in model context; scripts find them by search, ADR 0007).\n */\ntype StageableNames = {\n readonly pinned: ReadonlyArray<string>\n readonly listed: ReadonlyArray<string>\n}\n\nconst listedStageableNames = (staging: CodeModeStagingDescription): StageableNames => ({\n pinned: staging.tools.flatMap(({ def }) => (toolDiscovery(def) === 'pinned' ? [def.name] : [])),\n listed: staging.tools.flatMap(({ def }) => {\n const discovery = toolDiscovery(def)\n\n return discovery === 'search' || discovery === 'pinned' || isDeferredTool(def) ? [] : [def.name]\n })\n})\n\nconst hasStageableNames = (names: StageableNames) =>\n names.pinned.length > 0 || names.listed.length > 0\n\n/** `pinned` names are always shown; the cap applies to the others. */\nconst stageableSection = (names: StageableNames) => {\n const room = Math.max(0, maxListedStageableTools - names.pinned.length)\n const hidden = names.listed.length - room\n\n return [\n '## Stageable tools',\n 'Approval-gated tools a script cannot call through `tools`, only stage with `await stage(\"<name>\", args)` (arguments as for the tool of the same name):',\n ...[...names.pinned, ...names.listed.slice(0, room)].map(name => `- \\`${name}\\``),\n ...(hidden > 0 ? [`- … and ${hidden} more (find them with \\`searchTools\\`).`] : [])\n ].join('\\n')\n}\n\nconst globalLines = (store: boolean) => [\n '- `text(value)` and `console.log(...values)`: append text to the output.',\n '- `image(dataUrl)`: append a base64 image (a `data:` URL or `{ type: \"image\", data, mimeType }`).',\n '- `exit()`: end the script successfully.',\n '- `ALL_TOOLS`: `{ name, description }` of every callable tool.',\n '- `await searchTools(query, { limit?, namespace? })`: find tools by topic; resolves to `Array<{ name: string; description: string; signature: string }>` (default limit 8). `signature` is the call with its argument and result types. When you already know the exact name of a tool (listed in this description or the catalog, returned by an earlier `searchTools`, or called earlier in this conversation), call it as `tools.<id>(args)` without searching. Otherwise search, return the hits, and call the tool you choose in your next script. Never call a tool in the same script that found it. If a call fails on its arguments, read its `signature` (or `describeTool`) and retry.',\n '- `await describeTool(name)`: the full description and documented TypeScript declaration of a tool, or `undefined`; needed only when a signature is cut or unclear.',\n '- `await describeNamespace(name)`: `{ name, description?, tools: Array<{ name, description }> }` for a namespace, or `undefined`.',\n ...(store\n ? [\n '- `store(key, value)` and `load(key)`: keep JSON values (up to 256 KiB each) for later scripts; writes are saved only when the script succeeds. When fetched data is large, store it and return a summary, then filter it with `load()` in a later script instead of calling the tools again.'\n ]\n : [])\n]\n\nconst namespaceSection = (namespace: string, tools: ReadonlyArray<CodeModeCatalogTool>) => {\n const declared = tools.filter(tool => tool.exposure !== 'all')\n const direct = tools.filter(tool => tool.exposure === 'all')\n\n const description = tools.find(\n tool => tool.namespaceDescription !== undefined\n )?.namespaceDescription\n\n return [\n `### ${namespace}`,\n ...(description === undefined ? [] : [description]),\n ...(declared.length > 0 ? ['```ts', declarationMembers(declared), '```'] : []),\n ...direct.map(directToolLine)\n ].join('\\n')\n}\n\n// Fixed text: never derived from `search` tools or from which tools did not fit the budget.\nconst unlistedToolsLine =\n 'More tools may be callable than are listed here: find them with `await searchTools(query, { namespace? })` (each hit has its call signature) or `await describeNamespace(name)`; call the tool you choose in your next script, and call a tool whose exact name you already know directly, without searching.'\n\n/** Staged tool plans as the description shows them (`NestedToolStagingDescription`). */\nexport type CodeModeStagingDescription = {\n readonly tools: ReadonlyArray<NestedTool>\n readonly reviewToolName: string\n}\n\nexport type CodeModeDescriptionInput = {\n readonly tools: ReadonlyArray<NestedTool>\n readonly inlineBudget?: number\n /** Mention `store()`/`load()` persistence. */\n readonly store?: boolean\n /** Document `stage()` and list the stageable tools that are neither `search` nor deferred. */\n readonly staging?: CodeModeStagingDescription\n}\n\n/**\n * The code mode tool description: intro, globals one per line, then nested tools grouped by\n * namespace, then one fixed line pointing to `searchTools` and `describeNamespace`.\n * `all` tools get one line each outside the budget; `listed` tools are declared within the inline\n * budget; `search` tools never contribute, so adding or removing them (even whole namespaces of\n * them) leaves the text byte-identical. With `staging` (and stageable tools), `stage()` is listed\n * with the globals and the stageable tool names follow the nested tools; `search` and deferred\n * stageable tools are never named, so they leave the text byte-identical too once any stageable\n * tool exists.\n */\nexport const renderCodeModeDescription = (input: CodeModeDescriptionInput): string => {\n const catalog = codeModeCatalog(input.tools)\n\n const listing = selectCodeModeListing(catalog, input.inlineBudget ?? defaultCodeModeInlineBudget)\n\n const sections = [...groupByNamespace(listing.listed.map(tool => ({ tool })))].map(\n ([namespace, items]) =>\n namespaceSection(\n namespace,\n items.map(item => item.tool)\n )\n )\n\n const staging =\n input.staging !== undefined && input.staging.tools.length > 0 ? input.staging : undefined\n\n const stageableNames =\n staging === undefined ? { pinned: [], listed: [] } : listedStageableNames(staging)\n\n return [\n intro,\n [\n 'Globals:',\n ...globalLines(input.store === true),\n ...(staging === undefined ? [] : [stageLine(staging.reviewToolName)])\n ].join('\\n'),\n sections.length === 0\n ? 'Nested tools: none are listed here.'\n : ['## Nested tools by namespace', ...sections].join('\\n\\n'),\n ...(hasStageableNames(stageableNames) ? [stageableSection(stageableNames)] : []),\n unlistedToolsLine\n ].join('\\n\\n')\n}\n\n/** Longest `searchTools` signature, cut marker included (`describeTool` has the whole declaration). */\nexport const maxCodeModeSearchSignatureChars = 800\n\nconst signatureCutMarker = '… (describeTool for the full declaration)'\n\n// A rendered type on one line, without its description comments (`describeTool` keeps them). pi\n// puts every description line on its own `//` line and JSON-quotes keys and literals, so no other\n// line starts with `//`.\nconst compactText = (rendered: string) =>\n rendered\n .split('\\n')\n .map(line => line.trim())\n .filter(line => !line.startsWith('//'))\n .join(' ')\n\nconst compactResultType = (schema: CodeModeJsonSchema) => {\n const rendered = compactText(renderOutputType(schema))\n\n return rendered.length <= maxShortTypeChars ? rendered : shortType(schema)\n}\n\n// Cuts at a code point boundary: never leaves half of a surrogate pair.\nconst cutSignature = (signature: string) => {\n const end = maxCodeModeSearchSignatureChars - signatureCutMarker.length\n const kept = signature.slice(0, end)\n const last = kept.charCodeAt(kept.length - 1)\n\n return `${last >= 0xd800 && last <= 0xdbff ? kept.slice(0, -1) : kept}${signatureCutMarker}`\n}\n\n/**\n * The call a `searchTools` hit shows, enough to call the tool without `describeTool`:\n * `tools.<id>(args): Promise<result>`, or `stage(\"<name>\", args)` for a stageable tool. Bounded:\n * a long one ends with a pointer to `describeTool`.\n */\nexport const codeModeToolSignature = (tool: CodeModeCatalogTool): string => {\n const signature =\n tool.exposure === 'stage'\n ? `stage(${JSON.stringify(tool.name)}, ${compactText(schemaToType(tool.inputSchema))})`\n : `${tool.callLabel}(${compactText(schemaToType(tool.inputSchema))}): Promise<${compactResultType(tool.outputSchema)}>`\n\n return signature.length <= maxCodeModeSearchSignatureChars ? signature : cutSignature(signature)\n}\n\n/**\n * `describeTool` text: the description and TypeScript declaration of one tool. A stage-only tool\n * has no `tools` binding, so it is described as the `stage()` call that records it.\n */\nexport const describeCodeModeTool = (tool: CodeModeCatalogTool): string =>\n tool.exposure === 'stage'\n ? [\n tool.description,\n `Stage-only (approval-gated): record a call with \\`await stage(${JSON.stringify(tool.name)}, args)\\`; it runs only after a person approves the plan.`,\n `args: ${schemaToType(tool.inputSchema)}`\n ].join('\\n\\n')\n : withFileTypeNames(renderToolSample(declarationTool(tool)))\n\n/**\n * Finds a tool by identifier or raw name. A stageable tool's exact raw name wins first: it has no\n * binding, so a callable tool sharing its identifier (`archive-resource` vs `archive_resource`)\n * must not answer for it; callable lookups keep identifier precedence, as their bindings do.\n */\nexport const findCodeModeTool = (\n catalog: ReadonlyArray<CodeModeCatalogTool>,\n name: string\n): CodeModeCatalogTool | undefined =>\n catalog.find(tool => tool.exposure === 'stage' && tool.name === name) ??\n catalog.find(tool => tool.identifier === name) ??\n catalog.find(tool => tool.name === name)\n\n/** The instruction key of the code mode catalog (`catalog: 'instructions'`, ADR 0007). */\nexport const codeModeCatalogInstructionKey = 'yolk.codemode.catalog'\n\nconst CodeModeCatalogNamespace = Schema.Struct({\n name: Schema.String,\n description: Schema.optional(Schema.String),\n /** Every tool scripts reach in the namespace: callable and stage-only. */\n count: Schema.Number,\n /** Tools the listing shows. */\n shown: Schema.Number,\n /** The rendered declarations and direct tool lines of the shown tools. */\n listing: Schema.String\n})\n\n/** The catalog an instruction message records as its state (JSON, never model-visible). */\nexport const CodeModeCatalogSummary = Schema.Struct({\n namespaces: Schema.Array(CodeModeCatalogNamespace),\n /** Stageable tool names the catalog lists: `pinned` ones, then the others (never `search` or\n * deferred ones). */\n stageable: Schema.Struct({\n pinned: Schema.Array(Schema.String),\n listed: Schema.Array(Schema.String)\n })\n})\n\nexport type CodeModeCatalogSummary = typeof CodeModeCatalogSummary.Type\n\nconst decodeCatalogSummary = Schema.decodeUnknownOption(CodeModeCatalogSummary)\n\nconst encodeCatalogSummary = Schema.encodeOption(CodeModeCatalogSummary)\n\nconst namespaceListing = (tools: ReadonlyArray<CodeModeCatalogTool>) => {\n const declared = tools.filter(tool => tool.exposure !== 'all')\n const direct = tools.filter(tool => tool.exposure === 'all')\n\n return [\n ...(declared.length > 0 ? ['```ts', declarationMembers(declared), '```'] : []),\n ...direct.map(directToolLine)\n ].join('\\n')\n}\n\n/**\n * The catalog of one resolution: every namespace with its tool count and description, and the\n * listing of the tools the inline budget (or `pinned`) places. `search` and stage-only tools are\n * counted, never listed.\n */\nexport const summarizeCodeModeCatalog = (input: {\n readonly tools: ReadonlyArray<NestedTool>\n readonly staging?: CodeModeStagingDescription\n readonly inlineBudget?: number\n}): CodeModeCatalogSummary => {\n const catalog = codeModeCatalog(input.tools)\n const stageOnly = input.staging === undefined ? [] : codeModeStageOnlyCatalog(input.staging.tools)\n const listing = selectCodeModeListing(catalog, input.inlineBudget ?? defaultCodeModeInlineBudget)\n const listedByNamespace = groupByNamespace(listing.listed.map(tool => ({ tool })))\n\n const namespaces = [...groupByNamespace([...catalog, ...stageOnly].map(tool => ({ tool })))].map(\n ([name, items]) => {\n const shown = (listedByNamespace.get(name) ?? []).map(item => item.tool)\n\n const description = items.find(item => item.tool.namespaceDescription !== undefined)?.tool\n .namespaceDescription\n\n const namespace = {\n name,\n count: items.length,\n shown: shown.length,\n listing: namespaceListing(shown)\n }\n\n return description === undefined ? namespace : { ...namespace, description }\n }\n )\n\n return {\n namespaces,\n stageable:\n input.staging === undefined ? { pinned: [], listed: [] } : listedStageableNames(input.staging)\n }\n}\n\nconst namespaceLine = (namespace: CodeModeCatalogSummary['namespaces'][number]) => {\n const count = namespace.count === 1 ? '1 tool' : `${namespace.count} tools`\n\n const label =\n namespace.shown === namespace.count\n ? count\n : namespace.shown === 0\n ? `${count}, none listed`\n : `${count}, ${namespace.shown} listed`\n\n return `- \\`${namespace.name}\\` (${label})${namespace.description === undefined ? '' : `: ${namespace.description}`}`\n}\n\n/** The full catalog text of an instruction message. */\nexport const renderCodeModeCatalog = (summary: CodeModeCatalogSummary): string =>\n [\n '# Code mode catalog',\n 'Tools `codemode` scripts can call, by namespace; the latest catalog in the conversation replaces any earlier one. Call a listed tool as `await tools.<id>(args)`; find the others with `await searchTools(query, { namespace })` (each hit has its call signature); call the tool you choose in your next script, and call a tool whose exact name you already know directly, without searching.',\n ['## Namespaces', ...summary.namespaces.map(namespaceLine)].join('\\n'),\n ...summary.namespaces.flatMap(namespace =>\n namespace.listing.length === 0 ? [] : [`### ${namespace.name}\\n${namespace.listing}`]\n ),\n ...(hasStageableNames(summary.stageable) ? [stageableSection(summary.stageable)] : [])\n ].join('\\n\\n')\n\n/**\n * The host instruction of the code mode catalog: its state is the encoded summary, and its render\n * the full catalog (every change renders it whole, see `RunInstruction.render`) or a removal notice.\n */\nexport const codeModeCatalogInstruction = (input: {\n readonly tools: ReadonlyArray<NestedTool>\n readonly staging?: CodeModeStagingDescription\n readonly inlineBudget?: number\n}): RunInstruction => {\n const summary = summarizeCodeModeCatalog(input)\n\n return {\n key: codeModeCatalogInstructionKey,\n state:\n summary.namespaces.length === 0\n ? undefined\n : Option.getOrUndefined(encodeCatalogSummary(summary)),\n render: current => {\n const decoded = current === undefined ? Option.none() : decodeCatalogSummary(current)\n\n return Option.isNone(decoded)\n ? 'Code mode tools are no longer available: do not call previously listed code mode tools.'\n : renderCodeModeCatalog(decoded.value)\n }\n }\n}\n\n/**\n * The code mode description when the catalog is delivered as an instruction: invariant across\n * catalog changes (only staging availability adds the `stage()` line).\n */\nexport const renderCodeModeInstructedDescription = (input: {\n readonly store?: boolean\n readonly staging?: CodeModeStagingDescription\n}): string =>\n [\n intro,\n [\n 'Globals:',\n ...globalLines(input.store === true),\n ...(input.staging === undefined ? [] : [stageLine(input.staging.reviewToolName)])\n ].join('\\n'),\n 'The tools scripts can call are in the \"Code mode catalog\" (namespaces, tool counts and listed tools; the latest one applies). Find the others with `await searchTools(query, { namespace? })` (each hit has its call signature); call the tool you choose in your next script, and call a tool whose exact name you already know directly, without searching.'\n ].join('\\n\\n')\n"],"mappings":"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"}
|
package/dist/executor.d.mts
CHANGED
|
@@ -12,8 +12,9 @@ export type CodeModeToolContext = {
|
|
|
12
12
|
* A function a script can call. Tools are called as `tools.<identifier>(args)` and
|
|
13
13
|
* `tools["<name>"](args)`; the identifier replaces characters that are not valid in a JavaScript
|
|
14
14
|
* identifier with `_`. Arguments and results make a JSON round trip; a rejection surfaces in the
|
|
15
|
-
* script as an `Error` with the same message
|
|
16
|
-
* `<call label>: <message>`, the label being
|
|
15
|
+
* script as an `Error` with the same message and, when the rejected error has a string `code`, that
|
|
16
|
+
* `code` (`makeCodeModeTool` rejects with `<call label>: <message>`, the label being
|
|
17
|
+
* `tools.<identifier>` or `tools["<name>"]`, and the tool's model-visible error code).
|
|
17
18
|
*
|
|
18
19
|
* `args` is the JSON value of the script's first argument (`undefined` when there is none), read
|
|
19
20
|
* once the way `JSON.stringify` reads it (getters and `toJSON` run once); the checked value is the
|
package/dist/executor.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.d.mts","names":[],"sources":["../src/executor.ts"],"mappings":";;;YAIY,qBAAqB;;YAGrB,gBAAgB,SAAS,eAAe,OAAO;YAE/C;;WAED,QAAQ
|
|
1
|
+
{"version":3,"file":"executor.d.mts","names":[],"sources":["../src/executor.ts"],"mappings":";;;YAIY,qBAAqB;;YAGrB,gBAAgB,SAAS,eAAe,OAAO;YAE/C;;WAED,QAAQ;;;;;;;;;;;;;;;;;;;;;;YAuBP;WACD;WACA;WACA,cAAc;WACd,eAAe;;;;;WAKf;WACA,UAAU,eAAe,SAAS,wBAAwB;;;;;;YAOzD,yBAAyB,KAAK;;WAE/B;;WAEA;;;YAIC;WACG;WAAuB;;WACvB;WAAwB;WAAuB;;;;;;;;YAQlD;YAEA;WACD,MAAM;WACN;WACA;;;YAIC;WACD,KAAK;WACL,QAAQ;;YAGP;WACD;;WAEA;WACA,QAAQ;;WAER,QAAQ,cAAc;;WAEtB,cAAc;;YAGb;WACD,OAAO,cAAc;WACrB,SAAS,cAAc;;WAEvB;;WAEA;;WAEA,OAAO;WACP,QAAQ;;;;;;WAMR,UAAU;aAAyB;aAAsB;;;;;;;;;;;;;;YAcxD;WACD,UACP,cACA,SAAS,2BACN,QAAQ"}
|
package/dist/node.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.d.mts","names":[],"sources":["../src/node.ts"],"mappings":";;;qBAsBa;YAED;;;;;WAKD,OAAO,qBAAqB,QAAQ;;;;;WAKpC,qBAAqB;;WAErB;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"node.d.mts","names":[],"sources":["../src/node.ts"],"mappings":";;;qBAsBa;YAED;;;;;WAKD,OAAO,qBAAqB,QAAQ;;;;;WAKpC,qBAAqB;;WAErB;;;;;;;;;;;;;;qBAkfE,yBAAsB,UACxB,8BACR"}
|
package/dist/node.mjs
CHANGED
|
@@ -61,7 +61,7 @@ const wrapperSuffix = "\n}";
|
|
|
61
61
|
* proxy (close-match suggestions). Not a security boundary (`globalThis.tools` is unguarded):
|
|
62
62
|
* arguments are still decoded by the host.
|
|
63
63
|
*/
|
|
64
|
-
const toolsGuardSource = `(function (tools, labels, unbound) {
|
|
64
|
+
const toolsGuardSource = `(function (tools, labels, unbound, rejectionPrefix) {
|
|
65
65
|
// Captured before the script runs, so a script that changes built-ins cannot change the checks.
|
|
66
66
|
const { apply, getPrototypeOf, setPrototypeOf } = Reflect
|
|
67
67
|
const isFinite = Number.isFinite
|
|
@@ -76,6 +76,10 @@ const toolsGuardSource = `(function (tools, labels, unbound) {
|
|
|
76
76
|
const ObjectProto = Object.prototype
|
|
77
77
|
const { trunc, min } = Math
|
|
78
78
|
const stringify = JSON.stringify
|
|
79
|
+
const parse = JSON.parse
|
|
80
|
+
const ErrorCtor = Error
|
|
81
|
+
const startsWith = String.prototype.startsWith
|
|
82
|
+
const sliceString = String.prototype.slice
|
|
79
83
|
const plainJson = 'pass plain JSON (objects, arrays, strings, finite numbers, booleans, null)'
|
|
80
84
|
const maxValues = 100000
|
|
81
85
|
const maxDepth = 64
|
|
@@ -176,6 +180,24 @@ const toolsGuardSource = `(function (tools, labels, unbound) {
|
|
|
176
180
|
apply(setDelete, walk.ancestors, [value])
|
|
177
181
|
return copy
|
|
178
182
|
}
|
|
183
|
+
// The host sends every rejection of a tool call as the prefix and the JSON of { message, code? }
|
|
184
|
+
// (pi only carries a message): rebuild the Error with its code.
|
|
185
|
+
const restored = error => {
|
|
186
|
+
const message = error instanceof ErrorCtor ? error.message : undefined
|
|
187
|
+
if (typeof message !== 'string' || !apply(startsWith, message, [rejectionPrefix])) return error
|
|
188
|
+
let payload
|
|
189
|
+
try {
|
|
190
|
+
payload = parse(apply(sliceString, message, [rejectionPrefix.length]))
|
|
191
|
+
} catch {
|
|
192
|
+
return error
|
|
193
|
+
}
|
|
194
|
+
if (payload === null || typeof payload !== 'object') return error
|
|
195
|
+
const rebuilt = new ErrorCtor(typeof payload.message === 'string' ? payload.message : '')
|
|
196
|
+
if (typeof payload.code === 'string') {
|
|
197
|
+
defineProperty(rebuilt, 'code', { value: payload.code, writable: true, configurable: true })
|
|
198
|
+
}
|
|
199
|
+
return rebuilt
|
|
200
|
+
}
|
|
179
201
|
const wrap = (label, call) => async (...args) => {
|
|
180
202
|
let json
|
|
181
203
|
try {
|
|
@@ -184,7 +206,11 @@ const toolsGuardSource = `(function (tools, labels, unbound) {
|
|
|
184
206
|
if (error instanceof Rejected) throw new TypeErrorCtor(label + ': argument ' + error.message)
|
|
185
207
|
throw error
|
|
186
208
|
}
|
|
187
|
-
|
|
209
|
+
try {
|
|
210
|
+
return await call(json)
|
|
211
|
+
} catch (error) {
|
|
212
|
+
throw restored(error)
|
|
213
|
+
}
|
|
188
214
|
}
|
|
189
215
|
// Each tool's function sits at tools[name] unless an earlier tool holds that key, in which case
|
|
190
216
|
// that earlier function is already wrapped.
|
|
@@ -216,7 +242,7 @@ const guardSuffix = (tools, unbound) => {
|
|
|
216
242
|
const labels = codeModeCallLabels(names);
|
|
217
243
|
const pairs = tools.map((tool, index) => [tool.name, tool.callLabel ?? labels[index] ?? `tools[${JSON.stringify(tool.name)}]`]);
|
|
218
244
|
const rejected = (unbound ?? []).map((entry) => [entry.key, entry.message]);
|
|
219
|
-
return `\n})(${toolsGuardSource}(tools, ${jsonSource(pairs)}, ${jsonSource(rejected)}), console)`;
|
|
245
|
+
return `\n})(${toolsGuardSource}(tools, ${jsonSource(pairs)}, ${jsonSource(rejected)}, ${jsonSource(rejectionPrefix)}), console)`;
|
|
220
246
|
};
|
|
221
247
|
const lineTerminators = /\r\n?|[\n\u2028\u2029]/g;
|
|
222
248
|
/** Lines of the script as QuickJS counts them (at least; never fewer). */
|
|
@@ -273,18 +299,34 @@ const stripTypes = (code) => {
|
|
|
273
299
|
};
|
|
274
300
|
}
|
|
275
301
|
};
|
|
276
|
-
|
|
302
|
+
/**
|
|
303
|
+
* pi passes only the message of a rejected tool call into the VM. Every tool rejection crosses as
|
|
304
|
+
* this prefix and the JSON of `{ message, code? }`, which the tools guard turns back into an
|
|
305
|
+
* `Error` with its `code`. Calls through the unguarded `globalThis.tools` see the encoded message.
|
|
306
|
+
*/
|
|
307
|
+
const rejectionPrefix = "\0yolk-tool-rejection:";
|
|
308
|
+
const RejectionCode = Schema.Struct({ code: Schema.String });
|
|
309
|
+
const encodedRejection = (error) => {
|
|
310
|
+
const payload = { message: error instanceof Error ? error.message : String(error) };
|
|
311
|
+
const code = Option.getOrUndefined(Option.map(Schema.decodeUnknownOption(RejectionCode)(error), (found) => found.code));
|
|
312
|
+
if (code !== void 0) payload.code = code;
|
|
313
|
+
return /* @__PURE__ */ new Error(`${rejectionPrefix}${JSON.stringify(payload)}`);
|
|
314
|
+
};
|
|
315
|
+
const piCallable = (tool, execute) => {
|
|
277
316
|
const converted = {
|
|
278
317
|
name: tool.name,
|
|
279
|
-
execute
|
|
318
|
+
execute
|
|
280
319
|
};
|
|
281
320
|
if (tool.description !== void 0) converted.description = tool.description;
|
|
282
321
|
if (tool.inputSchema !== void 0) converted.inputSchema = tool.inputSchema;
|
|
283
322
|
if (tool.outputSchema !== void 0) converted.outputSchema = tool.outputSchema;
|
|
284
323
|
return converted;
|
|
285
324
|
};
|
|
325
|
+
const piTool = (tool) => piCallable(tool, (args, context) => tool.execute(args, context).catch((error) => {
|
|
326
|
+
throw encodedRejection(error);
|
|
327
|
+
}));
|
|
286
328
|
const piGlobal = (global) => {
|
|
287
|
-
const converted =
|
|
329
|
+
const converted = piCallable(global, (args, context) => global.execute(args, context));
|
|
288
330
|
if (global.spread !== void 0) converted.spread = global.spread;
|
|
289
331
|
if (global.signature !== void 0) converted.signature = global.signature;
|
|
290
332
|
return converted;
|
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 { 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, unbound) {\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 // Unbound keys (stage-only tools) reject with their hint instead of being undefined.\n const rejections = create(null)\n for (const [key, message] of unbound) {\n if (!(key in guarded)) rejections[key] = async () => { throw new TypeErrorCtor(message) }\n }\n return new Proxy(Object.freeze(guarded), {\n get: (target, key) => (key in target ? target[key] : key in rejections ? rejections[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 = (\n tools: ReadonlyArray<CodeModeExecutorTool>,\n unbound: CodeModeExecuteOptions['unbound']\n) => {\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 const rejected = (unbound ?? []).map(entry => [entry.key, entry.message])\n\n return `\\n})(${toolsGuardSource}(tools, ${jsonSource(pairs)}, ${jsonSource(rejected)}), 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(\n `${guardPrefix}${stripped.code}${guardSuffix(execution.tools, execution.unbound)}`,\n {\n signal: execution.signal,\n store: execution.store,\n timeoutMs\n }\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
|
+
{"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, unbound, rejectionPrefix) {\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 parse = JSON.parse\n const ErrorCtor = Error\n const startsWith = String.prototype.startsWith\n const sliceString = String.prototype.slice\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 // The host sends every rejection of a tool call as the prefix and the JSON of { message, code? }\n // (pi only carries a message): rebuild the Error with its code.\n const restored = error => {\n const message = error instanceof ErrorCtor ? error.message : undefined\n if (typeof message !== 'string' || !apply(startsWith, message, [rejectionPrefix])) return error\n let payload\n try {\n payload = parse(apply(sliceString, message, [rejectionPrefix.length]))\n } catch {\n return error\n }\n if (payload === null || typeof payload !== 'object') return error\n const rebuilt = new ErrorCtor(typeof payload.message === 'string' ? payload.message : '')\n if (typeof payload.code === 'string') {\n defineProperty(rebuilt, 'code', { value: payload.code, writable: true, configurable: true })\n }\n return rebuilt\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 try {\n return await call(json)\n } catch (error) {\n throw restored(error)\n }\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 // Unbound keys (stage-only tools) reject with their hint instead of being undefined.\n const rejections = create(null)\n for (const [key, message] of unbound) {\n if (!(key in guarded)) rejections[key] = async () => { throw new TypeErrorCtor(message) }\n }\n return new Proxy(Object.freeze(guarded), {\n get: (target, key) => (key in target ? target[key] : key in rejections ? rejections[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 = (\n tools: ReadonlyArray<CodeModeExecutorTool>,\n unbound: CodeModeExecuteOptions['unbound']\n) => {\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 const rejected = (unbound ?? []).map(entry => [entry.key, entry.message])\n\n return `\\n})(${toolsGuardSource}(tools, ${jsonSource(pairs)}, ${jsonSource(rejected)}, ${jsonSource(rejectionPrefix)}), 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\n/**\n * pi passes only the message of a rejected tool call into the VM. Every tool rejection crosses as\n * this prefix and the JSON of `{ message, code? }`, which the tools guard turns back into an\n * `Error` with its `code`. Calls through the unguarded `globalThis.tools` see the encoded message.\n */\nconst rejectionPrefix = '\\u0000yolk-tool-rejection:'\n\ntype EncodedRejection = { message: string; code?: string }\n\nconst RejectionCode = Schema.Struct({ code: Schema.String })\n\nconst encodedRejection = (error: unknown) => {\n const payload: EncodedRejection = {\n message: error instanceof Error ? error.message : String(error)\n }\n\n const code = Option.getOrUndefined(\n Option.map(Schema.decodeUnknownOption(RejectionCode)(error), found => found.code)\n )\n\n if (code !== undefined) payload.code = code\n\n return new Error(`${rejectionPrefix}${JSON.stringify(payload)}`)\n}\n\nconst piCallable = (\n tool: CodeModeExecutorTool | CodeModeExecutorGlobal,\n execute: CodemodeTool['execute']\n): CodemodeTool => {\n const converted: CodemodeTool = { name: tool.name, execute }\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 piTool = (tool: CodeModeExecutorTool): CodemodeTool =>\n piCallable(tool, (args, context) =>\n tool.execute(args, context).catch((error: unknown) => {\n throw encodedRejection(error)\n })\n )\n\nconst piGlobal = (global: CodeModeExecutorGlobal): CodemodeTool => {\n const converted = piCallable(global, (args, context) => global.execute(args, context))\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(\n `${guardPrefix}${stripped.code}${guardSuffix(execution.tools, execution.unbound)}`,\n {\n signal: execution.signal,\n store: execution.store,\n timeoutMs\n }\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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"}
|