@codeworksh/aikit 0.3.1 → 0.4.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.
Files changed (41) hide show
  1. package/README.md +73 -81
  2. package/{agent → dist/pack/agent}/codemode/drivers/drivers.d.mts +1 -1
  3. package/dist/pack/codemode-BEDXG7WN.d.mts +8736 -0
  4. package/{index.d.mts → dist/pack/index.d.mts} +1 -1
  5. package/dist/pack/index.mjs +8226 -0
  6. package/dist/pack/index.mjs.map +1 -0
  7. package/package.json +31 -24
  8. package/src/agent/agent.ts +790 -0
  9. package/src/agent/codemode/codemode.ts +561 -0
  10. package/src/agent/codemode/drivers/drivers.ts +1 -0
  11. package/src/agent/codemode/drivers/quickjs-wasi-driver.ts +149 -0
  12. package/src/agent/codemode/index.ts +1 -0
  13. package/src/agent/loop.ts +903 -0
  14. package/src/agent.ts +11 -0
  15. package/src/event/event.ts +277 -0
  16. package/src/index.ts +6 -0
  17. package/src/llm.ts +31 -0
  18. package/src/message/message.ts +311 -0
  19. package/src/model/catalog.ts +94 -0
  20. package/src/model/model.ts +250 -0
  21. package/src/model/transform.ts +59 -0
  22. package/src/provider/provider.ts +60 -0
  23. package/src/provider/providers/anthropic/index.ts +792 -0
  24. package/src/provider/providers/github/github.ts +34 -0
  25. package/src/provider/providers/openai/completions.ts +953 -0
  26. package/src/provider/providers/utils.ts +7 -0
  27. package/src/provider/register.ts +6 -0
  28. package/src/provider/stream.ts +278 -0
  29. package/src/stream.ts +72 -0
  30. package/src/utils/eventstream.ts +95 -0
  31. package/src/utils/helpers.ts +24 -0
  32. package/src/utils/jsonparse.ts +32 -0
  33. package/src/utils/overflow.ts +121 -0
  34. package/src/utils/sanitize.ts +25 -0
  35. package/src/utils/validation.ts +87 -0
  36. package/LICENSE +0 -21
  37. package/codemode-SiETGR3z.d.mts +0 -8410
  38. package/index.mjs +0 -3381
  39. package/index.mjs.map +0 -1
  40. /package/{agent → dist/pack/agent}/codemode/drivers/drivers.mjs +0 -0
  41. /package/{agent → dist/pack/agent}/codemode/drivers/drivers.mjs.map +0 -0
