import { n as toToolHandles } from "./declarations-jjWrmmrE.js";
import { Type } from "@sinclair/typebox";
import vm from "node:vm";

//#region src/koi/code-mode/run-code-tool.ts
/**
* `run_code` — the single meta-tool that collapses N tool calls into
* one model round-trip. The model emits a JavaScript program; we
* execute it inside a `vm` sandbox where every other tool is exposed
* as `tools.<name>(args) => Promise<...>`, and only what the program
* passes to `text(...)` (plus the resolved return value) flows back
* to the model.
*
* Why `node:vm` instead of `isolated-vm`:
*   - The model is the koi's own brain, not adversarial input. We
*     don't need a process-level boundary, just a clean global
*     surface that hides `process`, `require`, `fetch`, etc.
*   - `isolated-vm` is a native module — every EC2 deploy would need
*     a prebuild for the target platform. `node:vm` is built-in.
*   - Execution still runs in the koi process, so per-tool side
*     effects (sessions, file writes, sandbox bash) work unchanged.
*
* Lifetime: each `run_code` call spins up a fresh context. There's no
* persistent state between calls — the model can stitch one program
* across many tools, but if it wants to remember something across
* turns it has to call `text()`.
*/
const DEFAULT_WALL_TIME_MS = 6e4;
const MAX_TEXT_BYTES = 64 * 1024;
const MAX_PROGRESS_LINES = 64;
const MAX_PROGRESS_LINE_BYTES = 240;
const MAX_TOOL_CALLS_PER_RUN = 500;
/**
* Schema for the model-facing tool. We keep it boring on purpose so
* the model doesn't waste tokens on irrelevant params.
*/
const RUN_CODE_PARAMETERS = Type.Object({
	js: Type.String({ description: "An async JavaScript program. May `await` `tools.<name>(args)` calls and push output via `text(...)`. Wrap your code in `(async () => { ... })()` or use top-level await — both are accepted. The expression's resolved value will be appended as JSON to the output. Do NOT use require, import, fetch, process, or globalThis — none are available. Only `tools` and `text`/`progress` globals are exposed." }),
	timeoutMs: Type.Optional(Type.Number({ description: "Maximum total wall-clock budget for this run, in ms. Default 60000. Hard ceiling 180000." }))
});
/**
* Run a single piece of model-emitted JS in a fresh context.
* Exposes:
*   tools.<name>(args)  — every wrapped tool, async
*   text(msg)           — append to model-facing output
*   progress(msg)       — push a non-final status update
*   console.log/.error  — appended to a debug buffer (not surfaced)
*
* Disallows: anything Node-y. The global object is a plain {} with
* exactly the curated surface above.
*/
async function executeCodeInSandbox(source, handles, signal, deps, timeoutMs) {
	const texts = [];
	const progress = [];
	const toolCalls = [];
	let textBytes = 0;
	let totalToolCalls = 0;
	try {
		new vm.Script(`(async () => { ${source}\n})()`, { filename: "<code-mode:syntax-check>" });
	} catch (err) {
		const stack = err?.stack ?? "";
		const message = err?.message ?? String(err);
		throw new Error(`syntax error in run_code js: ${message}${stack ? `\n${stack.split("\n").slice(0, 2).join("\n")}` : ""}`);
	}
	const pushText = (msg) => {
		const s = typeof msg === "string" ? msg : safeStringify(msg);
		const next = textBytes + Buffer.byteLength(s);
		if (next > MAX_TEXT_BYTES) {
			const remaining = Math.max(0, MAX_TEXT_BYTES - textBytes);
			texts.push(s.slice(0, remaining) + "\n…[truncated]");
			textBytes = MAX_TEXT_BYTES;
			throw new Error(`text() output exceeded ${MAX_TEXT_BYTES} bytes — aborting`);
		}
		texts.push(s);
		textBytes = next;
	};
	function safeStringify(value) {
		try {
			return JSON.stringify(value, (_k, v) => {
				if (typeof v === "bigint") return v.toString();
				if (typeof v === "function") return `[Function ${v.name || "anonymous"}]`;
				return v;
			}, 2);
		} catch {
			const seen = /* @__PURE__ */ new WeakSet();
			try {
				return JSON.stringify(value, (_k, v) => {
					if (typeof v === "object" && v !== null) {
						if (seen.has(v)) return "[Circular]";
						seen.add(v);
					}
					if (typeof v === "bigint") return v.toString();
					return v;
				}, 2);
			} catch {
				return String(value);
			}
		}
	}
	const knownToolNames = new Set(handles.map((h) => h.name));
	const toolsObject = new Proxy({}, {
		get(_target, prop) {
			if (typeof prop !== "string") return void 0;
			if (prop === "then") return void 0;
			if (!knownToolNames.has(prop)) return async () => {
				const available = Array.from(knownToolNames).join(", ");
				throw new Error(`Unknown tool "${prop}". Available: ${available || "(none)"}.`);
			};
			const handle = handles.find((h) => h.name === prop);
			return async (args) => {
				if (signal.aborted) throw new Error("aborted before tool call");
				totalToolCalls += 1;
				if (totalToolCalls > MAX_TOOL_CALLS_PER_RUN) throw new Error(`Exceeded ${MAX_TOOL_CALLS_PER_RUN} tool calls in a single run_code program — refactor or batch.`);
				deps.onInnerStart?.(handle.name, args);
				const started = Date.now();
				let isError = false;
				try {
					return await handle.invoke(args, signal);
				} catch (err) {
					isError = true;
					const message = err?.message ?? String(err);
					throw new Error(`tools.${handle.name} failed: ${message}`);
				} finally {
					const durationMs = Date.now() - started;
					toolCalls.push({
						name: handle.name,
						durationMs,
						isError
					});
					deps.onInnerEnd?.(handle.name, isError);
				}
			};
		},
		ownKeys() {
			return Array.from(knownToolNames);
		},
		getOwnPropertyDescriptor(_target, prop) {
			if (typeof prop === "string" && knownToolNames.has(prop)) return {
				enumerable: true,
				configurable: true
			};
		},
		has(_target, prop) {
			return typeof prop === "string" && knownToolNames.has(prop);
		}
	});
	const context = vm.createContext({
		tools: toolsObject,
		text: pushText,
		progress: (msg) => {
			if (progress.length >= MAX_PROGRESS_LINES) return;
			const raw = typeof msg === "string" ? msg : safeStringify(msg);
			progress.push(raw.slice(0, MAX_PROGRESS_LINE_BYTES));
		},
		console: {
			log: () => {},
			warn: () => {},
			error: () => {},
			info: () => {},
			debug: () => {}
		},
		JSON,
		Math,
		Date,
		Array,
		Object,
		Number,
		String,
		Boolean,
		Map,
		Set,
		Promise,
		RegExp,
		Error,
		isNaN,
		isFinite,
		parseInt,
		parseFloat,
		encodeURIComponent,
		decodeURIComponent,
		setTimeout: ((cb, ms) => setTimeout(cb, Math.min(Math.max(0, ms), 1e4))),
		clearTimeout
	}, {
		name: "skykoi-code-mode",
		codeGeneration: {
			strings: false,
			wasm: false
		}
	});
	const wrapped = `(async () => { ${source}\n})()`;
	const started = Date.now();
	const script = new vm.Script(wrapped, { filename: "<code-mode>" });
	const innerAbort = new AbortController();
	const onAbort = () => innerAbort.abort();
	signal.addEventListener("abort", onAbort);
	const walltimer = setTimeout(() => innerAbort.abort(), timeoutMs);
	let returnValue = void 0;
	try {
		const promise = script.runInContext(context, { timeout: timeoutMs });
		returnValue = await Promise.race([promise, new Promise((_resolve, reject) => {
			innerAbort.signal.addEventListener("abort", () => reject(/* @__PURE__ */ new Error("run_code aborted (timeout or external cancel)")));
		})]);
	} finally {
		clearTimeout(walltimer);
		signal.removeEventListener("abort", onAbort);
	}
	return {
		texts,
		progress,
		returnValue,
		toolCalls,
		totalDurationMs: Date.now() - started
	};
}
/**
* Build the single meta-tool that replaces the koi's tool surface
* when code-mode is enabled. The inner tool set is captured at
* construction time — the same set the conversational loop would
* see — so behavior is consistent.
*/
function createRunCodeTool(deps) {
	const handles = toToolHandles(deps.inner);
	return {
		name: "run_code",
		label: "Run code",
		description: "Execute a JavaScript program that calls one or many koi tools in a single round-trip. Compose them: filter, loop, transform, chain. The program may push output to the user via `text(...)`. Only `text(...)` calls and the resolved return value reach the next model turn — intermediate tool results stay inside the sandbox.",
		parameters: RUN_CODE_PARAMETERS,
		execute: async (toolCallId, params, signal) => {
			const args = params ?? {};
			const timeoutMs = Math.min(18e4, Math.max(1e3, args.timeoutMs ?? DEFAULT_WALL_TIME_MS));
			const effectiveSignal = signal ?? new AbortController().signal;
			let outcome;
			try {
				outcome = await executeCodeInSandbox(args.js ?? "", handles, effectiveSignal, deps, timeoutMs);
			} catch (err) {
				return {
					content: [{
						type: "text",
						text: `run_code error: ${err?.message ?? String(err)}`
					}],
					details: {
						texts: [],
						progress: [],
						returnValue: null,
						toolCalls: [],
						totalDurationMs: 0
					}
				};
			}
			const parts = [];
			if (outcome.texts.length > 0) parts.push(outcome.texts.join("\n"));
			if (outcome.returnValue !== void 0 && outcome.returnValue !== null && outcome.returnValue !== "") {
				const rendered = typeof outcome.returnValue === "string" ? outcome.returnValue : JSON.stringify(outcome.returnValue, null, 2);
				parts.push(rendered);
			}
			if (parts.length === 0) parts.push("(program ran successfully but produced no output)");
			return {
				content: [{
					type: "text",
					text: parts.join("\n\n")
				}],
				details: outcome
			};
		}
	};
}

//#endregion
export { createRunCodeTool };