import { r as __exportAll } from "./chunk-D_gEzPfs.js";
import { t as createSubsystemLogger } from "./subsystem-hATE68va.js";
import { n as logError, t as logDebug } from "./logger-BKidfKRU.js";
import { o as normalizeToolName } from "./tool-policy-itfBRjY_.js";
import { o as detectMime } from "./image-ops-B6MN1Uli.js";
import { r as sanitizeToolResultImages } from "./tool-images-DOYdkgEG.js";
import fs from "node:fs/promises";

//#region src/plugins/hooks.ts
/**
* Get hooks for a specific hook name, sorted by priority (higher first).
*/
function getHooksForName(registry, hookName) {
	return registry.typedHooks.filter((h) => h.hookName === hookName).toSorted((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
}
/**
* Create a hook runner for a specific registry.
*/
function createHookRunner(registry, options = {}) {
	const logger = options.logger;
	const catchErrors = options.catchErrors ?? true;
	/**
	* Run a hook that doesn't return a value (fire-and-forget style).
	* All handlers are executed in parallel for performance.
	*/
	async function runVoidHook(hookName, event, ctx) {
		const hooks = getHooksForName(registry, hookName);
		if (hooks.length === 0) return;
		logger?.debug?.(`[hooks] running ${hookName} (${hooks.length} handlers)`);
		const promises = hooks.map(async (hook) => {
			try {
				await hook.handler(event, ctx);
			} catch (err) {
				const msg = `[hooks] ${hookName} handler from ${hook.pluginId} failed: ${String(err)}`;
				if (catchErrors) logger?.error(msg);
				else throw new Error(msg, { cause: err });
			}
		});
		await Promise.all(promises);
	}
	/**
	* Run a hook that can return a modifying result.
	* Handlers are executed sequentially in priority order, and results are merged.
	*/
	async function runModifyingHook(hookName, event, ctx, mergeResults) {
		const hooks = getHooksForName(registry, hookName);
		if (hooks.length === 0) return;
		logger?.debug?.(`[hooks] running ${hookName} (${hooks.length} handlers, sequential)`);
		let result;
		for (const hook of hooks) try {
			const handlerResult = await hook.handler(event, ctx);
			if (handlerResult !== void 0 && handlerResult !== null) if (mergeResults && result !== void 0) result = mergeResults(result, handlerResult);
			else result = handlerResult;
		} catch (err) {
			const msg = `[hooks] ${hookName} handler from ${hook.pluginId} failed: ${String(err)}`;
			if (catchErrors) logger?.error(msg);
			else throw new Error(msg, { cause: err });
		}
		return result;
	}
	/**
	* Run before_koi_start hook.
	* Allows plugins to inject context into the system prompt.
	* Runs sequentially, merging systemPrompt and prependContext from all handlers.
	*/
	async function runBeforeKoiStart(event, ctx) {
		return runModifyingHook("before_koi_start", event, ctx, (acc, next) => ({
			systemPrompt: next.systemPrompt ?? acc?.systemPrompt,
			prependContext: acc?.prependContext && next.prependContext ? `${acc.prependContext}\n\n${next.prependContext}` : next.prependContext ?? acc?.prependContext
		}));
	}
	/**
	* Run koi_end hook.
	* Allows plugins to analyze completed conversations.
	* Runs in parallel (fire-and-forget).
	*/
	async function runKoiEnd(event, ctx) {
		return runVoidHook("koi_end", event, ctx);
	}
	/**
	* Run before_compaction hook.
	*/
	async function runBeforeCompaction(event, ctx) {
		return runVoidHook("before_compaction", event, ctx);
	}
	/**
	* Run after_compaction hook.
	*/
	async function runAfterCompaction(event, ctx) {
		return runVoidHook("after_compaction", event, ctx);
	}
	/**
	* Run message_received hook.
	* Runs in parallel (fire-and-forget).
	*/
	async function runMessageReceived(event, ctx) {
		return runVoidHook("message_received", event, ctx);
	}
	/**
	* Run message_sending hook.
	* Allows plugins to modify or cancel outgoing messages.
	* Runs sequentially.
	*/
	async function runMessageSending(event, ctx) {
		return runModifyingHook("message_sending", event, ctx, (acc, next) => ({
			content: next.content ?? acc?.content,
			cancel: next.cancel ?? acc?.cancel
		}));
	}
	/**
	* Run message_sent hook.
	* Runs in parallel (fire-and-forget).
	*/
	async function runMessageSent(event, ctx) {
		return runVoidHook("message_sent", event, ctx);
	}
	/**
	* Run before_tool_call hook.
	* Allows plugins to modify or block tool calls.
	* Runs sequentially.
	*/
	async function runBeforeToolCall(event, ctx) {
		return runModifyingHook("before_tool_call", event, ctx, (acc, next) => ({
			params: next.params ?? acc?.params,
			block: next.block ?? acc?.block,
			blockReason: next.blockReason ?? acc?.blockReason
		}));
	}
	/**
	* Run after_tool_call hook.
	* Runs in parallel (fire-and-forget).
	*/
	async function runAfterToolCall(event, ctx) {
		return runVoidHook("after_tool_call", event, ctx);
	}
	/**
	* Run tool_result_persist hook.
	*
	* This hook is intentionally synchronous: it runs in hot paths where session
	* transcripts are appended synchronously.
	*
	* Handlers are executed sequentially in priority order (higher first). Each
	* handler may return `{ message }` to replace the message passed to the next
	* handler.
	*/
	function runToolResultPersist(event, ctx) {
		const hooks = getHooksForName(registry, "tool_result_persist");
		if (hooks.length === 0) return;
		let current = event.message;
		for (const hook of hooks) try {
			const out = hook.handler({
				...event,
				message: current
			}, ctx);
			if (out && typeof out.then === "function") {
				const msg = `[hooks] tool_result_persist handler from ${hook.pluginId} returned a Promise; this hook is synchronous and the result was ignored.`;
				if (catchErrors) {
					logger?.warn?.(msg);
					continue;
				}
				throw new Error(msg);
			}
			const next = out?.message;
			if (next) current = next;
		} catch (err) {
			const msg = `[hooks] tool_result_persist handler from ${hook.pluginId} failed: ${String(err)}`;
			if (catchErrors) logger?.error(msg);
			else throw new Error(msg, { cause: err });
		}
		return { message: current };
	}
	/**
	* Run session_start hook.
	* Runs in parallel (fire-and-forget).
	*/
	async function runSessionStart(event, ctx) {
		return runVoidHook("session_start", event, ctx);
	}
	/**
	* Run session_end hook.
	* Runs in parallel (fire-and-forget).
	*/
	async function runSessionEnd(event, ctx) {
		return runVoidHook("session_end", event, ctx);
	}
	/**
	* Run gateway_start hook.
	* Runs in parallel (fire-and-forget).
	*/
	async function runGatewayStart(event, ctx) {
		return runVoidHook("gateway_start", event, ctx);
	}
	/**
	* Run gateway_stop hook.
	* Runs in parallel (fire-and-forget).
	*/
	async function runGatewayStop(event, ctx) {
		return runVoidHook("gateway_stop", event, ctx);
	}
	/**
	* Check if any hooks are registered for a given hook name.
	*/
	function hasHooks(hookName) {
		return registry.typedHooks.some((h) => h.hookName === hookName);
	}
	/**
	* Get count of registered hooks for a given hook name.
	*/
	function getHookCount(hookName) {
		return registry.typedHooks.filter((h) => h.hookName === hookName).length;
	}
	return {
		runBeforeKoiStart,
		runKoiEnd,
		runBeforeCompaction,
		runAfterCompaction,
		runMessageReceived,
		runMessageSending,
		runMessageSent,
		runBeforeToolCall,
		runAfterToolCall,
		runToolResultPersist,
		runSessionStart,
		runSessionEnd,
		runGatewayStart,
		runGatewayStop,
		hasHooks,
		getHookCount
	};
}

//#endregion
//#region src/plugins/hook-runner-global.ts
const log$1 = createSubsystemLogger("plugins");
let globalHookRunner = null;
let globalRegistry = null;
/**
* Initialize the global hook runner with a plugin registry.
* Called once when plugins are loaded during gateway startup.
*/
function initializeGlobalHookRunner(registry) {
	globalRegistry = registry;
	globalHookRunner = createHookRunner(registry, {
		logger: {
			debug: (msg) => log$1.debug(msg),
			warn: (msg) => log$1.warn(msg),
			error: (msg) => log$1.error(msg)
		},
		catchErrors: true
	});
	const hookCount = registry.hooks.length;
	if (hookCount > 0) log$1.info(`hook runner initialized with ${hookCount} registered hooks`);
}
/**
* Get the global hook runner.
* Returns null if plugins haven't been loaded yet.
*/
function getGlobalHookRunner() {
	return globalHookRunner;
}

//#endregion
//#region src/koi/tools/common.ts
function createActionGate(actions) {
	return (key, defaultValue = true) => {
		const value = actions?.[key];
		if (value === void 0) return defaultValue;
		return value !== false;
	};
}
function readStringParam(params, key, options = {}) {
	const { required = false, trim = true, label = key, allowEmpty = false } = options;
	const raw = params[key];
	if (typeof raw !== "string") {
		if (required) throw new Error(`${label} required`);
		return;
	}
	const value = trim ? raw.trim() : raw;
	if (!value && !allowEmpty) {
		if (required) throw new Error(`${label} required`);
		return;
	}
	return value;
}
function readStringOrNumberParam(params, key, options = {}) {
	const { required = false, label = key } = options;
	const raw = params[key];
	if (typeof raw === "number" && Number.isFinite(raw)) return String(raw);
	if (typeof raw === "string") {
		const value = raw.trim();
		if (value) return value;
	}
	if (required) throw new Error(`${label} required`);
}
function readNumberParam(params, key, options = {}) {
	const { required = false, label = key, integer = false } = options;
	const raw = params[key];
	let value;
	if (typeof raw === "number" && Number.isFinite(raw)) value = raw;
	else if (typeof raw === "string") {
		const trimmed = raw.trim();
		if (trimmed) {
			const parsed = Number.parseFloat(trimmed);
			if (Number.isFinite(parsed)) value = parsed;
		}
	}
	if (value === void 0) {
		if (required) throw new Error(`${label} required`);
		return;
	}
	return integer ? Math.trunc(value) : value;
}
function readStringArrayParam(params, key, options = {}) {
	const { required = false, label = key } = options;
	const raw = params[key];
	if (Array.isArray(raw)) {
		const values = raw.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean);
		if (values.length === 0) {
			if (required) throw new Error(`${label} required`);
			return;
		}
		return values;
	}
	if (typeof raw === "string") {
		const value = raw.trim();
		if (!value) {
			if (required) throw new Error(`${label} required`);
			return;
		}
		return [value];
	}
	if (required) throw new Error(`${label} required`);
}
function readReactionParams(params, options) {
	const emojiKey = options.emojiKey ?? "emoji";
	const removeKey = options.removeKey ?? "remove";
	const remove = typeof params[removeKey] === "boolean" ? params[removeKey] : false;
	const emoji = readStringParam(params, emojiKey, {
		required: true,
		allowEmpty: true
	});
	if (remove && !emoji) throw new Error(options.removeErrorMessage);
	return {
		emoji,
		remove,
		isEmpty: !emoji
	};
}
function jsonResult(payload) {
	return {
		content: [{
			type: "text",
			text: JSON.stringify(payload, null, 2)
		}],
		details: payload
	};
}
async function imageResult(params) {
	return await sanitizeToolResultImages({
		content: [{
			type: "text",
			text: params.extraText ?? `MEDIA:${params.path}`
		}, {
			type: "image",
			data: params.base64,
			mimeType: params.mimeType
		}],
		details: {
			path: params.path,
			...params.details
		}
	}, params.label);
}
async function imageResultFromFile(params) {
	const buf = await fs.readFile(params.path);
	const mimeType = await detectMime({ buffer: buf.slice(0, 256) }) ?? "image/png";
	return await imageResult({
		label: params.label,
		path: params.path,
		base64: buf.toString("base64"),
		mimeType,
		extraText: params.extraText,
		details: params.details
	});
}

//#endregion
//#region src/koi/pi-tools.before-tool-call.ts
const log = createSubsystemLogger("kois/tools");
function isPlainObject$1(value) {
	return typeof value === "object" && value !== null && !Array.isArray(value);
}
async function runBeforeToolCallHook(args) {
	const hookRunner = getGlobalHookRunner();
	if (!hookRunner?.hasHooks("before_tool_call")) return {
		blocked: false,
		params: args.params
	};
	const toolName = normalizeToolName(args.toolName || "tool");
	const params = args.params;
	try {
		const normalizedParams = isPlainObject$1(params) ? params : {};
		const hookResult = await hookRunner.runBeforeToolCall({
			toolName,
			params: normalizedParams
		}, {
			toolName,
			koiId: args.ctx?.koiId,
			sessionKey: args.ctx?.sessionKey
		});
		if (hookResult?.block) return {
			blocked: true,
			reason: hookResult.blockReason || "Tool call blocked by plugin hook"
		};
		if (hookResult?.params && isPlainObject$1(hookResult.params)) {
			if (isPlainObject$1(params)) return {
				blocked: false,
				params: {
					...params,
					...hookResult.params
				}
			};
			return {
				blocked: false,
				params: hookResult.params
			};
		}
	} catch (err) {
		const toolCallId = args.toolCallId ? ` toolCallId=${args.toolCallId}` : "";
		log.warn(`before_tool_call hook failed: tool=${toolName}${toolCallId} error=${String(err)}`);
	}
	return {
		blocked: false,
		params
	};
}
function wrapToolWithBeforeToolCallHook(tool, ctx) {
	const execute = tool.execute;
	if (!execute) return tool;
	const toolName = tool.name || "tool";
	return {
		...tool,
		execute: async (toolCallId, params, signal, onUpdate) => {
			const outcome = await runBeforeToolCallHook({
				toolName,
				params,
				toolCallId,
				ctx
			});
			if (outcome.blocked) throw new Error(outcome.reason);
			return await execute(toolCallId, outcome.params, signal, onUpdate);
		}
	};
}

//#endregion
//#region src/koi/pi-tool-definition-adapter.ts
var pi_tool_definition_adapter_exports = /* @__PURE__ */ __exportAll({
	toClientToolDefinitions: () => toClientToolDefinitions,
	toToolDefinitions: () => toToolDefinitions
});
function isPlainObject(value) {
	return typeof value === "object" && value !== null && !Array.isArray(value);
}
function isAbortSignal(value) {
	return typeof value === "object" && value !== null && "aborted" in value;
}
function isLegacyToolExecuteArgs(args) {
	const third = args[2];
	const fourth = args[3];
	return isAbortSignal(third) || typeof fourth === "function";
}
function describeToolExecutionError(err) {
	if (err instanceof Error) return {
		message: err.message?.trim() ? err.message : String(err),
		stack: err.stack
	};
	return { message: String(err) };
}
function splitToolExecuteArgs(args) {
	if (isLegacyToolExecuteArgs(args)) {
		const [toolCallId, params, signal, onUpdate] = args;
		return {
			toolCallId,
			params,
			onUpdate,
			signal
		};
	}
	const [toolCallId, params, onUpdate, _ctx, signal] = args;
	return {
		toolCallId,
		params,
		onUpdate,
		signal
	};
}
/**
* Inject a `_description` property into a tool's JSON Schema parameters.
* The LLM fills this in with a brief human-readable explanation of what
* it's doing and why, which gets displayed in the UI instead of bare
* tool names. Stripped from args before actual tool execution.
*/
function injectDescriptionParam(parameters) {
	if (!parameters || typeof parameters !== "object") return parameters;
	const schema = { ...parameters };
	const props = schema.properties && typeof schema.properties === "object" ? { ...schema.properties } : {};
	props._description = {
		type: "string",
		description: "Very brief (5-10 word) description of what you are doing with this tool call and why, shown to the user as a status message. Write in present tense, e.g. 'Checking git status for uncommitted changes' or 'Reading the auth config'. Do NOT repeat the tool name."
	};
	schema.properties = props;
	if (!("type" in schema)) schema.type = "object";
	return schema;
}
/**
* Strip `_description` from tool call params before execution.
* Returns the description (if any) and cleaned params.
*/
function extractDescription(params) {
	if (!params || typeof params !== "object" || Array.isArray(params)) return { cleanParams: params };
	const record = params;
	const description = typeof record._description === "string" && record._description.trim() ? record._description.trim() : void 0;
	if (!description) return { cleanParams: params };
	const { _description, ...rest } = record;
	return {
		description,
		cleanParams: rest
	};
}
function toToolDefinitions(tools) {
	return tools.map((tool) => {
		const name = tool.name || "tool";
		const normalizedName = normalizeToolName(name);
		return {
			name,
			label: tool.label ?? name,
			description: tool.description ?? "",
			parameters: injectDescriptionParam(tool.parameters),
			execute: async (...args) => {
				const { toolCallId, params, onUpdate, signal } = splitToolExecuteArgs(args);
				const { cleanParams } = extractDescription(params);
				try {
					return await tool.execute(toolCallId, cleanParams, signal, onUpdate);
				} catch (err) {
					if (signal?.aborted) throw err;
					if ((err && typeof err === "object" && "name" in err ? String(err.name) : "") === "AbortError") throw err;
					const described = describeToolExecutionError(err);
					if (described.stack && described.stack !== described.message) logDebug(`tools: ${normalizedName} failed stack:\n${described.stack}`);
					logError(`[tools] ${normalizedName} failed: ${described.message}`);
					return jsonResult({
						status: "error",
						tool: normalizedName,
						error: described.message
					});
				}
			}
		};
	});
}
function toClientToolDefinitions(tools, onClientToolCall, hookContext) {
	return tools.map((tool) => {
		const func = tool.function;
		return {
			name: func.name,
			label: func.name,
			description: func.description ?? "",
			parameters: func.parameters,
			execute: async (...args) => {
				const { toolCallId, params } = splitToolExecuteArgs(args);
				const outcome = await runBeforeToolCallHook({
					toolName: func.name,
					params,
					toolCallId,
					ctx: hookContext
				});
				if (outcome.blocked) throw new Error(outcome.reason);
				const adjustedParams = outcome.params;
				const paramsRecord = isPlainObject(adjustedParams) ? adjustedParams : {};
				if (onClientToolCall) onClientToolCall(func.name, paramsRecord);
				return jsonResult({
					status: "pending",
					tool: func.name,
					message: "Tool execution delegated to client"
				});
			}
		};
	});
}

//#endregion
export { createActionGate as a, jsonResult as c, readStringArrayParam as d, readStringOrNumberParam as f, initializeGlobalHookRunner as h, wrapToolWithBeforeToolCallHook as i, readNumberParam as l, getGlobalHookRunner as m, toClientToolDefinitions as n, imageResult as o, readStringParam as p, toToolDefinitions as r, imageResultFromFile as s, pi_tool_definition_adapter_exports as t, readReactionParams as u };