@codeworksh/aikit 0.2.0 → 0.3.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/README.md +117 -260
- package/agent/codemode/drivers/drivers.d.mts +7 -0
- package/agent/codemode/drivers/drivers.mjs +122 -0
- package/agent/codemode/drivers/drivers.mjs.map +1 -0
- package/codemode-z3ZIw-5q.d.mts +8234 -0
- package/index.d.mts +2 -8865
- package/index.mjs +1814 -1635
- package/index.mjs.map +1 -1
- package/package.json +11 -7
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @codeworksh/aikit
|
|
2
2
|
|
|
3
|
-
Toolkit for
|
|
3
|
+
Toolkit for building AI agents and streaming LLM workflows.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,341 +8,198 @@ Toolkit for Building AI Agents and Workflows.
|
|
|
8
8
|
npm install @codeworksh/aikit
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Node `>=24.14.1` is required.
|
|
12
|
+
|
|
13
|
+
## What You Get
|
|
14
|
+
|
|
15
|
+
- `llm(...)` to resolve a model from the built-in registry
|
|
16
|
+
- `stream(...)` and `stream.complete(...)` for provider-normalized LLM streaming
|
|
17
|
+
- `Agent.create(...)` for a stateful agent instance
|
|
18
|
+
- `agent.loop(...)` and `agent.loopContinue(...)` for lower-level turn orchestration
|
|
19
|
+
- `Message` helpers for typed messages and generated `messageId` values
|
|
20
|
+
|
|
21
|
+
## Root API
|
|
12
22
|
|
|
13
23
|
```ts
|
|
14
|
-
import { Message, llm, stream } from "@codeworksh/aikit";
|
|
24
|
+
import { Agent, Message, agent, llm, stream } from "@codeworksh/aikit";
|
|
25
|
+
```
|
|
15
26
|
|
|
16
|
-
|
|
17
|
-
if (!model) throw new Error("Model not found");
|
|
27
|
+
## Quick Start
|
|
18
28
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
messages: [
|
|
22
|
-
{
|
|
23
|
-
role: "user",
|
|
24
|
-
time: { created: Date.now() },
|
|
25
|
-
parts: [{ type: "text", text: "Say hello in one line." }],
|
|
26
|
-
},
|
|
27
|
-
],
|
|
28
|
-
};
|
|
29
|
+
```ts
|
|
30
|
+
import { Agent } from "@codeworksh/aikit";
|
|
29
31
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
+
const agent = await Agent.create({
|
|
33
|
+
provider: "anthropic",
|
|
34
|
+
model: "claude-haiku-4-5-20251001",
|
|
35
|
+
getApiKey: async () => process.env.ANTHROPIC_API_KEY,
|
|
32
36
|
});
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
if (event.type === "text.delta") {
|
|
36
|
-
process.stdout.write(event.delta);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
38
|
+
agent.setSystemPrompt("Be concise.");
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
console.log(message.parts);
|
|
42
|
-
```
|
|
40
|
+
await agent.prompt([{ type: "text", text: "Reply with exactly: hello" }]);
|
|
43
41
|
|
|
44
|
-
|
|
42
|
+
const lastMessage = agent.state.messages.at(-1);
|
|
43
|
+
console.log(lastMessage?.messageId);
|
|
44
|
+
console.log(lastMessage?.role);
|
|
45
|
+
```
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
## Stream Events
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
Use `stream(...)` when you want token and part-level events.
|
|
49
50
|
|
|
50
51
|
```ts
|
|
51
|
-
import { llm, stream } from "@codeworksh/aikit";
|
|
52
|
+
import { Message, llm, stream } from "@codeworksh/aikit";
|
|
52
53
|
|
|
53
54
|
const model = await llm("anthropic", "claude-haiku-4-5-20251001");
|
|
54
|
-
if (!model) throw new Error("
|
|
55
|
+
if (!model) throw new Error("Model not found");
|
|
55
56
|
|
|
56
|
-
const
|
|
57
|
+
const s = stream(
|
|
57
58
|
model,
|
|
58
59
|
{
|
|
59
60
|
messages: [
|
|
60
|
-
{
|
|
61
|
+
Message.createUserMessage({
|
|
61
62
|
role: "user",
|
|
62
63
|
time: { created: Date.now() },
|
|
63
|
-
parts: [{ type: "text", text: "
|
|
64
|
-
},
|
|
64
|
+
parts: [{ type: "text", text: "Count from 1 to 3" }],
|
|
65
|
+
}),
|
|
65
66
|
],
|
|
66
67
|
},
|
|
67
68
|
{ apiKey: process.env.ANTHROPIC_API_KEY },
|
|
68
69
|
);
|
|
70
|
+
|
|
71
|
+
for await (const event of s) {
|
|
72
|
+
if (event.type === "text.delta") {
|
|
73
|
+
process.stdout.write(event.delta);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const finalMessage = await s.result();
|
|
78
|
+
console.log(finalMessage.messageId);
|
|
69
79
|
```
|
|
70
80
|
|
|
71
|
-
|
|
81
|
+
## Agent Instance
|
|
72
82
|
|
|
73
|
-
`
|
|
83
|
+
`Agent.create(...)` is the simplest way to work with a stateful agent.
|
|
74
84
|
|
|
75
85
|
```ts
|
|
76
|
-
import {
|
|
77
|
-
|
|
86
|
+
import { Agent } from "@codeworksh/aikit";
|
|
87
|
+
|
|
88
|
+
const agent = await Agent.create({
|
|
89
|
+
provider: "anthropic",
|
|
90
|
+
model: "claude-haiku-4-5-20251001",
|
|
91
|
+
getApiKey: async () => process.env.ANTHROPIC_API_KEY,
|
|
92
|
+
});
|
|
78
93
|
|
|
79
|
-
|
|
80
|
-
|
|
94
|
+
agent.setSystemPrompt("Be concise.");
|
|
95
|
+
|
|
96
|
+
agent.subscribe((event) => {
|
|
97
|
+
if (event.type === "message.end") {
|
|
98
|
+
console.log(event.message.role, event.message.messageId);
|
|
99
|
+
}
|
|
81
100
|
});
|
|
82
101
|
|
|
102
|
+
await agent.prompt([{ type: "text", text: "Say hello in one line." }]);
|
|
103
|
+
|
|
104
|
+
console.log(agent.state.messages);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## With Tools
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { Type } from "@sinclair/typebox";
|
|
111
|
+
import { Agent } from "@codeworksh/aikit";
|
|
112
|
+
|
|
83
113
|
const calculatorTool = Agent.defineTool({
|
|
84
114
|
name: "calculator",
|
|
85
115
|
label: "Calculator",
|
|
86
|
-
description: "Evaluate
|
|
87
|
-
parameters:
|
|
88
|
-
|
|
116
|
+
description: "Evaluate arithmetic expressions",
|
|
117
|
+
parameters: Type.Object({
|
|
118
|
+
expression: Type.String(),
|
|
119
|
+
}),
|
|
120
|
+
async execute(_callID, params) {
|
|
89
121
|
return {
|
|
90
122
|
status: "completed",
|
|
91
123
|
result: {
|
|
92
|
-
content: [{ type: "text", text:
|
|
124
|
+
content: [{ type: "text", text: `result for ${params.expression}` }],
|
|
93
125
|
isError: false,
|
|
94
126
|
},
|
|
95
127
|
};
|
|
96
128
|
},
|
|
97
129
|
});
|
|
98
130
|
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
time: { created: Date.now() },
|
|
105
|
-
parts: [{ type: "text", text: "Use the calculator tool for 2 + 2." }],
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const run = agent.run(
|
|
109
|
-
{
|
|
110
|
-
model,
|
|
111
|
-
convertToLlm: async (messages) => messages,
|
|
112
|
-
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
113
|
-
beforeToolExecution: async ({ toolCall }) => {
|
|
114
|
-
console.log("validated args", toolCall.args);
|
|
115
|
-
},
|
|
116
|
-
afterToolExecution: async ({ result }) => result,
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
systemPrompt: "Use tools when helpful.",
|
|
120
|
-
messages: [],
|
|
131
|
+
const instance = await Agent.create({
|
|
132
|
+
provider: "anthropic",
|
|
133
|
+
model: "claude-haiku-4-5-20251001",
|
|
134
|
+
getApiKey: async () => process.env.ANTHROPIC_API_KEY,
|
|
135
|
+
initialState: {
|
|
121
136
|
tools: [calculatorTool],
|
|
122
137
|
},
|
|
123
|
-
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
for await (const event of run) {
|
|
127
|
-
if (event.type === "message.part.update" && event.source === "tool") {
|
|
128
|
-
console.log(event.part);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const messages = await run.result();
|
|
133
|
-
console.log(messages.at(-1));
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### Agent Vs LLM
|
|
137
|
-
|
|
138
|
-
Use `stream(...)` when you want raw provider events and manual control over tool handling.
|
|
139
|
-
|
|
140
|
-
Use `agent.run(...)` when you want a higher-level runtime that:
|
|
141
|
-
|
|
142
|
-
- streams assistant message lifecycle events
|
|
143
|
-
- validates tool arguments
|
|
144
|
-
- executes tools sequentially or in parallel
|
|
145
|
-
- supports `beforeToolExecution` and `afterToolExecution` callbacks
|
|
146
|
-
|
|
147
|
-
## Event Flow
|
|
148
|
-
|
|
149
|
-
LLM streams emit low-level provider-normalized events:
|
|
138
|
+
});
|
|
150
139
|
|
|
151
|
-
|
|
152
|
-
start
|
|
153
|
-
text.start / thinking.start / toolcall.start
|
|
154
|
-
text.delta / thinking.delta / toolcall.delta
|
|
155
|
-
text.end / thinking.end / toolcall.end
|
|
156
|
-
done | error
|
|
140
|
+
await instance.prompt([{ type: "text", text: "Use the calculator tool for 25 * 18." }]);
|
|
157
141
|
```
|
|
158
142
|
|
|
159
|
-
##
|
|
160
|
-
|
|
161
|
-
Agent loops emit higher-level lifecycle events around messages, parts, turns, and tools:
|
|
162
|
-
|
|
163
|
-
```text
|
|
164
|
-
agent.start
|
|
165
|
-
turn.start
|
|
166
|
-
message.start
|
|
167
|
-
message.part.start
|
|
168
|
-
message.part.update
|
|
169
|
-
message.part.end
|
|
170
|
-
message.update
|
|
171
|
-
message.end
|
|
172
|
-
tool.execution.start
|
|
173
|
-
tool.execution.update
|
|
174
|
-
tool.execution.end
|
|
175
|
-
turn.end
|
|
176
|
-
agent.end
|
|
177
|
-
```
|
|
143
|
+
## Lower-Level Loop
|
|
178
144
|
|
|
179
|
-
|
|
145
|
+
Use `agent.loop(...)` when you want loop execution without creating a stateful instance.
|
|
180
146
|
|
|
181
147
|
```ts
|
|
182
|
-
import {
|
|
183
|
-
import { Agent, Message, agent, llm } from "@codeworksh/aikit";
|
|
184
|
-
|
|
185
|
-
const searchParams = Type.Object({
|
|
186
|
-
query: Type.String(),
|
|
187
|
-
limit: Type.Optional(Type.Number()),
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
const searchTool: Agent.AgentTool<typeof searchParams, { progress: number }, { hits: number }> = {
|
|
191
|
-
name: "search",
|
|
192
|
-
label: "Search",
|
|
193
|
-
description: "Search indexed documents",
|
|
194
|
-
parameters: searchParams,
|
|
195
|
-
async execute(callID, params, signal, onUpdate) {
|
|
196
|
-
await onUpdate?.({
|
|
197
|
-
status: "running",
|
|
198
|
-
partial: {
|
|
199
|
-
content: [{ type: "text", text: `Searching for ${params.query}` }],
|
|
200
|
-
details: { progress: 50 },
|
|
201
|
-
},
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
return {
|
|
205
|
-
status: "completed",
|
|
206
|
-
result: {
|
|
207
|
-
content: [{ type: "text", text: `Found results for ${params.query}` }],
|
|
208
|
-
details: { hits: 3 },
|
|
209
|
-
isError: false,
|
|
210
|
-
},
|
|
211
|
-
};
|
|
212
|
-
},
|
|
213
|
-
};
|
|
148
|
+
import { Message, agent, llm } from "@codeworksh/aikit";
|
|
214
149
|
|
|
215
150
|
const model = await llm("anthropic", "claude-haiku-4-5-20251001");
|
|
216
|
-
if (!model) throw new Error("
|
|
217
|
-
|
|
218
|
-
const prompt: Message.UserMessage = {
|
|
219
|
-
role: "user",
|
|
220
|
-
time: { created: Date.now() },
|
|
221
|
-
parts: [{ type: "text", text: "Search for agent loop documentation." }],
|
|
222
|
-
};
|
|
151
|
+
if (!model) throw new Error("Model not found");
|
|
223
152
|
|
|
224
|
-
const run = agent.
|
|
153
|
+
const run = agent.loop(
|
|
225
154
|
{
|
|
226
155
|
model,
|
|
227
156
|
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
228
157
|
convertToLlm: async (messages) => messages,
|
|
229
|
-
toolExecution: "parallel",
|
|
230
158
|
},
|
|
231
159
|
{
|
|
232
|
-
systemPrompt: "
|
|
160
|
+
systemPrompt: "Be concise.",
|
|
233
161
|
messages: [],
|
|
234
|
-
tools: [
|
|
162
|
+
tools: [],
|
|
235
163
|
},
|
|
236
|
-
[
|
|
164
|
+
[
|
|
165
|
+
Message.createUserMessage({
|
|
166
|
+
role: "user",
|
|
167
|
+
time: { created: Date.now() },
|
|
168
|
+
parts: [{ type: "text", text: "Reply with exactly: ok" }],
|
|
169
|
+
}),
|
|
170
|
+
],
|
|
237
171
|
);
|
|
238
172
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
console.log("tool started", event.name, event.args ?? event.rawArgs);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (event.type === "tool.execution.update") {
|
|
245
|
-
console.log("tool update", event.partial?.details);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (event.type === "tool.execution.end") {
|
|
249
|
-
console.log("tool finished", event.result.details);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
173
|
+
const messages = await run.result();
|
|
174
|
+
console.log(messages.at(-1)?.role);
|
|
252
175
|
```
|
|
253
176
|
|
|
254
|
-
|
|
177
|
+
## Message IDs
|
|
255
178
|
|
|
256
|
-
|
|
179
|
+
Every user and assistant message has a `messageId`.
|
|
257
180
|
|
|
258
|
-
|
|
259
|
-
- `sequential`: prepares and executes each tool call one by one. Use this when tools depend on shared state, rate limits, or side effects that should not overlap.
|
|
181
|
+
Use the helpers when constructing messages yourself:
|
|
260
182
|
|
|
261
183
|
```ts
|
|
262
|
-
|
|
263
|
-
{
|
|
264
|
-
model,
|
|
265
|
-
convertToLlm: async (messages) => messages,
|
|
266
|
-
toolExecution: "sequential",
|
|
267
|
-
},
|
|
268
|
-
context,
|
|
269
|
-
[prompt],
|
|
270
|
-
);
|
|
271
|
-
```
|
|
184
|
+
import { Message } from "@codeworksh/aikit";
|
|
272
185
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
186
|
+
const userMessage = Message.createUserMessage({
|
|
187
|
+
role: "user",
|
|
188
|
+
time: { created: Date.now() },
|
|
189
|
+
parts: [{ type: "text", text: "hello" }],
|
|
190
|
+
});
|
|
278
191
|
|
|
279
|
-
|
|
280
|
-
const run = agent.run(
|
|
281
|
-
{
|
|
282
|
-
model,
|
|
283
|
-
convertToLlm: async (messages) => messages,
|
|
284
|
-
beforeToolExecution: async ({ toolCall }) => {
|
|
285
|
-
if (toolCall.name === "search" && toolCall.args?.query === "secret") {
|
|
286
|
-
return {
|
|
287
|
-
block: true,
|
|
288
|
-
reason: "This query is not allowed",
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
},
|
|
292
|
-
},
|
|
293
|
-
context,
|
|
294
|
-
[prompt],
|
|
295
|
-
);
|
|
192
|
+
console.log(userMessage.messageId);
|
|
296
193
|
```
|
|
297
194
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
### afterToolExecution
|
|
301
|
-
|
|
302
|
-
`afterToolExecution` runs after the tool finishes and before the terminal tool result is emitted.
|
|
303
|
-
|
|
304
|
-
Use it to normalize output, attach metadata, or override the final tool result.
|
|
305
|
-
|
|
306
|
-
```ts
|
|
307
|
-
const run = agent.run(
|
|
308
|
-
{
|
|
309
|
-
model,
|
|
310
|
-
convertToLlm: async (messages) => messages,
|
|
311
|
-
afterToolExecution: async ({ result }) => {
|
|
312
|
-
if (result.status === "completed") {
|
|
313
|
-
return {
|
|
314
|
-
status: "completed",
|
|
315
|
-
result: {
|
|
316
|
-
...result.result,
|
|
317
|
-
details: {
|
|
318
|
-
...result.result.details,
|
|
319
|
-
source: "post-processed",
|
|
320
|
-
},
|
|
321
|
-
},
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
},
|
|
325
|
-
},
|
|
326
|
-
context,
|
|
327
|
-
[prompt],
|
|
328
|
-
);
|
|
329
|
-
```
|
|
195
|
+
This is useful for persistence layers like SQLite, event reconciliation, and updating stored messages by identity instead of array position.
|
|
330
196
|
|
|
331
|
-
##
|
|
197
|
+
## Lower-Level APIs
|
|
332
198
|
|
|
333
|
-
|
|
199
|
+
- `stream(...)`: raw provider-normalized stream
|
|
200
|
+
- `stream.complete(...)`: final assistant message without manual iteration
|
|
201
|
+
- `Agent.create(...)`: stateful agent instance
|
|
202
|
+
- `agent.loop(...)`: lower-level agent loop
|
|
203
|
+
- `agent.loopContinue(...)`: continue from an existing context
|
|
334
204
|
|
|
335
|
-
|
|
336
|
-
import {
|
|
337
|
-
agent,
|
|
338
|
-
llm,
|
|
339
|
-
stream,
|
|
340
|
-
Agent,
|
|
341
|
-
Event,
|
|
342
|
-
Message,
|
|
343
|
-
Model,
|
|
344
|
-
Stream,
|
|
345
|
-
validateToolArguments,
|
|
346
|
-
validateToolCall,
|
|
347
|
-
} from "@codeworksh/aikit";
|
|
348
|
-
```
|
|
205
|
+
API docs can be added later. For now, the tests under `packages/aikit/test/` are the best reference for concrete usage.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { t as CodeMode } from "../../../codemode-z3ZIw-5q.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/agent/codemode/drivers/quickjs-wasi-driver.d.ts
|
|
4
|
+
declare function createQuickJSWasiDriver(): CodeMode.Driver;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { createQuickJSWasiDriver };
|
|
7
|
+
//# sourceMappingURL=drivers.d.mts.map
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { QuickJS } from "quickjs-wasi";
|
|
2
|
+
//#region src/agent/codemode/drivers/quickjs-wasi-driver.ts
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
4
|
+
const DEFAULT_MEMORY_LIMIT_MB = 128;
|
|
5
|
+
function withMessageInStack(message, stack) {
|
|
6
|
+
if (!stack) return void 0;
|
|
7
|
+
return stack.includes(message) ? stack : `${message}\n${stack}`;
|
|
8
|
+
}
|
|
9
|
+
function parseErrorLine(stack) {
|
|
10
|
+
if (typeof stack !== "string") return void 0;
|
|
11
|
+
const lineMatch = stack.match(/:(\d+)(?::\d+)?\)?$/m);
|
|
12
|
+
return lineMatch ? Number(lineMatch[1]) : void 0;
|
|
13
|
+
}
|
|
14
|
+
function normalizeExecutionError(error) {
|
|
15
|
+
if (error instanceof Error) return {
|
|
16
|
+
name: error.name,
|
|
17
|
+
message: error.message,
|
|
18
|
+
stack: withMessageInStack(error.message, error.stack),
|
|
19
|
+
line: parseErrorLine(error.stack)
|
|
20
|
+
};
|
|
21
|
+
if (typeof error === "object" && error !== null) {
|
|
22
|
+
const candidate = error;
|
|
23
|
+
const message = typeof candidate.message === "string" ? candidate.message : JSON.stringify(candidate) || "Unknown Execution Error";
|
|
24
|
+
return {
|
|
25
|
+
name: typeof candidate.name === "string" ? candidate.name : void 0,
|
|
26
|
+
message,
|
|
27
|
+
stack: typeof candidate.stack === "string" ? withMessageInStack(message, candidate.stack) : void 0,
|
|
28
|
+
line: typeof candidate.line === "number" ? candidate.line : parseErrorLine(candidate.stack)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
name: "Error",
|
|
33
|
+
message: String(error)
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function toMegabytes(value) {
|
|
37
|
+
return (value ?? DEFAULT_MEMORY_LIMIT_MB) * 1024 * 1024;
|
|
38
|
+
}
|
|
39
|
+
function createConsole(vm, logs) {
|
|
40
|
+
const consoleObject = vm.newObject();
|
|
41
|
+
for (const [name, prefix] of [
|
|
42
|
+
["log", ""],
|
|
43
|
+
["info", "INFO: "],
|
|
44
|
+
["warn", "WARN: "],
|
|
45
|
+
["error", "ERROR: "]
|
|
46
|
+
]) {
|
|
47
|
+
const fn = vm.newFunction(name, (...args) => {
|
|
48
|
+
const message = args.map((arg) => String(vm.dump(arg))).join(" ");
|
|
49
|
+
logs.push(`${prefix}${message}`);
|
|
50
|
+
return vm.undefined;
|
|
51
|
+
});
|
|
52
|
+
consoleObject.setProp(name, fn);
|
|
53
|
+
}
|
|
54
|
+
vm.setProp(vm.global, "console", consoleObject);
|
|
55
|
+
}
|
|
56
|
+
function registerBindings(vm, bindings, signal) {
|
|
57
|
+
let callCounter = 0;
|
|
58
|
+
for (const [name, binding] of Object.entries(bindings)) {
|
|
59
|
+
const fn = vm.newFunction(name, (...args) => {
|
|
60
|
+
const input = args[0] ? vm.dump(args[0]) : void 0;
|
|
61
|
+
const result = Promise.resolve(binding.execute(`${name}:${++callCounter}`, input, signal));
|
|
62
|
+
return vm.hostToHandle(result);
|
|
63
|
+
});
|
|
64
|
+
vm.setProp(vm.global, name, fn);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function createQuickJSWasiDriver() {
|
|
68
|
+
return { async createContext(config) {
|
|
69
|
+
const timeout = config.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
70
|
+
const deadline = Date.now() + timeout;
|
|
71
|
+
const logs = [];
|
|
72
|
+
const vm = await QuickJS.create({
|
|
73
|
+
memoryLimit: toMegabytes(config.memoryLimit),
|
|
74
|
+
interruptHandler: () => Boolean(config.signal?.aborted) || Date.now() > deadline
|
|
75
|
+
});
|
|
76
|
+
createConsole(vm, logs);
|
|
77
|
+
registerBindings(vm, config.bindings, config.signal);
|
|
78
|
+
return {
|
|
79
|
+
async execute(code) {
|
|
80
|
+
let resultHandle;
|
|
81
|
+
try {
|
|
82
|
+
resultHandle = vm.evalCode(code, "script.js");
|
|
83
|
+
vm.executePendingJobs();
|
|
84
|
+
const settled = await vm.resolvePromise(resultHandle);
|
|
85
|
+
if ("error" in settled) try {
|
|
86
|
+
return {
|
|
87
|
+
success: false,
|
|
88
|
+
error: normalizeExecutionError(vm.dump(settled.error)),
|
|
89
|
+
logs: [...logs]
|
|
90
|
+
};
|
|
91
|
+
} finally {
|
|
92
|
+
settled.error.dispose();
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
return {
|
|
96
|
+
success: true,
|
|
97
|
+
value: vm.dump(settled.value),
|
|
98
|
+
logs: [...logs]
|
|
99
|
+
};
|
|
100
|
+
} finally {
|
|
101
|
+
settled.value.dispose();
|
|
102
|
+
}
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return {
|
|
105
|
+
success: false,
|
|
106
|
+
error: normalizeExecutionError(error),
|
|
107
|
+
logs: [...logs]
|
|
108
|
+
};
|
|
109
|
+
} finally {
|
|
110
|
+
resultHandle?.dispose();
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
async dispose() {
|
|
114
|
+
vm.dispose();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
} };
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
120
|
+
export { createQuickJSWasiDriver };
|
|
121
|
+
|
|
122
|
+
//# sourceMappingURL=drivers.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drivers.mjs","names":[],"sources":["../../../../../src/agent/codemode/drivers/quickjs-wasi-driver.ts"],"sourcesContent":["import { QuickJS } from \"quickjs-wasi\";\nimport type { CodeMode } from \"../codemode\";\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst DEFAULT_MEMORY_LIMIT_MB = 128;\n\nfunction withMessageInStack(message: string, stack: string | undefined): string | undefined {\n\tif (!stack) return undefined;\n\treturn stack.includes(message) ? stack : `${message}\\n${stack}`;\n}\n\nfunction parseErrorLine(stack: unknown): number | undefined {\n\tif (typeof stack !== \"string\") return undefined;\n\tconst lineMatch = stack.match(/:(\\d+)(?::\\d+)?\\)?$/m);\n\treturn lineMatch ? Number(lineMatch[1]) : undefined;\n}\n\nfunction normalizeExecutionError(error: unknown): CodeMode.NormalizedError {\n\tif (error instanceof Error) {\n\t\treturn {\n\t\t\tname: error.name,\n\t\t\tmessage: error.message,\n\t\t\tstack: withMessageInStack(error.message, error.stack),\n\t\t\tline: parseErrorLine(error.stack),\n\t\t};\n\t}\n\n\tif (typeof error === \"object\" && error !== null) {\n\t\tconst candidate = error as Record<string, unknown>;\n\t\tconst message =\n\t\t\ttypeof candidate.message === \"string\"\n\t\t\t\t? candidate.message\n\t\t\t\t: JSON.stringify(candidate) || \"Unknown Execution Error\";\n\t\treturn {\n\t\t\tname: typeof candidate.name === \"string\" ? candidate.name : undefined,\n\t\t\tmessage,\n\t\t\tstack: typeof candidate.stack === \"string\" ? withMessageInStack(message, candidate.stack) : undefined,\n\t\t\tline: typeof candidate.line === \"number\" ? candidate.line : parseErrorLine(candidate.stack),\n\t\t};\n\t}\n\n\treturn {\n\t\tname: \"Error\",\n\t\tmessage: String(error),\n\t};\n}\n\nfunction toMegabytes(value: number | undefined): number {\n\treturn (value ?? DEFAULT_MEMORY_LIMIT_MB) * 1024 * 1024;\n}\n\nfunction createConsole(vm: QuickJS, logs: string[]) {\n\tconst consoleObject = vm.newObject();\n\tconst methods = [\n\t\t[\"log\", \"\"],\n\t\t[\"info\", \"INFO: \"],\n\t\t[\"warn\", \"WARN: \"],\n\t\t[\"error\", \"ERROR: \"],\n\t] as const;\n\n\tfor (const [name, prefix] of methods) {\n\t\tconst fn = vm.newFunction(name, (...args) => {\n\t\t\tconst message = args.map((arg) => String(vm.dump(arg))).join(\" \");\n\t\t\tlogs.push(`${prefix}${message}`);\n\t\t\treturn vm.undefined;\n\t\t});\n\t\tconsoleObject.setProp(name, fn);\n\t}\n\n\tvm.setProp(vm.global, \"console\", consoleObject);\n}\n\nfunction registerBindings(vm: QuickJS, bindings: Record<string, CodeMode.ToolBinding>, signal?: AbortSignal) {\n\tlet callCounter = 0;\n\n\tfor (const [name, binding] of Object.entries(bindings)) {\n\t\tconst fn = vm.newFunction(name, (...args) => {\n\t\t\tconst input = args[0] ? vm.dump(args[0]) : undefined;\n\t\t\tconst result = Promise.resolve(binding.execute(`${name}:${++callCounter}`, input, signal));\n\t\t\treturn vm.hostToHandle(result);\n\t\t});\n\t\tvm.setProp(vm.global, name, fn);\n\t}\n}\n\nexport function createQuickJSWasiDriver(): CodeMode.Driver {\n\treturn {\n\t\tasync createContext(config: CodeMode.DriverContextConfig): Promise<CodeMode.Context> {\n\t\t\tconst timeout = config.timeout ?? DEFAULT_TIMEOUT_MS;\n\t\t\tconst deadline = Date.now() + timeout;\n\t\t\tconst logs: string[] = [];\n\t\t\t// create VM for execution sandbox\n\t\t\tconst vm = await QuickJS.create({\n\t\t\t\tmemoryLimit: toMegabytes(config.memoryLimit),\n\t\t\t\tinterruptHandler: () => Boolean(config.signal?.aborted) || Date.now() > deadline,\n\t\t\t});\n\n\t\t\t// create console log bindings\n\t\t\tcreateConsole(vm, logs);\n\t\t\t// register tool fn bindings\n\t\t\tregisterBindings(vm, config.bindings, config.signal);\n\n\t\t\treturn {\n\t\t\t\tasync execute<T = unknown>(code: string) {\n\t\t\t\t\tlet resultHandle: ReturnType<typeof vm.evalCode> | undefined;\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tresultHandle = vm.evalCode(code, \"script.js\");\n\t\t\t\t\t\tvm.executePendingJobs();\n\n\t\t\t\t\t\tconst settled = await vm.resolvePromise(resultHandle);\n\t\t\t\t\t\tif (\"error\" in settled) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\t\t\t\terror: normalizeExecutionError(vm.dump(settled.error)),\n\t\t\t\t\t\t\t\t\tlogs: [...logs],\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t} finally {\n\t\t\t\t\t\t\t\tsettled.error.dispose();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\t\t\tvalue: vm.dump(settled.value) as T,\n\t\t\t\t\t\t\t\tlogs: [...logs],\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t} finally {\n\t\t\t\t\t\t\tsettled.value.dispose();\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tsuccess: false,\n\t\t\t\t\t\t\terror: normalizeExecutionError(error),\n\t\t\t\t\t\t\tlogs: [...logs],\n\t\t\t\t\t\t};\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tresultHandle?.dispose();\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tasync dispose() {\n\t\t\t\t\tvm.dispose();\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t};\n}\n"],"mappings":";;AAGA,MAAM,qBAAqB;AAC3B,MAAM,0BAA0B;AAEhC,SAAS,mBAAmB,SAAiB,OAA+C;AAC3F,KAAI,CAAC,MAAO,QAAO,KAAA;AACnB,QAAO,MAAM,SAAS,QAAQ,GAAG,QAAQ,GAAG,QAAQ,IAAI;;AAGzD,SAAS,eAAe,OAAoC;AAC3D,KAAI,OAAO,UAAU,SAAU,QAAO,KAAA;CACtC,MAAM,YAAY,MAAM,MAAM,uBAAuB;AACrD,QAAO,YAAY,OAAO,UAAU,GAAG,GAAG,KAAA;;AAG3C,SAAS,wBAAwB,OAA0C;AAC1E,KAAI,iBAAiB,MACpB,QAAO;EACN,MAAM,MAAM;EACZ,SAAS,MAAM;EACf,OAAO,mBAAmB,MAAM,SAAS,MAAM,MAAM;EACrD,MAAM,eAAe,MAAM,MAAM;EACjC;AAGF,KAAI,OAAO,UAAU,YAAY,UAAU,MAAM;EAChD,MAAM,YAAY;EAClB,MAAM,UACL,OAAO,UAAU,YAAY,WAC1B,UAAU,UACV,KAAK,UAAU,UAAU,IAAI;AACjC,SAAO;GACN,MAAM,OAAO,UAAU,SAAS,WAAW,UAAU,OAAO,KAAA;GAC5D;GACA,OAAO,OAAO,UAAU,UAAU,WAAW,mBAAmB,SAAS,UAAU,MAAM,GAAG,KAAA;GAC5F,MAAM,OAAO,UAAU,SAAS,WAAW,UAAU,OAAO,eAAe,UAAU,MAAM;GAC3F;;AAGF,QAAO;EACN,MAAM;EACN,SAAS,OAAO,MAAM;EACtB;;AAGF,SAAS,YAAY,OAAmC;AACvD,SAAQ,SAAS,2BAA2B,OAAO;;AAGpD,SAAS,cAAc,IAAa,MAAgB;CACnD,MAAM,gBAAgB,GAAG,WAAW;AAQpC,MAAK,MAAM,CAAC,MAAM,WAPF;EACf,CAAC,OAAO,GAAG;EACX,CAAC,QAAQ,SAAS;EAClB,CAAC,QAAQ,SAAS;EAClB,CAAC,SAAS,UAAU;EACpB,EAEqC;EACrC,MAAM,KAAK,GAAG,YAAY,OAAO,GAAG,SAAS;GAC5C,MAAM,UAAU,KAAK,KAAK,QAAQ,OAAO,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI;AACjE,QAAK,KAAK,GAAG,SAAS,UAAU;AAChC,UAAO,GAAG;IACT;AACF,gBAAc,QAAQ,MAAM,GAAG;;AAGhC,IAAG,QAAQ,GAAG,QAAQ,WAAW,cAAc;;AAGhD,SAAS,iBAAiB,IAAa,UAAgD,QAAsB;CAC5G,IAAI,cAAc;AAElB,MAAK,MAAM,CAAC,MAAM,YAAY,OAAO,QAAQ,SAAS,EAAE;EACvD,MAAM,KAAK,GAAG,YAAY,OAAO,GAAG,SAAS;GAC5C,MAAM,QAAQ,KAAK,KAAK,GAAG,KAAK,KAAK,GAAG,GAAG,KAAA;GAC3C,MAAM,SAAS,QAAQ,QAAQ,QAAQ,QAAQ,GAAG,KAAK,GAAG,EAAE,eAAe,OAAO,OAAO,CAAC;AAC1F,UAAO,GAAG,aAAa,OAAO;IAC7B;AACF,KAAG,QAAQ,GAAG,QAAQ,MAAM,GAAG;;;AAIjC,SAAgB,0BAA2C;AAC1D,QAAO,EACN,MAAM,cAAc,QAAiE;EACpF,MAAM,UAAU,OAAO,WAAW;EAClC,MAAM,WAAW,KAAK,KAAK,GAAG;EAC9B,MAAM,OAAiB,EAAE;EAEzB,MAAM,KAAK,MAAM,QAAQ,OAAO;GAC/B,aAAa,YAAY,OAAO,YAAY;GAC5C,wBAAwB,QAAQ,OAAO,QAAQ,QAAQ,IAAI,KAAK,KAAK,GAAG;GACxE,CAAC;AAGF,gBAAc,IAAI,KAAK;AAEvB,mBAAiB,IAAI,OAAO,UAAU,OAAO,OAAO;AAEpD,SAAO;GACN,MAAM,QAAqB,MAAc;IACxC,IAAI;AAEJ,QAAI;AACH,oBAAe,GAAG,SAAS,MAAM,YAAY;AAC7C,QAAG,oBAAoB;KAEvB,MAAM,UAAU,MAAM,GAAG,eAAe,aAAa;AACrD,SAAI,WAAW,QACd,KAAI;AACH,aAAO;OACN,SAAS;OACT,OAAO,wBAAwB,GAAG,KAAK,QAAQ,MAAM,CAAC;OACtD,MAAM,CAAC,GAAG,KAAK;OACf;eACQ;AACT,cAAQ,MAAM,SAAS;;AAIzB,SAAI;AACH,aAAO;OACN,SAAS;OACT,OAAO,GAAG,KAAK,QAAQ,MAAM;OAC7B,MAAM,CAAC,GAAG,KAAK;OACf;eACQ;AACT,cAAQ,MAAM,SAAS;;aAEhB,OAAO;AACf,YAAO;MACN,SAAS;MACT,OAAO,wBAAwB,MAAM;MACrC,MAAM,CAAC,GAAG,KAAK;MACf;cACQ;AACT,mBAAc,SAAS;;;GAGzB,MAAM,UAAU;AACf,OAAG,SAAS;;GAEb;IAEF"}
|