@ny4n/codemode 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +324 -2
- package/dist/cloudflare.d.ts +53 -0
- package/dist/cloudflare.d.ts.map +1 -0
- package/dist/cloudflare.js +64 -0
- package/dist/cloudflare.js.map +1 -0
- package/dist/declarations.d.ts +58 -0
- package/dist/declarations.d.ts.map +1 -0
- package/dist/declarations.js +334 -0
- package/dist/declarations.js.map +1 -0
- package/dist/identifier.d.ts +6 -0
- package/dist/identifier.d.ts.map +1 -0
- package/dist/identifier.js +13 -0
- package/dist/identifier.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/portable.d.ts +15 -0
- package/dist/portable.d.ts.map +1 -0
- package/dist/portable.js +12 -0
- package/dist/portable.js.map +1 -0
- package/dist/runtime/execution.d.ts +54 -0
- package/dist/runtime/execution.d.ts.map +1 -0
- package/dist/runtime/execution.js +371 -0
- package/dist/runtime/execution.js.map +1 -0
- package/dist/runtime/host.d.ts +13 -0
- package/dist/runtime/host.d.ts.map +1 -0
- package/dist/runtime/host.js +72 -0
- package/dist/runtime/host.js.map +1 -0
- package/dist/runtime/inline.d.ts +63 -0
- package/dist/runtime/inline.d.ts.map +1 -0
- package/dist/runtime/inline.js +79 -0
- package/dist/runtime/inline.js.map +1 -0
- package/dist/runtime/prelude-source.d.ts +43 -0
- package/dist/runtime/prelude-source.d.ts.map +1 -0
- package/dist/runtime/prelude-source.js +510 -0
- package/dist/runtime/prelude-source.js.map +1 -0
- package/dist/runtime/protocol.d.ts +75 -0
- package/dist/runtime/protocol.d.ts.map +1 -0
- package/dist/runtime/protocol.js +10 -0
- package/dist/runtime/protocol.js.map +1 -0
- package/dist/runtime/remote.d.ts +102 -0
- package/dist/runtime/remote.d.ts.map +1 -0
- package/dist/runtime/remote.js +170 -0
- package/dist/runtime/remote.js.map +1 -0
- package/dist/runtime/vm.d.ts +22 -0
- package/dist/runtime/vm.d.ts.map +1 -0
- package/dist/runtime/vm.js +171 -0
- package/dist/runtime/vm.js.map +1 -0
- package/dist/runtime/worker.d.ts +2 -0
- package/dist/runtime/worker.d.ts.map +1 -0
- package/dist/runtime/worker.js +38 -0
- package/dist/runtime/worker.js.map +1 -0
- package/dist/source.d.ts +35 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +92 -0
- package/dist/source.js.map +1 -0
- package/dist/types.d.ts +148 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/wasm.d.ts +12 -0
- package/dist/wasm.d.ts.map +1 -0
- package/dist/wasm.js +24 -0
- package/dist/wasm.js.map +1 -0
- package/package.json +75 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Mario Zechner
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,325 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @ny4n/codemode
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Runs model-written JavaScript in a QuickJS VM (compiled to WebAssembly) where the only capability is calling injected tools. Nested tool calls never enter the LLM context; only the script's output and return value do.
|
|
4
|
+
|
|
5
|
+
Scripts use `tools`, `ALL_TOOLS`, `text`, `image`, `exit`, `store`, and `load`, and may start with a `// @options:` line. The coding agent uses it for its built-in `codemode` tool. It has no nyan dependencies and can be used on its own to expose any functions (remote APIs, MCP servers, application services) to model-written scripts.
|
|
6
|
+
|
|
7
|
+
The package runs in two kinds of environments. Each has its own sandbox class, but they share one API, one script environment, and one result format:
|
|
8
|
+
|
|
9
|
+
| Environment | Sandbox | Import from | Where scripts run |
|
|
10
|
+
| --- | --- | --- | --- |
|
|
11
|
+
| Node and Bun | `CodemodeSandbox` | `@ny4n/codemode` | a worker thread per execution |
|
|
12
|
+
| Cloudflare Workers and Durable Objects | `RemoteCodemodeSandbox` | `@ny4n/codemode/portable` | a Durable Object in a separately deployed Worker |
|
|
13
|
+
|
|
14
|
+
See [Usage on Node and Bun](#usage-on-node-and-bun) and [Usage on Cloudflare Workers](#usage-on-cloudflare-workers). A third sandbox, `InlineCodemodeSandbox`, runs scripts on the calling thread for other runtimes, or on Cloudflare when blocking the caller is acceptable.
|
|
15
|
+
|
|
16
|
+
## Usage on Node and Bun
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { CodemodeSandbox } from "@ny4n/codemode";
|
|
20
|
+
|
|
21
|
+
const sandbox = new CodemodeSandbox({
|
|
22
|
+
timeoutMs: 60_000,
|
|
23
|
+
tools: [
|
|
24
|
+
{
|
|
25
|
+
name: "read",
|
|
26
|
+
execute: async (args, { signal }) => {
|
|
27
|
+
const { path } = args as { path: string };
|
|
28
|
+
return await readFile(path, "utf8");
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const result = await sandbox.execute(`
|
|
35
|
+
const source = await tools.read({ path: "package.json" });
|
|
36
|
+
text("bytes " + source.length);
|
|
37
|
+
return JSON.parse(source).name;
|
|
38
|
+
`);
|
|
39
|
+
|
|
40
|
+
console.log(result.output); // [{ type: "text", text: "bytes 1234" }]
|
|
41
|
+
if (result.ok) console.log(result.value); // "@ny4n/codemode"
|
|
42
|
+
else console.error(result.error.kind, result.error.message);
|
|
43
|
+
|
|
44
|
+
await sandbox.close();
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`code` is the body of an async function: `return` and top-level `await` work. Inside the script, with every sandbox:
|
|
48
|
+
|
|
49
|
+
- `tools.<name>(args)` returns a promise. Arguments and results make a JSON round trip. A tool that throws rejects with an `Error` carrying the same message. Tool names are also exposed as identifiers: characters that are not valid in identifiers become `_` (`toCodemodeIdentifier`), so `my-tool` is `tools.my_tool` as well as `tools["my-tool"]`.
|
|
50
|
+
- `ALL_TOOLS` lists `{ name, description }` for every tool, with `name` as the identifier.
|
|
51
|
+
- `text(value)` appends a text item to `result.output`; values other than strings are JSON-stringified. `console.log/info/warn/error/debug` append text items with `console: true`.
|
|
52
|
+
- `image(urlOrItem)` appends an image item. It accepts a base64 `data:` URL, `{ image_url }`, or an MCP `ImageContent` block (`{ type: "image", data, mimeType }`). Remote URLs are rejected.
|
|
53
|
+
- `exit()` ends the script successfully right away, keeping its output and store writes.
|
|
54
|
+
- `globals` passed to the sandbox are called as top-level functions, for example a host helper `image(ref)`. They behave like tools but are not recorded in `result.calls`. A name like `models.classify` puts the function on a frozen `models` object. With `spread: true`, `execute` receives all call arguments as an array instead of the first one, and `signature` replaces the declaration generated from the schemas.
|
|
55
|
+
- `store(key, value)` and `load(key)` read and write JSON values synchronously. See [Store](#store).
|
|
56
|
+
- Nothing else: no timers, `fetch`, `process`, `require`, modules, or `WebAssembly`. `eval` and `Function` work but only produce more code inside the same VM.
|
|
57
|
+
|
|
58
|
+
`timeoutMs: Infinity` disables the deadline; the script then runs until it settles or `signal` aborts it. A script that waits on a promise nothing can settle (no tool call pending, and the VM has no timers or I/O) fails right away instead of hanging.
|
|
59
|
+
|
|
60
|
+
`memoryLimitBytes` caps the VM's heap. Allocations beyond it fail inside the script as `InternalError: out of memory`.
|
|
61
|
+
|
|
62
|
+
## Store
|
|
63
|
+
|
|
64
|
+
`store`/`load` let scripts keep values across executions. The sandbox does not persist anything itself: pass the current values as `options.store`, and a successful result reports what the script changed as `result.storeWrites` (`{ set, delete }`). Failed executions report no writes.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
const result = await sandbox.execute(`store("runs", (load("runs") ?? 0) + 1)`, { store: saved });
|
|
68
|
+
if (result.ok) {
|
|
69
|
+
for (const key of result.storeWrites.delete) delete saved[key];
|
|
70
|
+
Object.assign(saved, result.storeWrites.set);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`load` returns a copy, so mutating it does not change the store. Storing `undefined` deletes the key. A value may be at most `MAX_STORE_VALUE_CHARS` (256 Ki) characters of JSON and all values together at most `MAX_STORE_TOTAL_CHARS` (1 Mi); larger writes throw a `RangeError` inside the script.
|
|
75
|
+
|
|
76
|
+
## Source format
|
|
77
|
+
|
|
78
|
+
`parseCodemodeSource()` accepts a script whose first line may be an options line:
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
// @options: {"max_output_tokens": 2000, "timeout_ms": 30000}
|
|
82
|
+
const source = await tools.read({ path: "package.json" });
|
|
83
|
+
text(JSON.parse(source).name);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Supported fields are `max_output_tokens`, a token budget for the output, and `timeout_ms`, a hard deadline. The sandbox does not act on them; the caller decides. The options line is replaced by an empty line, so line numbers in stack traces still match the input. Empty input, invalid JSON, unknown fields, or an options line without code throw `CodemodeSourceError`. `CODEMODE_SOURCE_GRAMMAR` is a Lark grammar for providers that support grammar-constrained tool input. Both are also available from the lightweight `@ny4n/codemode/source` entry.
|
|
87
|
+
|
|
88
|
+
## Bundled Node and Bun hosts
|
|
89
|
+
|
|
90
|
+
By default `CodemodeSandbox` loads `quickjs-wasi/quickjs.wasm` from the installed package and starts the worker file that sits next to this package's module. Neither exists on disk when the host is bundled, so pass both:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { CodemodeSandbox, loadQuickJSWasm } from "@ny4n/codemode";
|
|
94
|
+
|
|
95
|
+
const sandbox = new CodemodeSandbox({
|
|
96
|
+
tools,
|
|
97
|
+
// Compiled once per path and cached.
|
|
98
|
+
wasm: loadQuickJSWasm(pathToQuickJSWasm),
|
|
99
|
+
// A file of your build containing `import "@ny4n/codemode/worker";`
|
|
100
|
+
workerUrl: new URL("./codemode-worker.js", import.meta.url),
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`workerUrl` accepts a URL or string. For a Bun compiled executable, include the worker as an
|
|
105
|
+
additional build entrypoint and pass its relative source path as a string, for example
|
|
106
|
+
`"./src/codemode-worker.ts"`; Bun resolves that form from its embedded module graph.
|
|
107
|
+
|
|
108
|
+
## Usage on Cloudflare Workers
|
|
109
|
+
|
|
110
|
+
`CodemodeSandbox` needs Node or Bun. It runs each script in a worker thread and reads the QuickJS binary from disk. Cloudflare Workers have neither, and they cannot compile WebAssembly at runtime. Two other sandboxes have the same API, the same script environment, and the same `CodemodeResult`. Only where the VM runs differs:
|
|
111
|
+
|
|
112
|
+
| Sandbox | Where the VM runs | Use it for |
|
|
113
|
+
| --- | --- | --- |
|
|
114
|
+
| `CodemodeSandbox` | a worker thread | Node and Bun |
|
|
115
|
+
| `RemoteCodemodeSandbox` | another isolate, usually a Durable Object in a separate Worker | a Durable Object that runs an agent session |
|
|
116
|
+
| `InlineCodemodeSandbox` | the calling thread | anywhere else with WebAssembly, when blocking the caller is acceptable |
|
|
117
|
+
|
|
118
|
+
Import the last two from `@ny4n/codemode/portable`, which has no Node imports. On Cloudflare, `@ny4n/codemode/cloudflare` adds the deployable Durable Object and the QuickJS module compiled by Wrangler.
|
|
119
|
+
|
|
120
|
+
### Why a separate Worker
|
|
121
|
+
|
|
122
|
+
QuickJS runs synchronously, so a script that computes holds its thread until it awaits a tool. Measured on Cloudflare (October 2026), a script that computed for ten seconds delayed every other request to the session object by the full ten seconds in two placements:
|
|
123
|
+
|
|
124
|
+
- inside the session's Durable Object
|
|
125
|
+
- in a Dynamic Worker loaded by that object
|
|
126
|
+
|
|
127
|
+
In a Durable Object of a separately deployed Worker, the delay was about 50 ms. `RemoteCodemodeSandbox` therefore keeps the tools and call records in the session and sends only the VM to the sandbox Worker. Each tool call travels back to the session over Workers RPC.
|
|
128
|
+
|
|
129
|
+
### Deploying the sandbox Worker
|
|
130
|
+
|
|
131
|
+
A complete setup is in [`examples/cloudflare`](https://github.com/blxzer77/nyan/tree/main/packages/codemode/examples/cloudflare). The sandbox Worker is one file:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
// sandbox/src/index.ts
|
|
135
|
+
export { CodemodeSandboxDurableObject as CodemodeSandbox } from "@ny4n/codemode/cloudflare";
|
|
136
|
+
|
|
137
|
+
export default {
|
|
138
|
+
fetch: () => new Response("Not found", { status: 404 }),
|
|
139
|
+
};
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```jsonc
|
|
143
|
+
// sandbox/wrangler.jsonc
|
|
144
|
+
{
|
|
145
|
+
"name": "nyan-codemode-sandbox",
|
|
146
|
+
"main": "src/index.ts",
|
|
147
|
+
"compatibility_date": "2026-04-22",
|
|
148
|
+
"workers_dev": false,
|
|
149
|
+
"preview_urls": false,
|
|
150
|
+
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["CodemodeSandbox"] }],
|
|
151
|
+
"limits": { "cpu_ms": 60000 }
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Install `@ny4n/codemode` in the sandbox Worker's project and run `npx wrangler deploy`. Wrangler bundles QuickJS (about 640 KB) as a compiled WebAssembly module. The object has no routes and keeps no state, and it never uses its storage.
|
|
156
|
+
|
|
157
|
+
### Using it from the agent's Worker
|
|
158
|
+
|
|
159
|
+
Bind the class with `script_name`. Binding a class exported from the agent's own Worker would bring back the blocking described above.
|
|
160
|
+
|
|
161
|
+
```jsonc
|
|
162
|
+
// agent/wrangler.jsonc
|
|
163
|
+
"durable_objects": {
|
|
164
|
+
"bindings": [
|
|
165
|
+
{ "name": "CODEMODE_SANDBOX", "class_name": "CodemodeSandbox", "script_name": "nyan-codemode-sandbox" }
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Then replace the sandbox. Tools, options other than `wasm` and `workerUrl`, and results stay the same:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import type { CodemodeSandboxDurableObject } from "@ny4n/codemode/cloudflare";
|
|
174
|
+
import { RemoteCodemodeSandbox } from "@ny4n/codemode/portable";
|
|
175
|
+
|
|
176
|
+
interface Env {
|
|
177
|
+
CODEMODE_SANDBOX: DurableObjectNamespace<CodemodeSandboxDurableObject>;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Before: new CodemodeSandbox({ tools, timeoutMs: 60_000 })
|
|
181
|
+
const sandbox = new RemoteCodemodeSandbox({
|
|
182
|
+
tools,
|
|
183
|
+
timeoutMs: 60_000,
|
|
184
|
+
remote: () => env.CODEMODE_SANDBOX.getByName(sessionId),
|
|
185
|
+
});
|
|
186
|
+
const result = await sandbox.execute(code, { signal, store });
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Import the sandbox from `/portable` in the agent's Worker. Importing `/cloudflare` would also bundle QuickJS there. `remote` is called once per execution and picks the Durable Object that runs it.
|
|
190
|
+
|
|
191
|
+
- **One object per session**, `getByName(sessionId)`, keeps a session's scripts together. Two scripts of the same session take turns on one thread.
|
|
192
|
+
- **One object per execution**, `getByName(crypto.randomUUID())`, runs every script on its own.
|
|
193
|
+
|
|
194
|
+
Run both Workers locally with `wrangler dev -c agent/wrangler.jsonc -c sandbox/wrangler.jsonc`.
|
|
195
|
+
|
|
196
|
+
### Limits on Cloudflare
|
|
197
|
+
|
|
198
|
+
- **Interrupt budget.** While a script computes without calling a tool, no timer can run, and `Date.now()` does not advance. A script that never yields is limited only by the interrupt budget, not by `timeoutMs`. QuickJS polls an interrupt handler while it runs, and the sandbox counts the polls. Past the budget, the execution fails with `kind: "timeout"`.
|
|
199
|
+
- The default `DEFAULT_INTERRUPT_BUDGET` of 100,000 polls is about 2 seconds of a tight loop on an Apple M-series laptop and about 10 seconds on Cloudflare.
|
|
200
|
+
- Keep the budget below the Worker's `cpu_ms` limit (30 seconds by default). Hitting that limit resets the object instead of failing one script.
|
|
201
|
+
- **Memory.** Each VM may use up to 32 MiB by default. All VMs in one isolate share its 128 MB.
|
|
202
|
+
- **Changing the limits.** The Durable Object caps every request at its own limits, whatever the client asks for. To change them, subclass it in the sandbox Worker:
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
import { CodemodeSandboxDurableObject } from "@ny4n/codemode/cloudflare";
|
|
206
|
+
|
|
207
|
+
export class CodemodeSandbox extends CodemodeSandboxDurableObject {
|
|
208
|
+
protected maxInterruptBudget = 200_000;
|
|
209
|
+
protected maxMemoryLimitBytes = 64 * 1024 * 1024;
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
- **Aborts and timeouts.** `timeoutMs` and `signal` end the execution in the caller right away, with `kind: "timeout"` or `kind: "aborted"`. The remote VM stops at its next tool call. A remote VM that is computing at that moment keeps running until it calls a tool or uses up its budget.
|
|
214
|
+
- **Billing.** Each execution is one Durable Object request. The sandbox object is billed for wall-clock duration while an execution runs, including time spent waiting on tools.
|
|
215
|
+
|
|
216
|
+
### On the calling thread
|
|
217
|
+
|
|
218
|
+
When blocking the caller is acceptable, for example in a stateless Worker that serves one request, run the VM on the caller's thread instead. Nothing extra needs to be deployed:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
import { createInlineCodemodeSandbox } from "@ny4n/codemode/cloudflare";
|
|
222
|
+
|
|
223
|
+
const sandbox = createInlineCodemodeSandbox({ tools, timeoutMs: 60_000, interruptBudget: 50_000 });
|
|
224
|
+
const result = await sandbox.execute(code);
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`createInlineCodemodeSandbox()` passes the QuickJS module Wrangler compiled to `InlineCodemodeSandbox`. With another bundler, import `InlineCodemodeSandbox` from `/portable` and pass your own compiled module as `wasm`.
|
|
228
|
+
|
|
229
|
+
- **Blocking.** A script that computes blocks the caller until it awaits a tool or uses up its `interruptBudget`.
|
|
230
|
+
- **Stack.** QuickJS's stack guard is reduced to `INLINE_MAX_STACK_SIZE`, so that it trips before the caller's smaller stack runs out.
|
|
231
|
+
|
|
232
|
+
## Declarations for the model
|
|
233
|
+
|
|
234
|
+
Tools and globals can carry `description`, `inputSchema`, and `outputSchema` (JSON Schema). `renderDeclarations()` turns them into TypeScript declarations for a model-facing tool description:
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
renderDeclarations({ tools: sandbox.tools, globals: sandbox.globals });
|
|
238
|
+
// declare const tools: {
|
|
239
|
+
// /** Read a file */
|
|
240
|
+
// read(args: {
|
|
241
|
+
// path: string;
|
|
242
|
+
// }): Promise<string>;
|
|
243
|
+
// };
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Schemas only shape the declarations; values are not validated against them. Local references (`#/$defs/...`, `#/definitions/...`) are expanded; recursive and remote references render as `unknown`.
|
|
247
|
+
|
|
248
|
+
## Using with @ny4n/agent-core
|
|
249
|
+
|
|
250
|
+
To give an `Agent` a codemode tool, expose its other tools to the sandbox and wrap `execute()` as an `AgentTool`:
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
import type { AgentTool } from "@ny4n/agent-core";
|
|
254
|
+
import {
|
|
255
|
+
type CodemodeJsonSchema,
|
|
256
|
+
CodemodeSandbox,
|
|
257
|
+
type CodemodeTool,
|
|
258
|
+
renderDeclarations,
|
|
259
|
+
} from "@ny4n/codemode";
|
|
260
|
+
import { Type } from "typebox";
|
|
261
|
+
|
|
262
|
+
const sandboxTools: CodemodeTool[] = agentTools.map((tool) => ({
|
|
263
|
+
name: tool.name,
|
|
264
|
+
description: tool.description,
|
|
265
|
+
inputSchema: tool.parameters as CodemodeJsonSchema,
|
|
266
|
+
outputSchema: (tool.outputSchema as CodemodeJsonSchema | undefined) ?? { type: "string" },
|
|
267
|
+
execute: async (args, { signal }) => {
|
|
268
|
+
const result = await tool.execute("nested", args as never, signal);
|
|
269
|
+
if (tool.outputSchema && result.structuredContent !== undefined) return result.structuredContent;
|
|
270
|
+
return result.content.map((block) => (block.type === "text" ? block.text : "")).join("\n");
|
|
271
|
+
},
|
|
272
|
+
}));
|
|
273
|
+
|
|
274
|
+
const codemodeTool: AgentTool = {
|
|
275
|
+
name: "codemode",
|
|
276
|
+
label: "Codemode",
|
|
277
|
+
description: `Run JavaScript that calls tools as \`await tools.<name>(args)\`. Output with text() or return.\n\n${renderDeclarations({ tools: sandboxTools })}`,
|
|
278
|
+
parameters: Type.Object({ code: Type.String() }),
|
|
279
|
+
execute: async (_toolCallId, { code }, signal) => {
|
|
280
|
+
const sandbox = new CodemodeSandbox({ tools: sandboxTools });
|
|
281
|
+
try {
|
|
282
|
+
const result = await sandbox.execute(code, { signal });
|
|
283
|
+
const content = [...result.output];
|
|
284
|
+
if (result.ok && result.value !== undefined) content.push({ type: "text", text: JSON.stringify(result.value) });
|
|
285
|
+
if (!result.ok) content.push({ type: "text", text: result.error.stack ?? result.error.message });
|
|
286
|
+
return { content, details: undefined, isError: !result.ok };
|
|
287
|
+
} finally {
|
|
288
|
+
await sandbox.close();
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
`result.output` items already have the shape of `@ny4n/ai`'s `TextContent` and `ImageContent`. Calling `tool.execute()` directly skips the agent's `beforeToolCall` and `afterToolCall` hooks. To apply them to nested calls too, run each call through `runToolCall()` from `@ny4n/agent-core`, as the [mcp-codemode example](https://github.com/blxzer77/nyan/tree/main/packages/agent/examples/mcp-codemode) does. That example also rejects failed nested calls inside the script and combines codemode with MCP tools.
|
|
295
|
+
|
|
296
|
+
## Results
|
|
297
|
+
|
|
298
|
+
`execute()` never rejects for script failures. `result.error.kind` is one of:
|
|
299
|
+
|
|
300
|
+
| kind | meaning |
|
|
301
|
+
| --------- | --------------------------------------------------------------------------- |
|
|
302
|
+
| `script` | the script threw or failed to parse; `stack` points at `codemode.js:<line>` |
|
|
303
|
+
| `timeout` | the deadline expired or the interrupt budget ran out; the VM was stopped |
|
|
304
|
+
| `aborted` | `options.signal` fired or `close()` was called; the VM was stopped |
|
|
305
|
+
| `sandbox` | the VM or its transport failed, for example a wasm trap, a missing worker file, or a failed remote |
|
|
306
|
+
|
|
307
|
+
`result.output` holds the text and image items in the order the script produced them, also for failed executions. The host keeps all of it until the script ends, so output is limited to `MAX_OUTPUT_CHARS` (16 Mi) characters of text and base64 image data and `MAX_OUTPUT_ITEMS` (100000) items. Past either limit the script fails with a `RangeError`, even if it catches the error. `result.calls` lists every tool call with `status: "ok" | "error" | "cancelled"`. A call that is still running when the script returns (not awaited) is aborted through the tool's `signal` and reported as `cancelled`.
|
|
308
|
+
|
|
309
|
+
## How it works
|
|
310
|
+
|
|
311
|
+
This section describes `CodemodeSandbox`. The other sandboxes share the same host side, prelude, and VM, and replace only the worker thread. See [Usage on Cloudflare Workers](#usage-on-cloudflare-workers).
|
|
312
|
+
|
|
313
|
+
Each `execute()` starts a worker thread (about 20 ms including VM creation) that instantiates a fresh QuickJS VM from the compiled wasm module. The VM is a separate wasm instance with its own linear memory. Its only imports are a WASI shim (clock, random, and stdout/stderr writes, which the worker discards) and one host-call entry point, so the script cannot reach the host except through the functions the worker registers.
|
|
314
|
+
|
|
315
|
+
The worker evaluates a prelude inside the VM that holds the single host bridge in a closure and builds `tools`, `console`, and globals on top of it. The script is compiled as an async function body.
|
|
316
|
+
|
|
317
|
+
Tool calls are relayed to the host thread as messages; the host runs the tool and posts the JSON result back. The host owns the deadline and the abort signal. When either fires, it sets a shared interrupt flag that the VM polls, then calls `worker.terminate()`. The flag is needed on Bun, where `terminate()` cannot stop a thread that is spinning in wasm (`while (true) {}` or `while (true) await null`).
|
|
318
|
+
|
|
319
|
+
The worker keeps script execution off the host thread: QuickJS runs synchronously, so a spinning script on the host thread would block its event loop.
|
|
320
|
+
|
|
321
|
+
## Runtime notes
|
|
322
|
+
|
|
323
|
+
- Works the same on Node and Bun, including memory limits, interrupts, and a catchable `RangeError` for deep recursion (QuickJS's stack guard is enabled; without it the wasm stack overflows and traps).
|
|
324
|
+
- Stack traces use QuickJS frames (`at f (codemode.js:2:31)`), prefixed with `Name: message` like V8. The wrapper prefix shares line 1 with the script, so line numbers are exact; column numbers on line 1 are shifted.
|
|
325
|
+
- QuickJS is an interpreter. Glue code and filtering tool results are fast enough; heavy computation is slower than in V8.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Workers entry, for Workers built with Wrangler.
|
|
3
|
+
*
|
|
4
|
+
* Wrangler's default `CompiledWasm` rule turns the `.wasm` import below into a
|
|
5
|
+
* `WebAssembly.Module`; Workers cannot compile WebAssembly from bytes at runtime. Other bundlers
|
|
6
|
+
* do not support the import: use `@ny4n/codemode/portable` with your own module there.
|
|
7
|
+
*
|
|
8
|
+
* Two ways to run codemode on Cloudflare:
|
|
9
|
+
* - `RemoteCodemodeSandbox` with a {@link CodemodeSandboxDurableObject} deployed as its own
|
|
10
|
+
* Worker: scripts run in a separate isolate and never block the caller. Use this from a Durable
|
|
11
|
+
* Object that serves a session.
|
|
12
|
+
* - {@link createInlineCodemodeSandbox}: scripts run on the caller's thread. Simplest; a script
|
|
13
|
+
* that computes blocks the caller until it awaits a tool or uses up its interrupt budget.
|
|
14
|
+
*/
|
|
15
|
+
import { DurableObject } from "cloudflare:workers";
|
|
16
|
+
import { InlineCodemodeSandbox, type InlineCodemodeSandboxOptions } from "./runtime/inline.ts";
|
|
17
|
+
import { type CodemodeRemoteExchange, type CodemodeRemoteRequest } from "./runtime/remote.ts";
|
|
18
|
+
import type { CodemodeWasmModule } from "./wasm.ts";
|
|
19
|
+
export * from "./portable.ts";
|
|
20
|
+
/** Default VM memory limit of {@link CodemodeSandboxDurableObject}. Isolates have 128 MB in total. */
|
|
21
|
+
export declare const DEFAULT_REMOTE_MEMORY_LIMIT_BYTES: number;
|
|
22
|
+
/** `quickjs-wasi/quickjs.wasm`, compiled by Wrangler. */
|
|
23
|
+
export declare const cloudflareQuickJSWasm: CodemodeWasmModule;
|
|
24
|
+
/** `InlineCodemodeSandbox` with the QuickJS module Wrangler bundled. */
|
|
25
|
+
export declare function createInlineCodemodeSandbox(options?: Omit<InlineCodemodeSandboxOptions, "wasm">): InlineCodemodeSandbox;
|
|
26
|
+
/**
|
|
27
|
+
* The remote end of `RemoteCodemodeSandbox`: a Durable Object whose one RPC method runs a script's
|
|
28
|
+
* VM and calls back into the client for every tool call.
|
|
29
|
+
*
|
|
30
|
+
* Deploy it as its own Worker and bind it from the Worker that runs your agent with `script_name`.
|
|
31
|
+
* A separate Worker is what keeps scripts off the agent's thread: a class in the same Worker can
|
|
32
|
+
* share an isolate, and therefore a thread, with the objects that call it.
|
|
33
|
+
*
|
|
34
|
+
* The object keeps no state between executions and never touches storage. Executions that reach
|
|
35
|
+
* the same instance share its thread: they interleave while they wait on tools, and a script that
|
|
36
|
+
* computes delays the others. Name instances per session (`getByName(sessionId)`) to keep one
|
|
37
|
+
* session's scripts together, or per execution for full independence.
|
|
38
|
+
*
|
|
39
|
+
* Limits apply to every request, whatever the client asks for. Override the fields in a subclass:
|
|
40
|
+
*
|
|
41
|
+
* export class CodemodeSandbox extends CodemodeSandboxDurableObject {
|
|
42
|
+
* protected maxInterruptBudget = 200_000;
|
|
43
|
+
* }
|
|
44
|
+
*/
|
|
45
|
+
export declare class CodemodeSandboxDurableObject<Env = unknown> extends DurableObject<Env> {
|
|
46
|
+
/** Interrupt polls per execution; the client may only ask for less. See `InlineCodemodeSandboxOptions`. */
|
|
47
|
+
protected maxInterruptBudget: number;
|
|
48
|
+
/** VM memory per execution; the client may only ask for less. */
|
|
49
|
+
protected maxMemoryLimitBytes: number;
|
|
50
|
+
/** Called by `RemoteCodemodeSandbox` over RPC. Resolves when the script has settled or was stopped. */
|
|
51
|
+
execute(request: CodemodeRemoteRequest, exchange: CodemodeRemoteExchange): Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=cloudflare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../src/cloudflare.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAEN,qBAAqB,EACrB,KAAK,4BAA4B,EACjC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,sBAAsB,EAAE,KAAK,qBAAqB,EAAuB,MAAM,qBAAqB,CAAC;AACnH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAEpD,cAAc,eAAe,CAAC;AAM9B,sGAAsG;AACtG,eAAO,MAAM,iCAAiC,QAAmB,CAAC;AAElE,yDAAyD;AACzD,eAAO,MAAM,qBAAqB,EAAE,kBAAgC,CAAC;AAErE,wEAAwE;AACxE,wBAAgB,2BAA2B,CAAC,OAAO,GAAE,IAAI,CAAC,4BAA4B,EAAE,MAAM,CAAM,yBAEnG;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,4BAA4B,CAAC,GAAG,GAAG,OAAO,CAAE,SAAQ,aAAa,CAAC,GAAG,CAAC;IAClF,2GAA2G;IAC3G,SAAS,CAAC,kBAAkB,EAAE,MAAM,CAA4B;IAChE,iEAAiE;IACjE,SAAS,CAAC,mBAAmB,EAAE,MAAM,CAAqC;IAE1E,uGAAuG;IACvG,OAAO,CAAC,OAAO,EAAE,qBAAqB,EAAE,QAAQ,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAMvF;CACD"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Workers entry, for Workers built with Wrangler.
|
|
3
|
+
*
|
|
4
|
+
* Wrangler's default `CompiledWasm` rule turns the `.wasm` import below into a
|
|
5
|
+
* `WebAssembly.Module`; Workers cannot compile WebAssembly from bytes at runtime. Other bundlers
|
|
6
|
+
* do not support the import: use `@ny4n/codemode/portable` with your own module there.
|
|
7
|
+
*
|
|
8
|
+
* Two ways to run codemode on Cloudflare:
|
|
9
|
+
* - `RemoteCodemodeSandbox` with a {@link CodemodeSandboxDurableObject} deployed as its own
|
|
10
|
+
* Worker: scripts run in a separate isolate and never block the caller. Use this from a Durable
|
|
11
|
+
* Object that serves a session.
|
|
12
|
+
* - {@link createInlineCodemodeSandbox}: scripts run on the caller's thread. Simplest; a script
|
|
13
|
+
* that computes blocks the caller until it awaits a tool or uses up its interrupt budget.
|
|
14
|
+
*/
|
|
15
|
+
import { DurableObject } from "cloudflare:workers";
|
|
16
|
+
import quickjsWasmImport from "quickjs-wasi/quickjs.wasm";
|
|
17
|
+
import { DEFAULT_INTERRUPT_BUDGET, InlineCodemodeSandbox, } from "./runtime/inline.js";
|
|
18
|
+
import { serveCodemodeRemote } from "./runtime/remote.js";
|
|
19
|
+
export * from "./portable.js";
|
|
20
|
+
// Wrangler's `CompiledWasm` rule makes this a compiled module; the declaration cannot say so (see
|
|
21
|
+
// cloudflare-modules.d.ts).
|
|
22
|
+
const quickjsWasm = quickjsWasmImport;
|
|
23
|
+
/** Default VM memory limit of {@link CodemodeSandboxDurableObject}. Isolates have 128 MB in total. */
|
|
24
|
+
export const DEFAULT_REMOTE_MEMORY_LIMIT_BYTES = 32 * 1024 * 1024;
|
|
25
|
+
/** `quickjs-wasi/quickjs.wasm`, compiled by Wrangler. */
|
|
26
|
+
export const cloudflareQuickJSWasm = quickjsWasm;
|
|
27
|
+
/** `InlineCodemodeSandbox` with the QuickJS module Wrangler bundled. */
|
|
28
|
+
export function createInlineCodemodeSandbox(options = {}) {
|
|
29
|
+
return new InlineCodemodeSandbox({ ...options, wasm: quickjsWasm });
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The remote end of `RemoteCodemodeSandbox`: a Durable Object whose one RPC method runs a script's
|
|
33
|
+
* VM and calls back into the client for every tool call.
|
|
34
|
+
*
|
|
35
|
+
* Deploy it as its own Worker and bind it from the Worker that runs your agent with `script_name`.
|
|
36
|
+
* A separate Worker is what keeps scripts off the agent's thread: a class in the same Worker can
|
|
37
|
+
* share an isolate, and therefore a thread, with the objects that call it.
|
|
38
|
+
*
|
|
39
|
+
* The object keeps no state between executions and never touches storage. Executions that reach
|
|
40
|
+
* the same instance share its thread: they interleave while they wait on tools, and a script that
|
|
41
|
+
* computes delays the others. Name instances per session (`getByName(sessionId)`) to keep one
|
|
42
|
+
* session's scripts together, or per execution for full independence.
|
|
43
|
+
*
|
|
44
|
+
* Limits apply to every request, whatever the client asks for. Override the fields in a subclass:
|
|
45
|
+
*
|
|
46
|
+
* export class CodemodeSandbox extends CodemodeSandboxDurableObject {
|
|
47
|
+
* protected maxInterruptBudget = 200_000;
|
|
48
|
+
* }
|
|
49
|
+
*/
|
|
50
|
+
export class CodemodeSandboxDurableObject extends DurableObject {
|
|
51
|
+
/** Interrupt polls per execution; the client may only ask for less. See `InlineCodemodeSandboxOptions`. */
|
|
52
|
+
maxInterruptBudget = DEFAULT_INTERRUPT_BUDGET;
|
|
53
|
+
/** VM memory per execution; the client may only ask for less. */
|
|
54
|
+
maxMemoryLimitBytes = DEFAULT_REMOTE_MEMORY_LIMIT_BYTES;
|
|
55
|
+
/** Called by `RemoteCodemodeSandbox` over RPC. Resolves when the script has settled or was stopped. */
|
|
56
|
+
execute(request, exchange) {
|
|
57
|
+
return serveCodemodeRemote(request, exchange, {
|
|
58
|
+
wasm: quickjsWasm,
|
|
59
|
+
maxInterruptBudget: this.maxInterruptBudget,
|
|
60
|
+
maxMemoryLimitBytes: this.maxMemoryLimitBytes,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=cloudflare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../src/cloudflare.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,iBAAiB,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EACN,wBAAwB,EACxB,qBAAqB,GAErB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAA2D,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAGnH,cAAc,eAAe,CAAC;AAE9B,kGAAkG;AAClG,4BAA4B;AAC5B,MAAM,WAAW,GAAG,iBAAkD,CAAC;AAEvE,sGAAsG;AACtG,MAAM,CAAC,MAAM,iCAAiC,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAElE,yDAAyD;AACzD,MAAM,CAAC,MAAM,qBAAqB,GAAuB,WAAW,CAAC;AAErE,wEAAwE;AACxE,MAAM,UAAU,2BAA2B,CAAC,OAAO,GAA+C,EAAE;IACnG,OAAO,IAAI,qBAAqB,CAAC,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,OAAO,4BAA4C,SAAQ,aAAkB;IAClF,2GAA2G;IACjG,kBAAkB,GAAW,wBAAwB,CAAC;IAChE,iEAAiE;IACvD,mBAAmB,GAAW,iCAAiC,CAAC;IAE1E,uGAAuG;IACvG,OAAO,CAAC,OAA8B,EAAE,QAAgC;QACvE,OAAO,mBAAmB,CAAC,OAAO,EAAE,QAAQ,EAAE;YAC7C,IAAI,EAAE,WAAW;YACjB,kBAAkB,EAAE,IAAI,CAAC,kBAAkB;YAC3C,mBAAmB,EAAE,IAAI,CAAC,mBAAmB;SAC7C,CAAC,CAAC;IACJ,CAAC;CACD","sourcesContent":["/**\n * Cloudflare Workers entry, for Workers built with Wrangler.\n *\n * Wrangler's default `CompiledWasm` rule turns the `.wasm` import below into a\n * `WebAssembly.Module`; Workers cannot compile WebAssembly from bytes at runtime. Other bundlers\n * do not support the import: use `@ny4n/codemode/portable` with your own module there.\n *\n * Two ways to run codemode on Cloudflare:\n * - `RemoteCodemodeSandbox` with a {@link CodemodeSandboxDurableObject} deployed as its own\n * Worker: scripts run in a separate isolate and never block the caller. Use this from a Durable\n * Object that serves a session.\n * - {@link createInlineCodemodeSandbox}: scripts run on the caller's thread. Simplest; a script\n * that computes blocks the caller until it awaits a tool or uses up its interrupt budget.\n */\nimport { DurableObject } from \"cloudflare:workers\";\nimport quickjsWasmImport from \"quickjs-wasi/quickjs.wasm\";\nimport {\n\tDEFAULT_INTERRUPT_BUDGET,\n\tInlineCodemodeSandbox,\n\ttype InlineCodemodeSandboxOptions,\n} from \"./runtime/inline.ts\";\nimport { type CodemodeRemoteExchange, type CodemodeRemoteRequest, serveCodemodeRemote } from \"./runtime/remote.ts\";\nimport type { CodemodeWasmModule } from \"./wasm.ts\";\n\nexport * from \"./portable.ts\";\n\n// Wrangler's `CompiledWasm` rule makes this a compiled module; the declaration cannot say so (see\n// cloudflare-modules.d.ts).\nconst quickjsWasm = quickjsWasmImport as unknown as CodemodeWasmModule;\n\n/** Default VM memory limit of {@link CodemodeSandboxDurableObject}. Isolates have 128 MB in total. */\nexport const DEFAULT_REMOTE_MEMORY_LIMIT_BYTES = 32 * 1024 * 1024;\n\n/** `quickjs-wasi/quickjs.wasm`, compiled by Wrangler. */\nexport const cloudflareQuickJSWasm: CodemodeWasmModule = quickjsWasm;\n\n/** `InlineCodemodeSandbox` with the QuickJS module Wrangler bundled. */\nexport function createInlineCodemodeSandbox(options: Omit<InlineCodemodeSandboxOptions, \"wasm\"> = {}) {\n\treturn new InlineCodemodeSandbox({ ...options, wasm: quickjsWasm });\n}\n\n/**\n * The remote end of `RemoteCodemodeSandbox`: a Durable Object whose one RPC method runs a script's\n * VM and calls back into the client for every tool call.\n *\n * Deploy it as its own Worker and bind it from the Worker that runs your agent with `script_name`.\n * A separate Worker is what keeps scripts off the agent's thread: a class in the same Worker can\n * share an isolate, and therefore a thread, with the objects that call it.\n *\n * The object keeps no state between executions and never touches storage. Executions that reach\n * the same instance share its thread: they interleave while they wait on tools, and a script that\n * computes delays the others. Name instances per session (`getByName(sessionId)`) to keep one\n * session's scripts together, or per execution for full independence.\n *\n * Limits apply to every request, whatever the client asks for. Override the fields in a subclass:\n *\n * export class CodemodeSandbox extends CodemodeSandboxDurableObject {\n * protected maxInterruptBudget = 200_000;\n * }\n */\nexport class CodemodeSandboxDurableObject<Env = unknown> extends DurableObject<Env> {\n\t/** Interrupt polls per execution; the client may only ask for less. See `InlineCodemodeSandboxOptions`. */\n\tprotected maxInterruptBudget: number = DEFAULT_INTERRUPT_BUDGET;\n\t/** VM memory per execution; the client may only ask for less. */\n\tprotected maxMemoryLimitBytes: number = DEFAULT_REMOTE_MEMORY_LIMIT_BYTES;\n\n\t/** Called by `RemoteCodemodeSandbox` over RPC. Resolves when the script has settled or was stopped. */\n\texecute(request: CodemodeRemoteRequest, exchange: CodemodeRemoteExchange): Promise<void> {\n\t\treturn serveCodemodeRemote(request, exchange, {\n\t\t\twasm: quickjsWasm,\n\t\t\tmaxInterruptBudget: this.maxInterruptBudget,\n\t\t\tmaxMemoryLimitBytes: this.maxMemoryLimitBytes,\n\t\t});\n\t}\n}\n"]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { toCodemodeIdentifier } from "./identifier.ts";
|
|
2
|
+
export { toCodemodeIdentifier };
|
|
3
|
+
import type { CodemodeJsonSchema, CodemodeTool } from "./types.ts";
|
|
4
|
+
/** Largest rendered input type, in characters, before it becomes `unknown`. */
|
|
5
|
+
export declare const DEFAULT_INPUT_SCHEMA_MAX_CHARS = 16000;
|
|
6
|
+
/**
|
|
7
|
+
* TypeScript types for MCP results, from the MCP `CallToolResult` schema, so `CallToolResult<T>`
|
|
8
|
+
* declarations can refer to them.
|
|
9
|
+
*/
|
|
10
|
+
export declare const MCP_TYPESCRIPT_PREAMBLE = "type Role = \"user\" | \"assistant\";\ntype MetaObject = Record<string, unknown>;\ntype Annotations = {\n audience?: Role[];\n priority?: number;\n lastModified?: string;\n};\ntype Icon = {\n src: string;\n mimeType?: string;\n sizes?: string[];\n theme?: \"light\" | \"dark\";\n};\ntype TextResourceContents = {\n uri: string;\n mimeType?: string;\n _meta?: MetaObject;\n text: string;\n};\ntype BlobResourceContents = {\n uri: string;\n mimeType?: string;\n _meta?: MetaObject;\n blob: string;\n};\ntype TextContent = {\n type: \"text\";\n text: string;\n annotations?: Annotations;\n _meta?: MetaObject;\n};\ntype ImageContent = {\n type: \"image\";\n data: string;\n mimeType: string;\n annotations?: Annotations;\n _meta?: MetaObject;\n};\ntype AudioContent = {\n type: \"audio\";\n data: string;\n mimeType: string;\n annotations?: Annotations;\n _meta?: MetaObject;\n};\ntype ResourceLink = {\n icons?: Icon[];\n name: string;\n title?: string;\n uri: string;\n description?: string;\n mimeType?: string;\n annotations?: Annotations;\n size?: number;\n _meta?: MetaObject;\n type: \"resource_link\";\n};\ntype EmbeddedResource = {\n type: \"resource\";\n resource: TextResourceContents | BlobResourceContents;\n annotations?: Annotations;\n _meta?: MetaObject;\n};\ntype ContentBlock =\n | TextContent\n | ImageContent\n | AudioContent\n | ResourceLink\n | EmbeddedResource;\ntype CallToolResult<TStructured = { [key: string]: unknown }> = {\n _meta?: MetaObject;\n content: ContentBlock[];\n isError?: boolean;\n structuredContent?: TStructured;\n [key: string]: unknown;\n};";
|
|
11
|
+
export interface RenderDeclarationsOptions {
|
|
12
|
+
tools?: readonly CodemodeTool[];
|
|
13
|
+
globals?: readonly CodemodeTool[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Render TypeScript declarations for the script-visible API. Tools become members of
|
|
17
|
+
* `declare const tools`, globals become `declare function` statements, and `ns.member` globals
|
|
18
|
+
* members of `declare const ns`. Descriptions become doc comments; schemas become types.
|
|
19
|
+
*/
|
|
20
|
+
export declare function renderDeclarations(options: RenderDeclarationsOptions): string;
|
|
21
|
+
/**
|
|
22
|
+
* One tool as a member of the `tools` object: `name(args: T): Promise<R>;` with the
|
|
23
|
+
* name as the identifier scripts use. Input types longer than `inputMaxChars` render as `unknown`.
|
|
24
|
+
* Tools whose output schema is an MCP `CallToolResult` render as `Promise<CallToolResult<T>>`,
|
|
25
|
+
* which needs {@link MCP_TYPESCRIPT_PREAMBLE}.
|
|
26
|
+
*/
|
|
27
|
+
export declare function renderToolSignature(tool: Pick<CodemodeTool, "name" | "inputSchema" | "outputSchema">, options?: {
|
|
28
|
+
inputMaxChars?: number;
|
|
29
|
+
}): string;
|
|
30
|
+
/**
|
|
31
|
+
* A tool's sample: the description followed by the tool's declaration. Used for tool
|
|
32
|
+
* listings and `ALL_TOOLS` entries.
|
|
33
|
+
*/
|
|
34
|
+
export declare function renderToolSample(tool: Pick<CodemodeTool, "name" | "description" | "inputSchema" | "outputSchema">, options?: {
|
|
35
|
+
inputMaxChars?: number;
|
|
36
|
+
}): string;
|
|
37
|
+
/**
|
|
38
|
+
* The `structuredContent` schema of an MCP `CallToolResult` output schema (detected by a
|
|
39
|
+
* `content` array of objects, boolean `isError`, and object `_meta`), `true` when it declares none, or
|
|
40
|
+
* `undefined` when the schema is not a `CallToolResult`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function mcpStructuredContentSchema(schema: CodemodeJsonSchema | undefined): CodemodeJsonSchema | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* The type a tool call resolves to: `CallToolResult<T>` for MCP output schemas (needs
|
|
45
|
+
* {@link MCP_TYPESCRIPT_PREAMBLE}), the schema's type otherwise, and `unknown` without a schema.
|
|
46
|
+
*/
|
|
47
|
+
export declare function renderToolOutputType(schema: CodemodeJsonSchema | undefined): string;
|
|
48
|
+
/**
|
|
49
|
+
* Convert a JSON Schema to a TypeScript type expression: objects on one line (`{ a: string; b?: number; }`) with properties sorted by name,
|
|
50
|
+
* or one property per line with `//` comments when a property has a description; `Array<T>` for
|
|
51
|
+
* arrays. Local references (`#/$defs/...`, `#/definitions/...`) resolve against `schema`;
|
|
52
|
+
* recursive and remote references render as `unknown`. A result longer than `maxChars` renders as
|
|
53
|
+
* `unknown`.
|
|
54
|
+
*/
|
|
55
|
+
export declare function schemaToType(schema: CodemodeJsonSchema, options?: {
|
|
56
|
+
maxChars?: number;
|
|
57
|
+
}): string;
|
|
58
|
+
//# sourceMappingURL=declarations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"declarations.d.ts","sourceRoot":"","sources":["../src/declarations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,OAAO,EAAE,oBAAoB,EAAE,CAAC;AAEhC,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAInE,+EAA+E;AAC/E,eAAO,MAAM,8BAA8B,QAAS,CAAC;AAIrD;;;GAGG;AACH,eAAO,MAAM,uBAAuB,ymDA2EjC,CAAC;AAEJ,MAAM,WAAW,yBAAyB;IACzC,KAAK,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAChC,OAAO,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;CAClC;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,MAAM,CAyB7E;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAClC,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa,GAAG,cAAc,CAAC,EACjE,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GACtC,MAAM,CAMR;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC/B,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa,GAAG,aAAa,GAAG,cAAc,CAAC,EACjF,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GACtC,MAAM,CAGR;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,kBAAkB,GAAG,SAAS,GAAG,kBAAkB,GAAG,SAAS,CASjH;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,kBAAkB,GAAG,SAAS,GAAG,MAAM,CAOnF;AAiCD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,kBAAkB,EAAE,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,CAGpG"}
|