import { Type } from "@sinclair/typebox";
import { Script, createContext } from "node:vm";
//#region src/agent/tools/execute-code-tool.ts
/**
* Programmatic tool calling (PTC): run sandboxed JS that invokes a subset of agent tools.
* vm is not a strong security boundary; only trusted models should use this tool.
*/
/** Default script wall time when `timeout` omitted (30 minutes). */
const DEFAULT_TIMEOUT_SEC = 1800;
/** Hard cap for `timeout` parameter (4 hours). */
const MAX_TIMEOUT_SEC = 14400;
const ExecuteCodeSchema = Type.Object({
	code: Type.String({ description: "JavaScript to run. Exposed as `tools` (async functions) and `console`:\n  await tools.web_search(query, count?)\n  await tools.web_fetch(url, maxChars?)\n  await tools.read_file(path, limit?)\n  await tools.write_file(path, content)\n  await tools.grep(pattern, { path?, glob?, ignoreCase?, literal?, context?, limit? })\n  await tools.find(pattern, { path?, limit? })\n  await tools.shell(command)\n\nUse console.log() for output; combined stdout/stderr is returned." }),
	timeout: Type.Optional(Type.Number({
		description: "Execution timeout in seconds (default: 3600 = 60m, max: 14400 = 4h)",
		default: DEFAULT_TIMEOUT_SEC
	}))
});
const SANDBOX_ALLOWED_TOOLS = new Set([
	"web_search",
	"web_fetch",
	"read_file",
	"write_file",
	"grep",
	"find",
	"shell",
	"skills_list",
	"skill_view"
]);
const MAX_TIMEOUT_MS = MAX_TIMEOUT_SEC * 1e3;
const MAX_TOOL_CALLS = 50;
const MAX_STDOUT_CHARS = 5e4;
const MAX_STDERR_CHARS = 1e4;
function stringifyArg(a) {
	if (typeof a === "string") return a;
	try {
		return JSON.stringify(a);
	} catch {
		return String(a);
	}
}
function extractTextFromResult(result) {
	return result.content.filter((c) => c.type === "text").map((c) => c.text).join("");
}
function buildSandboxToolMap(tools) {
	const m = /* @__PURE__ */ new Map();
	for (const t of tools) if (SANDBOX_ALLOWED_TOOLS.has(t.name)) m.set(t.name, t);
	return m;
}
function createToolsApi(getMap, signal) {
	let seq = 0;
	let calls = 0;
	const bump = () => {
		calls += 1;
		if (calls > MAX_TOOL_CALLS) throw new Error(`Exceeded max sandbox tool calls (${MAX_TOOL_CALLS})`);
	};
	const run = async (name, params) => {
		if (signal?.aborted) throw new Error("aborted");
		bump();
		const tool = getMap().get(name);
		if (!tool) throw new Error(`Tool not available in sandbox: ${name}`);
		const id = `ptc-${Date.now()}-${seq++}`;
		return extractTextFromResult(await tool.execute(id, params, signal));
	};
	return {
		web_search: async (query, count) => run("web_search", {
			query: String(query ?? ""),
			...typeof count === "number" && Number.isFinite(count) ? { count } : {}
		}),
		web_fetch: async (url, maxChars) => run("web_fetch", {
			url: String(url ?? ""),
			...typeof maxChars === "number" && Number.isFinite(maxChars) ? { maxChars } : {}
		}),
		read_file: async (path, limit) => run("read_file", {
			path: String(path ?? ""),
			...typeof limit === "number" && Number.isFinite(limit) ? { limit } : {}
		}),
		write_file: async (path, content) => run("write_file", {
			path: String(path ?? ""),
			content: String(content ?? "")
		}),
		grep: async (pattern, opts) => {
			const o = opts && typeof opts === "object" && opts !== null ? opts : {};
			return run("grep", {
				pattern: String(pattern ?? ""),
				...typeof o.path === "string" ? { path: o.path } : {},
				...typeof o.glob === "string" ? { glob: o.glob } : {},
				...typeof o.ignoreCase === "boolean" ? { ignoreCase: o.ignoreCase } : {},
				...typeof o.literal === "boolean" ? { literal: o.literal } : {},
				...typeof o.context === "number" && Number.isFinite(o.context) ? { context: o.context } : {},
				...typeof o.limit === "number" && Number.isFinite(o.limit) ? { limit: o.limit } : {}
			});
		},
		find: async (pattern, opts) => {
			const o = opts && typeof opts === "object" && opts !== null ? opts : {};
			return run("find", {
				pattern: String(pattern ?? ""),
				...typeof o.path === "string" ? { path: o.path } : {},
				...typeof o.limit === "number" && Number.isFinite(o.limit) ? { limit: o.limit } : {}
			});
		},
		shell: async (command) => run("shell", { command: String(command ?? "") })
	};
}
async function runSandboxedScript(code, sandbox, timeoutMs, signal) {
	const context = createContext(sandbox, { name: "execute_code" });
	const runPromise = new Script(`(async () => {\n${code}\n})()`, { filename: "execute_code.vm" }).runInContext(context);
	let timeoutId;
	const timeoutPromise = new Promise((_, rej) => {
		timeoutId = setTimeout(() => rej(/* @__PURE__ */ new Error(`Script timed out after ${Math.round(timeoutMs / 1e3)}s`)), timeoutMs);
	});
	const abortPromise = signal && new Promise((_, rej) => {
		if (signal.aborted) {
			rej(/* @__PURE__ */ new Error("aborted"));
			return;
		}
		signal.addEventListener("abort", () => rej(/* @__PURE__ */ new Error("aborted")), { once: true });
	});
	const racers = [runPromise, timeoutPromise];
	if (abortPromise) racers.push(abortPromise);
	try {
		await Promise.race(racers);
	} finally {
		if (timeoutId !== void 0) clearTimeout(timeoutId);
	}
}
function createExecuteCodeTool(deps) {
	return {
		name: "execute_code",
		label: "⚡ Execute Code",
		description: "Run sandboxed JavaScript that calls a subset of tools via `tools.*` (batch work in one step).\n\nOnly stdout/stderr from `console` plus tool return text (as strings) are visible — not full tool JSON.\n\nWHEN TO USE: loops over files/URLs, simple branching between tool calls.\nWHEN NOT TO USE: single tool calls; tasks needing full tool schemas or disallowed tools.\n\nAPI: `await tools.web_search(q, count?)`, `web_fetch(url, maxChars?)`, `read_file(path, limit?)`, `write_file(path, content)`, `grep(pattern, opts?)`, `find(glob, opts?)`, `shell(command)`. Use `console.log` for output.",
		parameters: ExecuteCodeSchema,
		async execute(_toolCallId, params, signal) {
			const p = params;
			const sec = p.timeout ?? DEFAULT_TIMEOUT_SEC;
			const timeoutMs = Math.min(Math.max(1, Number.isFinite(sec) ? sec : DEFAULT_TIMEOUT_SEC) * 1e3, MAX_TIMEOUT_MS);
			const stdout = [];
			const stderr = [];
			const sandbox = {
				tools: createToolsApi(deps.getSandboxToolMap, signal),
				Promise,
				JSON,
				Math,
				Date,
				Array,
				Object,
				String,
				Number,
				Boolean,
				RegExp,
				Map,
				Set,
				Error,
				TypeError,
				RangeError,
				parseInt,
				parseFloat,
				isNaN,
				isFinite,
				console: {
					log: (...args) => {
						stdout.push(args.map(stringifyArg).join(" "));
					},
					error: (...args) => {
						stderr.push(args.map(stringifyArg).join(" "));
					},
					warn: (...args) => {
						stderr.push(args.map(stringifyArg).join(" "));
					}
				},
				setTimeout,
				clearTimeout
			};
			try {
				await runSandboxedScript(p.code, sandbox, timeoutMs, signal);
				let out = stdout.join("\n");
				if (out.length > MAX_STDOUT_CHARS) out = `${out.slice(0, MAX_STDOUT_CHARS)}\n...(truncated)`;
				const parts = [];
				if (out.length > 0) parts.push(out);
				if (stderr.length > 0) {
					let err = stderr.join("\n");
					if (err.length > MAX_STDERR_CHARS) err = `${err.slice(0, MAX_STDERR_CHARS)}\n...(truncated)`;
					parts.push(`\nSTDERR:\n${err}`);
				}
				return {
					content: [{
						type: "text",
						text: parts.join("") || "(no output)"
					}],
					details: { exitCode: 0 }
				};
			} catch (error) {
				return {
					content: [{
						type: "text",
						text: `Execution failed: ${error instanceof Error ? error.message : String(error)}`
					}],
					details: { exitCode: 1 }
				};
			}
		}
	};
}
//#endregion
export { SANDBOX_ALLOWED_TOOLS, buildSandboxToolMap, createExecuteCodeTool };

//# sourceMappingURL=execute-code-tool.js.map