@@ -0,0 +1,561 @@
1
+ import { NamedError } from "@codeworksh/utils";
2
+ import Type, { type Static, type TSchema } from "typebox";
3
+ import { transform } from "esbuild";
4
+ import { capitalize } from "remeda";
5
+ import { validateSchema } from "../../utils/validation";
6
+ import { Agent } from "../agent";
7
+
8
+ export namespace CodeMode {
9
+ export const ToolDefinitionErr = NamedError.create(
10
+ "ToolDefinitionErr",
11
+ Type.Object({
12
+ message: Type.String(),
13
+ }),
14
+ );
15
+
16
+ const toolFnPrefix = process.env.CODEWORK_CODEMODE_FN_PREFIX ?? "external_";
17
+
18
+ export const SandboxExecutionInputSchema = Type.Object({
19
+ typescriptCode: Type.String({
20
+ description: "TypeScript code to execute in the sandbox.",
21
+ }),
22
+ });
23
+ export type SandboxExecutionInput = Static<typeof SandboxExecutionInputSchema>;
24
+
25
+ export const SandboxExecutionOutputSchema = Type.Object({
26
+ result: Type.Optional(Type.Unknown()),
27
+ logs: Type.Optional(Type.Array(Type.String())),
28
+ });
29
+ export type SandboxExecutionOutput = Static<typeof SandboxExecutionOutputSchema>;
30
+
31
+ export const SandboxExecutionErrorSchema = Type.Object({
32
+ message: Type.String(),
33
+ name: Type.Optional(Type.String()),
34
+ stack: Type.Optional(Type.String()),
35
+ line: Type.Optional(Type.Number()),
36
+ logs: Type.Optional(Type.Array(Type.String())),
37
+ });
38
+ export type SandboxExecutionError = Static<typeof SandboxExecutionErrorSchema>;
39
+
40
+ export interface NormalizedError {
41
+ message: string;
42
+ name?: string;
43
+ stack?: string;
44
+ line?: number;
45
+ }
46
+
47
+ export interface ExecutionResult<T = unknown> {
48
+ success: boolean;
49
+ value?: T;
50
+ logs?: string[];
51
+ error?: NormalizedError;
52
+ }
53
+
54
+ export interface Context {
55
+ execute: <T = unknown>(code: string) => Promise<ExecutionResult<T>>;
56
+ dispose: () => Promise<void>;
57
+ }
58
+
59
+ export interface DriverContextConfig {
60
+ bindings: Record<string, ToolBinding>;
61
+ timeout?: number;
62
+ memoryLimit?: number;
63
+ signal?: AbortSignal;
64
+ }
65
+
66
+ export interface Driver {
67
+ createContext: (config: DriverContextConfig) => Promise<Context>;
68
+ }
69
+
70
+ export interface ToolConfig {
71
+ /**
72
+ * Driver for sandbox code execution
73
+ */
74
+ driver: Driver;
75
+ /**
76
+ * Tools to expose for functions in sandbox
77
+ */
78
+ tools: Agent.AnyAgentTool[];
79
+ /**
80
+ * Execution timeout in milliseconds (default: 30000)
81
+ */
82
+ timeout?: number;
83
+ /**
84
+ * Memory limit in MB for sandbox (default: 128)
85
+ */
86
+ memoryLimit?: number;
87
+ /**
88
+ * Tool function prefix.
89
+ * @default env.CODEWORK_CODEMODE_FN_PREFIX ?? "external_"
90
+ */
91
+ fnPrefix?: string;
92
+ }
93
+
94
+ export interface ToolBinding {
95
+ name: string;
96
+ description: string;
97
+ inputSchema: TSchema;
98
+ outputSchema?: TSchema;
99
+ errorSchema?: TSchema;
100
+ execute: (callID: string, params: unknown, signal?: AbortSignal) => Promise<unknown>;
101
+ }
102
+
103
+ /**
104
+ * Options for type stub generation
105
+ */
106
+ export interface TypeGeneratorOptions {
107
+ /**
108
+ * Include JSDoc comments with descriptions
109
+ * @default true
110
+ */
111
+ includeDescriptions?: boolean;
112
+ }
113
+
114
+ function withMessageInStack(message: string, stack: string | undefined): string | undefined {
115
+ if (!stack) return undefined;
116
+ return stack.includes(message) ? stack : `${message}\n${stack}`;
117
+ }
118
+
119
+ function normalizeExecutionError(error: unknown): NormalizedError {
120
+ if (error instanceof Error) {
121
+ return {
122
+ name: error.name,
123
+ message: error.message,
124
+ stack: withMessageInStack(error.message, error.stack),
125
+ line: parseErrorLine(error.stack),
126
+ };
127
+ }
128
+
129
+ if (typeof error === "object" && error !== null) {
130
+ const candidate = error as Record<string, unknown>;
131
+ return {
132
+ name: typeof candidate.name === "string" ? candidate.name : undefined,
133
+ message:
134
+ typeof candidate.message === "string"
135
+ ? candidate.message
136
+ : JSON.stringify(candidate) || "Unknown execution error",
137
+ stack:
138
+ typeof candidate.stack === "string"
139
+ ? withMessageInStack(
140
+ typeof candidate.message === "string"
141
+ ? candidate.message
142
+ : JSON.stringify(candidate) || "Unknown execution error",
143
+ candidate.stack,
144
+ )
145
+ : undefined,
146
+ line: typeof candidate.line === "number" ? candidate.line : parseErrorLine(candidate.stack),
147
+ };
148
+ }
149
+
150
+ return {
151
+ name: "Error",
152
+ message: String(error),
153
+ };
154
+ }
155
+
156
+ function parseErrorLine(stack: unknown): number | undefined {
157
+ if (typeof stack !== "string") return undefined;
158
+ const lineMatch = stack.match(/:(\d+)(?::\d+)?\)?$/m);
159
+ return lineMatch ? Number(lineMatch[1]) : undefined;
160
+ }
161
+
162
+ function serializeForContent(value: unknown): string {
163
+ return JSON.stringify(
164
+ value,
165
+ (_key, currentValue) => (typeof currentValue === "bigint" ? currentValue.toString() : currentValue),
166
+ 2,
167
+ );
168
+ }
169
+
170
+ function toolContentToText(result: { content: Array<{ type: string; text?: string }> }): string {
171
+ const parts = result.content
172
+ .filter((part) => part.type === "text" && typeof part.text === "string")
173
+ .map((part) => part.text);
174
+ return parts.join("\n").trim();
175
+ }
176
+
177
+ function unwrapToolResult(tool: Agent.AnyAgentTool, result: Agent.ToolTerminalResult<unknown, unknown>): unknown {
178
+ if (result.status === "completed") {
179
+ if (tool.outputSchema) {
180
+ return validateSchema(tool.outputSchema, result.result.details, `Tool "${tool.name}" output`);
181
+ }
182
+ return result.result.details;
183
+ }
184
+
185
+ if (tool.errorSchema && result.result.details !== undefined) {
186
+ validateSchema(tool.errorSchema, result.result.details, `Tool "${tool.name}" error`);
187
+ }
188
+
189
+ const message = toolContentToText(result.result) || `Tool "${tool.name}" failed`;
190
+ const error = new Error(message);
191
+ if (result.result.details !== undefined) {
192
+ (error as Error & { cause?: unknown }).cause = result.result.details;
193
+ }
194
+ throw error;
195
+ }
196
+
197
+ function toolToBinding(tool: Agent.AnyAgentTool, prefix: string = ""): ToolBinding {
198
+ const inputSchema = Type.IsSchema(tool.parameters) ? tool.parameters : Type.Object({});
199
+ const outputSchema = Type.IsSchema(tool.outputSchema) ? tool.outputSchema : undefined;
200
+ const errorSchema = Type.IsSchema(tool.errorSchema) ? tool.errorSchema : undefined;
201
+
202
+ if (!("execute" in tool) || typeof tool.execute !== "function") {
203
+ throw new ToolDefinitionErr({
204
+ message: `tool "${tool.name}" does not have an execute function. code mode requires tools with implementation`,
205
+ });
206
+ }
207
+
208
+ return {
209
+ name: `${prefix}${tool.name}`,
210
+ description: tool.description,
211
+ inputSchema,
212
+ outputSchema,
213
+ errorSchema,
214
+ execute: async (callID: string, params: unknown, signal?: AbortSignal) => {
215
+ const validatedParams = validateSchema(inputSchema, params, `Tool "${tool.name}" input`);
216
+ const terminalResult = await tool.execute(callID, validatedParams, signal);
217
+ return unwrapToolResult(tool, terminalResult);
218
+ },
219
+ };
220
+ }
221
+
222
+ function toolsForBinding(tools: Agent.AnyAgentTool[], prefix: string): Record<string, ToolBinding> {
223
+ const bindings: Record<string, ToolBinding> = {};
224
+
225
+ for (const tool of tools) {
226
+ const name = `${prefix}${tool.name}`;
227
+ bindings[name] = toolToBinding(tool, prefix);
228
+ }
229
+
230
+ return bindings;
231
+ }
232
+
233
+ function indent(value: string, depth: number = 1): string {
234
+ const prefix = "\t".repeat(depth);
235
+ return value
236
+ .split("\n")
237
+ .map((line) => `${prefix}${line}`)
238
+ .join("\n");
239
+ }
240
+
241
+ function toPropertyKey(name: string): string {
242
+ return /^[$A-Z_a-z][$\w]*$/i.test(name) ? name : JSON.stringify(name);
243
+ }
244
+
245
+ function schemaToTypeScript(schema: TSchema): string {
246
+ if (Type.IsAny(schema)) return "any";
247
+ if (Type.IsUnknown(schema)) return "unknown";
248
+ if (Type.IsString(schema)) return "string";
249
+ if (Type.IsNumber(schema) || Type.IsInteger(schema)) return "number";
250
+ if (Type.IsBoolean(schema)) return "boolean";
251
+ if (Type.IsNull(schema)) return "null";
252
+ if (Type.IsUndefined(schema) || Type.IsVoid(schema)) return "undefined";
253
+ if (Type.IsLiteral(schema)) return JSON.stringify(schema.const);
254
+
255
+ if (Type.IsArray(schema)) {
256
+ return `Array<${schemaToTypeScript(schema.items)}>`;
257
+ }
258
+
259
+ if (Type.IsTuple(schema)) {
260
+ const items = schema.items ?? [];
261
+ return `[${items.map((item) => schemaToTypeScript(item)).join(", ")}]`;
262
+ }
263
+
264
+ if (Type.IsUnion(schema)) {
265
+ return schema.anyOf.map((item) => schemaToTypeScript(item)).join(" | ");
266
+ }
267
+
268
+ if (Type.IsIntersect(schema)) {
269
+ return schema.allOf.map((item) => schemaToTypeScript(item)).join(" & ");
270
+ }
271
+
272
+ if (Type.IsRecord(schema)) {
273
+ const additionalProperties = (schema as { additionalProperties?: unknown }).additionalProperties;
274
+ const valueSchema =
275
+ Object.values(schema.patternProperties ?? {}).find(Type.IsSchema) ??
276
+ (additionalProperties && Type.IsSchema(additionalProperties) ? additionalProperties : Type.Unknown());
277
+ return `Record<string, ${schemaToTypeScript(valueSchema)}>`;
278
+ }
279
+
280
+ if (Type.IsObject(schema)) {
281
+ const additionalProperties = (schema as { additionalProperties?: unknown }).additionalProperties;
282
+ const required = new Set(schema.required ?? []);
283
+ const properties = Object.entries(schema.properties ?? {}).map(([key, value]) => {
284
+ const optional = required.has(key) ? "" : "?";
285
+ return `${toPropertyKey(key)}${optional}: ${schemaToTypeScript(value)};`;
286
+ });
287
+
288
+ if (additionalProperties === true) {
289
+ properties.push("[key: string]: unknown;");
290
+ } else if (additionalProperties && Type.IsSchema(additionalProperties)) {
291
+ properties.push(`[key: string]: ${schemaToTypeScript(additionalProperties)};`);
292
+ }
293
+
294
+ if (properties.length === 0) return "{}";
295
+
296
+ return `{\n${indent(properties.join("\n"))}\n}`;
297
+ }
298
+
299
+ return "unknown";
300
+ }
301
+
302
+ /**
303
+ * Generate TypeScript type stubs for all tool bindings.
304
+ *
305
+ * These stubs are included in the LLM system prompt so it knows the exact
306
+ * type signatures of available tools.
307
+ */
308
+ export function generateTypeStubs(
309
+ bindings: Record<string, ToolBinding>,
310
+ options: TypeGeneratorOptions = {},
311
+ ): string {
312
+ const { includeDescriptions = true } = options;
313
+ const declarations: string[] = [];
314
+
315
+ for (const [name, binding] of Object.entries(bindings)) {
316
+ const inputTypeName = `${capitalize(name)}Input`;
317
+ const outputTypeName = `${capitalize(name)}Output`;
318
+
319
+ declarations.push(`type ${inputTypeName} = ${schemaToTypeScript(binding.inputSchema)};`);
320
+
321
+ let outputTypeRef = "unknown";
322
+ if (binding.outputSchema !== undefined) {
323
+ declarations.push(`type ${outputTypeName} = ${schemaToTypeScript(binding.outputSchema)};`);
324
+ outputTypeRef = outputTypeName;
325
+ }
326
+
327
+ const description = includeDescriptions && binding.description ? `/** ${binding.description} */\n` : "";
328
+ declarations.push(
329
+ `${description}declare function ${name}(input: ${inputTypeName}): Promise<${outputTypeRef}>;`,
330
+ );
331
+ }
332
+
333
+ return declarations.join("\n\n");
334
+ }
335
+
336
+ function buildExampleSnippet(): string[] {
337
+ return [
338
+ "```typescript",
339
+ "const numbers = [12, 24, 36];",
340
+ "const total = numbers.reduce((sum, value) => sum + value, 0);",
341
+ "",
342
+ "return {",
343
+ " count: numbers.length,",
344
+ " average: total / numbers.length,",
345
+ "};",
346
+ "```",
347
+ ];
348
+ }
349
+
350
+ export function generateSystemPrompt(config: ToolConfig): string {
351
+ const fnPrefix = config.fnPrefix ?? toolFnPrefix;
352
+ const bindings = toolsForBinding(config.tools, fnPrefix);
353
+ const typeStubs = generateTypeStubs(bindings);
354
+ const functionDocs =
355
+ Object.entries(bindings)
356
+ .map(([name, binding]) => `- \`${name}(input)\`: ${binding.description}`)
357
+ .join("\n") || "- No external APIs available for this run.";
358
+
359
+ return [
360
+ "## Code Execution Tool",
361
+ "",
362
+ "You have access to `sandbox_execute_typescript` which runs TypeScript code in a sandboxed environment.",
363
+ "",
364
+ "### When to Use",
365
+ "",
366
+ "Use `sandbox_execute_typescript` when you need to:",
367
+ "- Process data with loops, conditionals, or complex logic",
368
+ "- Make multiple tool calls in parallel with `Promise.all(...)`",
369
+ "- Transform, filter, or aggregate data",
370
+ "- Perform calculations or data analysis",
371
+ "",
372
+ "For simple operations, prefer calling tools directly.",
373
+ "",
374
+ "### Available External APIs",
375
+ "",
376
+ "Inside your TypeScript code, you can call these async functions:",
377
+ "",
378
+ functionDocs,
379
+ "",
380
+ "### Type Definitions",
381
+ "",
382
+ "```typescript",
383
+ typeStubs,
384
+ "```",
385
+ "",
386
+ "### Example",
387
+ "",
388
+ ...buildExampleSnippet(),
389
+ "",
390
+ "### Important Notes",
391
+ "",
392
+ "- All" + `\`${fnPrefix}\`*` + "calls are async, so always use `await`",
393
+ "- Return a value to pass results back to you",
394
+ "- Use `console.log()` for debugging (logs are captured)",
395
+ "- The sandbox is isolated - no network access or file system",
396
+ "- Each execution is isolated and does not share state with previous runs",
397
+ "",
398
+ ].join("\n");
399
+ }
400
+
401
+ async function transpileTypeScript(typescriptCode: string): Promise<string> {
402
+ const wrappedSource = [
403
+ "(async () => {",
404
+ "\tconst __codemodeNormalizeError = (error: unknown) => {",
405
+ "\t\tif (error instanceof Error) return { name: error.name, message: error.message, stack: error.stack };",
406
+ "\t\tif (typeof error === 'object' && error !== null) {",
407
+ "\t\t\tconst candidate = error as { name?: unknown; message?: unknown; stack?: unknown };",
408
+ "\t\t\treturn {",
409
+ "\t\t\t\tname: typeof candidate.name === 'string' ? candidate.name : 'Error',",
410
+ "\t\t\t\tmessage:",
411
+ "\t\t\t\t\ttypeof candidate.message === 'string'",
412
+ "\t\t\t\t\t\t? candidate.message",
413
+ "\t\t\t\t\t\t: JSON.stringify(candidate) || 'Unknown execution error',",
414
+ "\t\t\t\tstack: typeof candidate.stack === 'string' ? candidate.stack : undefined,",
415
+ "\t\t\t};",
416
+ "\t\t}",
417
+ "\t\treturn { name: 'Error', message: String(error) };",
418
+ "\t};",
419
+ "\ttry {",
420
+ typescriptCode,
421
+ "\t} catch (error) {",
422
+ "\t\tthrow __codemodeNormalizeError(error);",
423
+ "\t}",
424
+ "})()",
425
+ ].join("\n");
426
+ const transformed = await transform(wrappedSource, {
427
+ loader: "ts",
428
+ target: "es2022",
429
+ sourcefile: "script.ts",
430
+ minify: false,
431
+ keepNames: false,
432
+ });
433
+ return transformed.code;
434
+ }
435
+
436
+ function buildToolDescription(tools: Agent.AnyAgentTool[], fnPrefix = toolFnPrefix): string {
437
+ const externalFunctions = tools.map((tool) => `${fnPrefix}${tool.name}`).join("\n");
438
+
439
+ return [
440
+ "Execute TypeScript code in a secure sandbox environment. ",
441
+ "The code can use these external API functions:",
442
+ "<functions>",
443
+ ` ${externalFunctions}`,
444
+ "</functions>",
445
+ `All ${fnPrefix}* calls are async and must be awaited. `,
446
+ "Return a value to pass results back. Use console.log() for debugging.",
447
+ ].join("\n");
448
+ }
449
+
450
+ export function createCodeModeTool(
451
+ config: ToolConfig,
452
+ ): Agent.AgentTool<
453
+ typeof SandboxExecutionInputSchema,
454
+ typeof SandboxExecutionOutputSchema,
455
+ unknown,
456
+ typeof SandboxExecutionErrorSchema
457
+ > {
458
+ if (config.tools.length === 0) {
459
+ throw new ToolDefinitionErr({ message: "at least one tool must be provided to `createCodeModeTool`" });
460
+ }
461
+
462
+ const tool = Agent.defineTool({
463
+ name: "sandbox_execute_typescript",
464
+ label: "Sandbox TypeScript",
465
+ description: buildToolDescription(config.tools, config.fnPrefix),
466
+ parameters: SandboxExecutionInputSchema,
467
+ outputSchema: SandboxExecutionOutputSchema,
468
+ errorSchema: SandboxExecutionErrorSchema,
469
+ async execute(_callID, params, signal, onUpdate) {
470
+ const bindings = toolsForBinding(config.tools, config.fnPrefix ?? toolFnPrefix);
471
+ let context: Context | undefined;
472
+
473
+ try {
474
+ await onUpdate?.({
475
+ status: "running",
476
+ partial: {
477
+ content: [{ type: "text", text: "Transpiling TypeScript for sandbox execution" }],
478
+ },
479
+ });
480
+
481
+ const javascriptCode = await transpileTypeScript(params.typescriptCode);
482
+
483
+ await onUpdate?.({
484
+ status: "running",
485
+ partial: {
486
+ content: [{ type: "text", text: "Executing code in sandbox runtime" }],
487
+ },
488
+ });
489
+
490
+ context = await config.driver.createContext({
491
+ bindings,
492
+ timeout: config.timeout,
493
+ memoryLimit: config.memoryLimit,
494
+ signal,
495
+ });
496
+
497
+ const executionResult = await context.execute(javascriptCode);
498
+ if (executionResult.success) {
499
+ const details: SandboxExecutionOutput = {
500
+ result: executionResult.value,
501
+ ...(executionResult.logs && executionResult.logs.length > 0 ? { logs: executionResult.logs } : {}),
502
+ };
503
+
504
+ return {
505
+ status: "completed",
506
+ result: {
507
+ content: [{ type: "text", text: serializeForContent(details) }],
508
+ details,
509
+ isError: false,
510
+ },
511
+ };
512
+ }
513
+
514
+ const details: SandboxExecutionError = {
515
+ message: executionResult.error?.message || "sandbox execution failed",
516
+ ...(executionResult.error?.name ? { name: executionResult.error.name } : {}),
517
+ ...(executionResult.error?.stack ? { stack: executionResult.error.stack } : {}),
518
+ ...(executionResult.error?.line != null ? { line: executionResult.error.line } : {}),
519
+ ...(executionResult.logs && executionResult.logs.length > 0 ? { logs: executionResult.logs } : {}),
520
+ };
521
+
522
+ return {
523
+ status: "error",
524
+ result: {
525
+ content: [{ type: "text", text: serializeForContent(details) }],
526
+ details,
527
+ isError: true,
528
+ },
529
+ };
530
+ } catch (error) {
531
+ const normalizedError = normalizeExecutionError(error);
532
+ const details: SandboxExecutionError = {
533
+ message: normalizedError.message,
534
+ ...(normalizedError.name ? { name: normalizedError.name } : {}),
535
+ ...(normalizedError.stack ? { stack: normalizedError.stack } : {}),
536
+ ...(normalizedError.line != null ? { line: normalizedError.line } : {}),
537
+ };
538
+
539
+ return {
540
+ status: "error",
541
+ result: {
542
+ content: [{ type: "text", text: serializeForContent(details) }],
543
+ details,
544
+ isError: true,
545
+ },
546
+ };
547
+ } finally {
548
+ await context?.dispose();
549
+ }
550
+ },
551
+ });
552
+ return tool;
553
+ }
554
+
555
+ export async function create(config: ToolConfig) {
556
+ return {
557
+ tool: createCodeModeTool(config),
558
+ systemPrompt: generateSystemPrompt(config),
559
+ };
560
+ }
561
+ }
@@ -0,0 +1 @@
1
+ export { createQuickJSWasiDriver } from "./quickjs-wasi-driver";
@@ -0,0 +1,149 @@
1
+ import { QuickJS } from "quickjs-wasi";
2
+ import type { CodeMode } from "../codemode";
3
+
4
+ const DEFAULT_TIMEOUT_MS = 30_000;
5
+ const DEFAULT_MEMORY_LIMIT_MB = 128;
6
+
7
+ function withMessageInStack(message: string, stack: string | undefined): string | undefined {
8
+ if (!stack) return undefined;
9
+ return stack.includes(message) ? stack : `${message}\n${stack}`;
10
+ }
11
+
12
+ function parseErrorLine(stack: unknown): number | undefined {
13
+ if (typeof stack !== "string") return undefined;
14
+ const lineMatch = stack.match(/:(\d+)(?::\d+)?\)?$/m);
15
+ return lineMatch ? Number(lineMatch[1]) : undefined;
16
+ }
17
+
18
+ function normalizeExecutionError(error: unknown): CodeMode.NormalizedError {
19
+ if (error instanceof Error) {
20
+ return {
21
+ name: error.name,
22
+ message: error.message,
23
+ stack: withMessageInStack(error.message, error.stack),
24
+ line: parseErrorLine(error.stack),
25
+ };
26
+ }
27
+
28
+ if (typeof error === "object" && error !== null) {
29
+ const candidate = error as Record<string, unknown>;
30
+ const message =
31
+ typeof candidate.message === "string"
32
+ ? candidate.message
33
+ : JSON.stringify(candidate) || "Unknown Execution Error";
34
+ return {
35
+ name: typeof candidate.name === "string" ? candidate.name : undefined,
36
+ message,
37
+ stack: typeof candidate.stack === "string" ? withMessageInStack(message, candidate.stack) : undefined,
38
+ line: typeof candidate.line === "number" ? candidate.line : parseErrorLine(candidate.stack),
39
+ };
40
+ }
41
+
42
+ return {
43
+ name: "Error",
44
+ message: String(error),
45
+ };
46
+ }
47
+
48
+ function toMegabytes(value: number | undefined): number {
49
+ return (value ?? DEFAULT_MEMORY_LIMIT_MB) * 1024 * 1024;
50
+ }
51
+
52
+ function createConsole(vm: QuickJS, logs: string[]) {
53
+ const consoleObject = vm.newObject();
54
+ const methods = [
55
+ ["log", ""],
56
+ ["info", "INFO: "],
57
+ ["warn", "WARN: "],
58
+ ["error", "ERROR: "],
59
+ ] as const;
60
+
61
+ for (const [name, prefix] of methods) {
62
+ const fn = vm.newFunction(name, (...args) => {
63
+ const message = args.map((arg) => String(vm.dump(arg))).join(" ");
64
+ logs.push(`${prefix}${message}`);
65
+ return vm.undefined;
66
+ });
67
+ consoleObject.setProp(name, fn);
68
+ }
69
+
70
+ vm.setProp(vm.global, "console", consoleObject);
71
+ }
72
+
73
+ function registerBindings(vm: QuickJS, bindings: Record<string, CodeMode.ToolBinding>, signal?: AbortSignal) {
74
+ let callCounter = 0;
75
+
76
+ for (const [name, binding] of Object.entries(bindings)) {
77
+ const fn = vm.newFunction(name, (...args) => {
78
+ const input = args[0] ? vm.dump(args[0]) : undefined;
79
+ const result = Promise.resolve(binding.execute(`${name}:${++callCounter}`, input, signal));
80
+ return vm.hostToHandle(result);
81
+ });
82
+ vm.setProp(vm.global, name, fn);
83
+ }
84
+ }
85
+
86
+ export function createQuickJSWasiDriver(): CodeMode.Driver {
87
+ return {
88
+ async createContext(config: CodeMode.DriverContextConfig): Promise<CodeMode.Context> {
89
+ const timeout = config.timeout ?? DEFAULT_TIMEOUT_MS;
90
+ const deadline = Date.now() + timeout;
91
+ const logs: string[] = [];
92
+ // create VM for execution sandbox
93
+ const vm = await QuickJS.create({
94
+ memoryLimit: toMegabytes(config.memoryLimit),
95
+ interruptHandler: () => Boolean(config.signal?.aborted) || Date.now() > deadline,
96
+ });
97
+
98
+ // create console log bindings
99
+ createConsole(vm, logs);
100
+ // register tool fn bindings
101
+ registerBindings(vm, config.bindings, config.signal);
102
+
103
+ return {
104
+ async execute<T = unknown>(code: string) {
105
+ let resultHandle: ReturnType<typeof vm.evalCode> | undefined;
106
+
107
+ try {
108
+ resultHandle = vm.evalCode(code, "script.js");
109
+ vm.executePendingJobs();
110
+
111
+ const settled = await vm.resolvePromise(resultHandle);
112
+ if ("error" in settled) {
113
+ try {
114
+ return {
115
+ success: false,
116
+ error: normalizeExecutionError(vm.dump(settled.error)),
117
+ logs: [...logs],
118
+ };
119
+ } finally {
120
+ settled.error.dispose();
121
+ }
122
+ }
123
+
124
+ try {
125
+ return {
126
+ success: true,
127
+ value: vm.dump(settled.value) as T,
128
+ logs: [...logs],
129
+ };
130
+ } finally {
131
+ settled.value.dispose();
132
+ }
133
+ } catch (error) {
134
+ return {
135
+ success: false,
136
+ error: normalizeExecutionError(error),
137
+ logs: [...logs],
138
+ };
139
+ } finally {
140
+ resultHandle?.dispose();
141
+ }
142
+ },
143
+ async dispose() {
144
+ vm.dispose();
145
+ },
146
+ };
147
+ },
148
+ };
149
+ }
@@ -0,0 +1 @@
1
+ export { CodeMode } from "./codemode";