import { DEFAULT_AGENT_ID, init_agent_scope, listAgentEntries, normalizeAgentId, resolveAgentDir, resolveAgentProfileDir, resolveAgentWorkspaceDir, resolveDefaultAgentId, resolveUserPath, validateAgentIdForNewAgent } from "../agent/agent-scope.js";
import { WORKSPACE_FILES, init_paths } from "../config/paths.js";
import { resolveEffectiveAgentProfile } from "../config/agent-profile.js";
import { AGENT_PROFILE_MARKDOWN_SYSTEM_FILES } from "../agent/context/workspace.js";
import { isPathUnderWorkspace, resolveWorkspaceSafePath } from "./workspace-editor-path.js";
import { resolveEffectiveTypedModels } from "../config/agent-typed-models.js";
import { applyAgentConfig, findAgentEntryIndex, pruneAgentConfig, removeAgentDirsFromDisk } from "../commands/agents.config.js";
import { GATEWAY_BUILTIN_TOOL_IDS } from "./agent-builtin-tools.js";
import { seedAgentProfileMarkdownFiles } from "../agent/context/workspace-seed.js";
import { normalizePatchTypedModels } from "./hono/lib/agent-model.js";
import { mkdir, readFile, realpath, stat, unlink, writeFile } from "node:fs/promises";
import { join, resolve } from "node:path";
//#region src/gateway/agents-admin.ts
/**
* Gateway REST helpers for multi-agent management.
*/
init_agent_scope();
init_paths();
const EDITABLE_PROFILE_MARKDOWN_NAMES = new Set([...AGENT_PROFILE_MARKDOWN_SYSTEM_FILES, WORKSPACE_FILES.BOOTSTRAP]);
function collectAgentIdsForList(cfg) {
	const entries = listAgentEntries(cfg).filter((e) => e.enabled !== false);
	const defaultId = resolveDefaultAgentId(cfg);
	if (entries.length === 0) return [defaultId];
	const ids = /* @__PURE__ */ new Set();
	for (const e of entries) ids.add(normalizeAgentId(e.id));
	ids.add(defaultId);
	return [...ids];
}
/** Extract `**Avatar:**` value from profile IDENTITY.md (same line shape as the gateway console parser). */
function extractAvatarFromIdentityMarkdown(content) {
	for (const line of content.split("\n")) {
		const match = line.match(/^[-*]\s+\*\*Avatar:\*\*\s*(.*)/i);
		if (match) {
			const v = match[1]?.trim() ?? "";
			return v.length > 0 ? v : void 0;
		}
	}
}
async function listGatewayAgents(cfg) {
	const defaultId = resolveDefaultAgentId(cfg);
	const agents = [];
	const defaultsSkills = cfg.agents?.defaults?.skills;
	const defaultsDisable = cfg.agents?.defaults?.tools?.disable ?? [];
	const defaultsTypedModels = cfg.agents?.defaults?.models ?? [];
	for (const id of collectAgentIdsForList(cfg)) {
		const profile = resolveEffectiveAgentProfile(cfg, id);
		const entry = listAgentEntries(cfg).find((e) => normalizeAgentId(e.id) === id);
		const model = profile.primaryModelRef || profile.fallbacks.length > 0 ? {
			...profile.primaryModelRef ? { primary: profile.primaryModelRef } : {},
			...profile.fallbacks.length > 0 ? { fallbacks: profile.fallbacks } : {}
		} : void 0;
		const entrySkills = entry?.skills;
		const entryDisable = entry?.tools?.disable ?? [];
		const entryTypedModels = entry?.models;
		const effectiveTypedModels = [...resolveEffectiveTypedModels(cfg, id).values()];
		let avatar;
		try {
			avatar = extractAvatarFromIdentityMarkdown(await readFile(join(resolveAgentProfileDir(cfg, id), WORKSPACE_FILES.IDENTITY), "utf-8"));
		} catch {}
		agents.push({
			id,
			...entry?.name?.trim() ? { name: entry.name.trim() } : {},
			...entry?.description?.trim() ? { description: entry.description.trim() } : {},
			...avatar ? { avatar } : {},
			workspace: profile.resolvedWorkspacePath,
			profileDir: resolveAgentProfileDir(cfg, id),
			...model ? { model } : {},
			typedModels: {
				defaults: [...defaultsTypedModels],
				...entryTypedModels !== void 0 ? { entry: [...entryTypedModels] } : {},
				effective: effectiveTypedModels
			},
			isDefault: id === defaultId,
			skills: {
				defaults: defaultsSkills ? [...defaultsSkills] : [],
				...entrySkills !== void 0 ? { entry: [...entrySkills] } : {},
				...profile.skillsAllowlist !== void 0 ? { effectiveAllowlist: [...profile.skillsAllowlist] } : {}
			},
			tools: {
				defaultsDisable: [...defaultsDisable],
				entryDisable: [...entryDisable],
				effectiveDisable: [...profile.tools.disable].sort((a, b) => a.localeCompare(b))
			}
		});
	}
	agents.sort((a, b) => a.id.localeCompare(b.id));
	return {
		defaultId,
		agents,
		builtinToolIds: [...GATEWAY_BUILTIN_TOOL_IDS]
	};
}
function requireNonMain(id) {
	if (normalizeAgentId(id) === "main") return {
		ok: false,
		error: `"${DEFAULT_AGENT_ID}" is reserved`,
		status: 400
	};
	return null;
}
function prepareCreateAgent(cfg, body) {
	const name = body.name?.trim() ?? "";
	if (!name) return {
		ok: false,
		error: "name is required",
		status: 400
	};
	const workspace = body.workspace?.trim() ?? "";
	if (!workspace) return {
		ok: false,
		error: "workspace is required",
		status: 400
	};
	const idRes = validateAgentIdForNewAgent(body.id, name);
	if (idRes.ok === false) return {
		ok: false,
		error: idRes.error,
		status: 400
	};
	const agentId = idRes.agentId;
	if (findAgentEntryIndex(listAgentEntries(cfg), agentId) >= 0) return {
		ok: false,
		error: `agent "${agentId}" already exists`,
		status: 409
	};
	if (body.profileFiles !== void 0) {
		if (typeof body.profileFiles !== "object" || body.profileFiles === null || Array.isArray(body.profileFiles)) return {
			ok: false,
			error: "profileFiles must be an object",
			status: 400
		};
		for (const [name, content] of Object.entries(body.profileFiles)) {
			const bad = assertAllowedFile(name);
			if (bad) return bad;
			if (typeof content !== "string") return {
				ok: false,
				error: `profileFiles["${name}"] must be a string`,
				status: 400
			};
		}
	}
	const wsAbs = resolveUserPath(workspace);
	let next = applyAgentConfig(cfg, {
		agentId,
		name,
		workspace: wsAbs,
		...body.model?.trim() ? { model: body.model.trim() } : {},
		...body.agentDir?.trim() ? { agentDir: body.agentDir.trim() } : {},
		...body.description?.trim() ? { description: body.description.trim() } : {}
	});
	if (body.toolsDisable !== void 0) {
		const list = [...listAgentEntries(next)];
		const idx = findAgentEntryIndex(list, agentId);
		if (idx >= 0) {
			const entry = { ...list[idx] };
			const disable = body.toolsDisable.map((s) => String(s).trim()).filter(Boolean);
			entry.tools = {
				...entry.tools,
				disable
			};
			list[idx] = entry;
			next = {
				...next,
				agents: {
					...next.agents,
					list
				}
			};
		}
	}
	return {
		ok: true,
		data: {
			nextConfig: next,
			agentId,
			workspace: wsAbs
		}
	};
}
function prepareCreateAgentsBatch(cfg, bodies) {
	if (!Array.isArray(bodies) || bodies.length === 0) return {
		ok: false,
		error: "agents must be a non-empty array",
		status: 400
	};
	let next = cfg;
	const created = [];
	for (const body of bodies) {
		const prep = prepareCreateAgent(next, body);
		if (prep.ok === false) return prep;
		next = prep.data.nextConfig;
		created.push({
			agentId: prep.data.agentId,
			...body.profileFiles !== void 0 ? { profileFiles: body.profileFiles } : {}
		});
	}
	return {
		ok: true,
		data: {
			nextConfig: next,
			created
		}
	};
}
async function finalizeCreateAgentDirs(cfg, agentId, opts) {
	const wsPath = resolveAgentWorkspaceDir(cfg, agentId);
	const profilePath = resolveAgentProfileDir(cfg, agentId);
	const adPath = resolveAgentDir(cfg, agentId);
	await mkdir(wsPath, { recursive: true });
	await mkdir(profilePath, { recursive: true });
	await mkdir(adPath, { recursive: true });
	const id = normalizeAgentId(agentId);
	seedAgentProfileMarkdownFiles(profilePath, wsPath, { displayName: listAgentEntries(cfg).find((e) => normalizeAgentId(e.id) === id)?.name?.trim() || id });
	const profileFiles = opts?.profileFiles;
	if (profileFiles && Object.keys(profileFiles).length > 0) for (const [name, content] of Object.entries(profileFiles)) {
		const written = await writeAgentProfileFile(cfg, agentId, name, content);
		if (written.ok === false) return written;
	}
	return {
		ok: true,
		data: void 0
	};
}
function prepareUpdateAgent(cfg, agentIdRaw, body) {
	const agentId = normalizeAgentId(agentIdRaw);
	let list = [...listAgentEntries(cfg)];
	let idx = findAgentEntryIndex(list, agentId);
	if (idx < 0 && agentId === resolveDefaultAgentId(cfg)) {
		list = [...list, {
			id: agentId,
			enabled: true
		}];
		idx = list.length - 1;
	}
	if (idx < 0) return {
		ok: false,
		error: `agent "${agentId}" not found`,
		status: 404
	};
	const entry = { ...list[idx] };
	if (body.name !== void 0) {
		const n = body.name.trim();
		if (n) entry.name = n;
	}
	if (body.description !== void 0) if (body.description === null || String(body.description).trim() === "") delete entry.description;
	else entry.description = String(body.description).trim();
	if (body.workspace !== void 0) {
		const w = body.workspace.trim();
		if (w) entry.workspace = resolveUserPath(w);
	}
	if (body.model !== void 0) if (body.model === null) delete entry.model;
	else {
		const trimmed = String(body.model).trim();
		if (!trimmed) return {
			ok: false,
			error: "model must be a non-empty string or null",
			status: 400
		};
		entry.model = { primary: trimmed };
	}
	if (body.agentDir !== void 0) if (body.agentDir === null || String(body.agentDir).trim() === "") delete entry.agentDir;
	else entry.agentDir = String(body.agentDir).trim();
	if (body.skills !== void 0) if (body.skills === null) delete entry.skills;
	else {
		const next = body.skills.map((s) => String(s).trim()).filter(Boolean);
		if (next.length === 0) entry.skills = [];
		else entry.skills = next;
	}
	if (body.toolsDisable !== void 0) if (body.toolsDisable === null) {
		if (entry.tools) {
			delete entry.tools.disable;
			if (Object.keys(entry.tools).length === 0) delete entry.tools;
		}
	} else {
		const next = body.toolsDisable.map((s) => String(s).trim()).filter(Boolean);
		entry.tools = {
			...entry.tools,
			disable: next
		};
	}
	if (body.models !== void 0) if (body.models === null) delete entry.models;
	else {
		const normalized = normalizePatchTypedModels(body.models);
		if (normalized === null || normalized === void 0) delete entry.models;
		else entry.models = normalized;
	}
	list[idx] = entry;
	let next = {
		...cfg,
		agents: {
			...cfg.agents,
			list
		}
	};
	if (body.setDefault === true) next = {
		...next,
		agents: {
			...next.agents,
			default: agentId
		}
	};
	return {
		ok: true,
		data: { nextConfig: next }
	};
}
function prepareDeleteAgent(cfg, agentIdRaw) {
	const agentId = normalizeAgentId(agentIdRaw);
	const reserved = requireNonMain(agentId);
	if (reserved) return reserved;
	if (findAgentEntryIndex(listAgentEntries(cfg), agentId) < 0) return {
		ok: false,
		error: `agent "${agentId}" not found`,
		status: 404
	};
	const { config: pruned } = pruneAgentConfig(cfg, agentId);
	return {
		ok: true,
		data: {
			nextConfig: pruned,
			agentId
		}
	};
}
async function runAfterDeletePurge(cfg, agentId) {
	await removeAgentDirsFromDisk(cfg, agentId);
}
async function profileMarkdownRootReal(cfg, agentId) {
	const dir = resolveAgentProfileDir(cfg, agentId);
	await mkdir(dir, { recursive: true });
	try {
		return await realpath(dir);
	} catch {
		return resolve(dir);
	}
}
function assertAllowedFile(name) {
	if (!name || name.includes("/") || name.includes("\\") || !EDITABLE_PROFILE_MARKDOWN_NAMES.has(name)) return {
		ok: false,
		error: `unsupported file "${name}"`,
		status: 400
	};
	return null;
}
async function listAgentProfileFiles(cfg, agentId) {
	const id = normalizeAgentId(agentId);
	if (collectAgentIdsForList(cfg).every((x) => x !== id)) return {
		ok: false,
		error: `agent "${id}" not found`,
		status: 404
	};
	const root = await profileMarkdownRootReal(cfg, id);
	const names = [...EDITABLE_PROFILE_MARKDOWN_NAMES];
	const files = [];
	for (const name of names.sort((a, b) => a.localeCompare(b))) {
		const abs = resolveWorkspaceSafePath(root, name);
		if (!abs) continue;
		try {
			const st = await stat(abs);
			if (!st.isFile()) continue;
			files.push({
				name,
				missing: false,
				size: st.size,
				updatedAtMs: st.mtimeMs
			});
		} catch {
			files.push({
				name,
				missing: true
			});
		}
	}
	files.sort((a, b) => a.name.localeCompare(b.name));
	return {
		ok: true,
		data: {
			agentId: id,
			profileDir: root,
			files
		}
	};
}
async function readAgentProfileFile(cfg, agentId, name) {
	const bad = assertAllowedFile(name);
	if (bad) return bad;
	const id = normalizeAgentId(agentId);
	if (collectAgentIdsForList(cfg).every((x) => x !== id)) return {
		ok: false,
		error: `agent "${id}" not found`,
		status: 404
	};
	const abs = resolveWorkspaceSafePath(await profileMarkdownRootReal(cfg, id), name);
	if (!abs) return {
		ok: false,
		error: "invalid path",
		status: 400
	};
	try {
		return {
			ok: true,
			data: {
				agentId: id,
				content: await readFile(abs, "utf-8"),
				path: abs
			}
		};
	} catch {
		return {
			ok: false,
			error: "file not found",
			status: 404
		};
	}
}
async function writeAgentProfileFile(cfg, agentId, name, content) {
	const bad = assertAllowedFile(name);
	if (bad) return bad;
	const id = normalizeAgentId(agentId);
	if (collectAgentIdsForList(cfg).every((x) => x !== id)) return {
		ok: false,
		error: `agent "${id}" not found`,
		status: 404
	};
	const abs = resolveWorkspaceSafePath(await profileMarkdownRootReal(cfg, id), name);
	if (!abs) return {
		ok: false,
		error: "invalid path",
		status: 400
	};
	if (!isPathUnderWorkspace(await profileMarkdownRootReal(cfg, id), abs)) return {
		ok: false,
		error: "path escapes profile markdown root",
		status: 400
	};
	await writeFile(abs, content, "utf-8");
	return {
		ok: true,
		data: {
			agentId: id,
			path: abs
		}
	};
}
const AGENT_AVATAR_MAX_BYTES = 512 * 1024;
const AGENT_AVATAR_BASENAME = "agent-avatar";
const AGENT_AVATAR_EXTENSIONS = [
	".png",
	".jpg",
	".jpeg",
	".webp"
];
function agentAvatarFilenames() {
	return AGENT_AVATAR_EXTENSIONS.map((ext) => `${AGENT_AVATAR_BASENAME}${ext}`);
}
function mimeToExt(mime) {
	const m = mime.toLowerCase().trim();
	if (m === "image/png") return ".png";
	if (m === "image/jpeg" || m === "image/jpg") return ".jpg";
	if (m === "image/webp") return ".webp";
	return null;
}
function detectImageMimeFromBytes(buf) {
	if (buf.length >= 8 && buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
	if (buf.length >= 3 && buf[0] === 255 && buf[1] === 216 && buf[2] === 255) return "image/jpeg";
	if (buf.length >= 12 && buf[0] === 82 && buf[1] === 73 && buf[2] === 70 && buf[3] === 70 && buf[8] === 87 && buf[9] === 69 && buf[10] === 66 && buf[11] === 80) return "image/webp";
	return null;
}
function assertAgentExistsForAvatar(cfg, id) {
	if (collectAgentIdsForList(cfg).every((x) => x !== id)) return {
		ok: false,
		error: `agent "${id}" not found`,
		status: 404
	};
	return null;
}
async function readAgentAvatarFile(cfg, agentId) {
	const missingAgent = assertAgentExistsForAvatar(cfg, agentId);
	if (missingAgent) return missingAgent;
	const id = normalizeAgentId(agentId);
	const root = await profileMarkdownRootReal(cfg, id);
	for (const name of agentAvatarFilenames()) {
		const abs = resolveWorkspaceSafePath(root, name);
		if (!abs) continue;
		try {
			const st = await stat(abs);
			if (!st.isFile() || st.size <= 0 || st.size > AGENT_AVATAR_MAX_BYTES) continue;
			const buffer = await readFile(abs);
			const detected = detectImageMimeFromBytes(buffer);
			if (!detected) continue;
			return {
				ok: true,
				data: {
					agentId: id,
					buffer,
					contentType: detected,
					path: abs
				}
			};
		} catch {}
	}
	return {
		ok: false,
		error: "avatar not found",
		status: 404
	};
}
async function writeAgentAvatarFromBase64(cfg, agentId, base64, mimeType) {
	const missingAgent = assertAgentExistsForAvatar(cfg, agentId);
	if (missingAgent) return missingAgent;
	const id = normalizeAgentId(agentId);
	const ext = mimeToExt(mimeType);
	if (!ext) return {
		ok: false,
		error: "unsupported mimeType (use image/png, image/jpeg, or image/webp)",
		status: 400
	};
	let raw;
	try {
		raw = Buffer.from(base64, "base64");
	} catch {
		return {
			ok: false,
			error: "invalid base64",
			status: 400
		};
	}
	if (raw.length === 0 || raw.length > AGENT_AVATAR_MAX_BYTES) return {
		ok: false,
		error: `avatar must be non-empty and at most ${AGENT_AVATAR_MAX_BYTES} bytes`,
		status: 400
	};
	const detected = detectImageMimeFromBytes(raw);
	if (!detected || !extMatchesDetectedMime(ext, detected)) return {
		ok: false,
		error: "file content does not match declared image type",
		status: 400
	};
	const root = await profileMarkdownRootReal(cfg, id);
	const rootReal = await profileMarkdownRootReal(cfg, id);
	const targetName = `${AGENT_AVATAR_BASENAME}${ext}`;
	const abs = resolveWorkspaceSafePath(root, targetName);
	if (!abs || !isPathUnderWorkspace(rootReal, abs)) return {
		ok: false,
		error: "invalid path",
		status: 400
	};
	for (const name of agentAvatarFilenames()) {
		if (name === targetName) continue;
		const other = resolveWorkspaceSafePath(root, name);
		if (other && isPathUnderWorkspace(rootReal, other)) try {
			await unlink(other);
		} catch {}
	}
	await writeFile(abs, raw);
	return {
		ok: true,
		data: {
			agentId: id,
			path: abs
		}
	};
}
function mimeToExtToMime(ext) {
	if (ext === ".png") return "image/png";
	if (ext === ".webp") return "image/webp";
	return "image/jpeg";
}
function extMatchesDetectedMime(ext, detected) {
	return detected === mimeToExtToMime(ext);
}
/** Remove any `agent-avatar.*` in the agent profile markdown root. Idempotent: ok even when no file existed. */
async function deleteAgentAvatarFile(cfg, agentId) {
	const missingAgent = assertAgentExistsForAvatar(cfg, agentId);
	if (missingAgent) return missingAgent;
	const id = normalizeAgentId(agentId);
	const root = await profileMarkdownRootReal(cfg, id);
	const rootReal = await profileMarkdownRootReal(cfg, id);
	for (const name of agentAvatarFilenames()) {
		const abs = resolveWorkspaceSafePath(root, name);
		if (!abs || !isPathUnderWorkspace(rootReal, abs)) continue;
		try {
			await unlink(abs);
		} catch {}
	}
	return {
		ok: true,
		data: { agentId: id }
	};
}
//#endregion
export { deleteAgentAvatarFile, extractAvatarFromIdentityMarkdown, finalizeCreateAgentDirs, listAgentProfileFiles, listGatewayAgents, prepareCreateAgent, prepareCreateAgentsBatch, prepareDeleteAgent, prepareUpdateAgent, readAgentAvatarFile, readAgentProfileFile, runAfterDeletePurge, writeAgentAvatarFromBase64, writeAgentProfileFile };

//# sourceMappingURL=agents-admin.js.map