-
Notifications
You must be signed in to change notification settings - Fork 507
Expand file tree
/
Copy pathexecutor-tool.ts
More file actions
121 lines (106 loc) 路 4.03 KB
/
Copy pathexecutor-tool.ts
File metadata and controls
121 lines (106 loc) 路 4.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import { createExecTool, type ExecToolOutput } from "@cloudflare/computer/tools";
import type { ToolSet } from "ai";
export const EXECUTOR_BACKEND = "oolong-javascript";
export const RLM_BACKEND = "oolong-rlm-javascript";
type ExecWorkspace = Parameters<typeof createExecTool>[0]["workspace"];
const MAX_SAFE_RESULT_BYTES = 16 * 1024;
export function createExecutorTool(
workspace: ExecWorkspace,
backend: typeof EXECUTOR_BACKEND | typeof RLM_BACKEND,
): ToolSet {
const executor = createExecTool({
workspace,
backends: {
[backend]: {
description:
"Callable isolated JavaScript. The command must be a complete ES module with a default async function.",
},
},
defaultBackend: backend,
maxBytes: 16 * 1024,
streamMaxBytes: 16 * 1024,
});
const execute = executor.execute;
if (!execute) throw new Error("The executor tool must define execute().");
return {
executor: {
...executor,
execute: async function* (...args: Parameters<typeof execute>) {
const output = await execute(...args);
if (isAsyncIterable(output)) {
for await (const snapshot of output) yield sanitizeExecutorOutput(snapshot);
return;
}
yield sanitizeExecutorOutput(output);
},
},
};
}
export function sanitizeExecutorOutput(output: ExecToolOutput): ExecToolOutput {
const base = { command: output.command, cwd: output.cwd, backend: output.backend };
if ("error" in output) return { ...base, error: "Generated execution failed." };
return {
...base,
exitCode: output.exitCode,
stdout: "",
stderr: "",
result: safeResult(output.result),
};
}
function safeResult(value: unknown): Record<string, unknown> {
const answer = isRecord(value) && Object.hasOwn(value, "answer") ? value.answer : value;
const valid =
typeof answer === "string" ||
(typeof answer === "number" && Number.isFinite(answer)) ||
(Array.isArray(answer) &&
answer.every(
(item): item is string | number =>
typeof item === "string" || (typeof item === "number" && Number.isFinite(item)),
));
if (valid && encodedBytes(answer) <= MAX_SAFE_RESULT_BYTES) return { answer };
return {
accepted: false,
reason: "Expected a bounded scalar/list or an object containing answer.",
received: describeResult(value),
};
}
function describeResult(value: unknown): string {
if (Array.isArray(value)) return `array with ${value.length} items`;
if (isRecord(value)) {
return Object.hasOwn(value, "answer") ? "object with answer" : "object without answer";
}
if (value === null) return "null";
return typeof value;
}
function isAsyncIterable(value: unknown): value is AsyncIterable<ExecToolOutput> {
return value !== null && typeof value === "object" && Symbol.asyncIterator in value;
}
export function extractExecutionResult(toolResults: unknown): {
found: boolean;
value: unknown;
bytes: number;
} {
if (!Array.isArray(toolResults)) return { found: false, value: undefined, bytes: 0 };
for (const item of toolResults) {
if (!isRecord(item) || item.toolName !== "executor" || !isRecord(item.output)) continue;
if (Object.hasOwn(item.output, "result")) {
const value = item.output.result;
return { found: true, value, bytes: encodedBytes(value) };
}
}
return { found: false, value: undefined, bytes: 0 };
}
export function extractGeneratedSourceBytes(toolCalls: unknown): number {
if (!Array.isArray(toolCalls)) return 0;
return toolCalls.reduce((total, item) => {
if (!isRecord(item) || item.toolName !== "executor" || !isRecord(item.input)) return total;
return total + (typeof item.input.command === "string" ? encodedBytes(item.input.command) : 0);
}, 0);
}
function encodedBytes(value: unknown): number {
const text = typeof value === "string" ? value : JSON.stringify(value);
return new TextEncoder().encode(text ?? "").byteLength;
}
function isRecord(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === "object" && !Array.isArray(value);
}