import { h as __exportAll } from "./pi-embedded-utils-CPkknmhn.js";
import { R as resolveUserPath } from "./subsystem-CBhYrL33.js";
import { $ as DEFAULT_MODEL, E as resolveEnvApiKey, S as getApiKeyForModel, U as ensureAuthProfileStore, V as listProfilesForProvider, et as DEFAULT_PROVIDER, u as resolveConfiguredModelRef, w as requireApiKey } from "./model-selection-C249ZOnu.js";
import { t as wrapToolWithTracking } from "./tool-tracking-D1HWsfb6.js";
import { n as loadWebMedia, t as getDefaultLocalRoots } from "./media-IKchB7_u.js";
import { n as discoverModels, t as discoverAuthStorage } from "./pi-model-discovery-DGABDrxC.js";
import { a as minimaxUnderstandImage, i as resolveProviderVisionModelFromConfig, n as coerceImageModelConfig, r as decodeDataUrl, s as ensureOpenClawModelsJson, t as coerceImageAssistantText } from "./image-tool.helpers-D7iUmlqH.js";
import { s as normalizeWorkspaceDir, t as runWithImageModelFallback } from "./model-fallback-BWc7ddLG.js";
import path from "node:path";
import { Type } from "@sinclair/typebox";
import { complete } from "@mariozechner/pi-ai";

//#region src/agents/tools/media/image-tool.ts
var image_tool_exports = /* @__PURE__ */ __exportAll({
	__testing: () => __testing,
	createImageTool: () => createImageTool,
	resolveImageModelConfigForTool: () => resolveImageModelConfigForTool
});
const DEFAULT_PROMPT = "Describe the image.";
const ANTHROPIC_IMAGE_PRIMARY = "anthropic/claude-opus-4-6";
const ANTHROPIC_IMAGE_FALLBACK = "anthropic/claude-opus-4-5";
const DEFAULT_MAX_IMAGES = 20;
const __testing = {
	decodeDataUrl,
	coerceImageAssistantText,
	resolveImageToolMaxTokens
};
function resolveImageToolMaxTokens(modelMaxTokens, requestedMaxTokens = 4096) {
	if (typeof modelMaxTokens !== "number" || !Number.isFinite(modelMaxTokens) || modelMaxTokens <= 0) return requestedMaxTokens;
	return Math.min(requestedMaxTokens, modelMaxTokens);
}
function resolveDefaultModelRef(cfg) {
	if (cfg) {
		const resolved = resolveConfiguredModelRef({
			cfg,
			defaultProvider: DEFAULT_PROVIDER,
			defaultModel: DEFAULT_MODEL
		});
		return {
			provider: resolved.provider,
			model: resolved.model
		};
	}
	return {
		provider: DEFAULT_PROVIDER,
		model: DEFAULT_MODEL
	};
}
function hasAuthForProvider(params) {
	if (resolveEnvApiKey(params.provider)?.apiKey) return true;
	return listProfilesForProvider(ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false }), params.provider).length > 0;
}
/**
* Resolve the effective image model config for the `image` tool.
*
* - Prefer explicit config (`agents.defaults.imageModel`).
* - Otherwise, try to "pair" the primary model with an image-capable model:
*   - same provider (best effort)
*   - fall back to OpenAI/Anthropic when available
*/
function resolveImageModelConfigForTool(params) {
	const explicit = coerceImageModelConfig(params.cfg);
	if (explicit.primary?.trim() || (explicit.fallbacks?.length ?? 0) > 0) return explicit;
	const primary = resolveDefaultModelRef(params.cfg);
	const openaiOk = hasAuthForProvider({
		provider: "openai",
		agentDir: params.agentDir
	});
	const anthropicOk = hasAuthForProvider({
		provider: "anthropic",
		agentDir: params.agentDir
	});
	const fallbacks = [];
	const addFallback = (modelRef) => {
		const ref = (modelRef ?? "").trim();
		if (!ref) return;
		if (fallbacks.includes(ref)) return;
		fallbacks.push(ref);
	};
	const providerVisionFromConfig = resolveProviderVisionModelFromConfig({
		cfg: params.cfg,
		provider: primary.provider
	});
	const providerOk = hasAuthForProvider({
		provider: primary.provider,
		agentDir: params.agentDir
	});
	let preferred = null;
	if (primary.provider === "minimax" && providerOk) preferred = "minimax/MiniMax-VL-01";
	else if (providerOk && providerVisionFromConfig) preferred = providerVisionFromConfig;
	else if (primary.provider === "zai" && providerOk) preferred = "zai/glm-4.6v";
	else if (primary.provider === "openai" && openaiOk) preferred = "openai/gpt-5-mini";
	else if (primary.provider === "anthropic" && anthropicOk) preferred = ANTHROPIC_IMAGE_PRIMARY;
	if (preferred?.trim()) {
		if (openaiOk) addFallback("openai/gpt-5-mini");
		if (anthropicOk) addFallback(ANTHROPIC_IMAGE_FALLBACK);
		const pruned = fallbacks.filter((ref) => ref !== preferred);
		return {
			primary: preferred,
			...pruned.length > 0 ? { fallbacks: pruned } : {}
		};
	}
	if (openaiOk) {
		if (anthropicOk) addFallback(ANTHROPIC_IMAGE_FALLBACK);
		return {
			primary: "openai/gpt-5-mini",
			...fallbacks.length ? { fallbacks } : {}
		};
	}
	if (anthropicOk) return {
		primary: ANTHROPIC_IMAGE_PRIMARY,
		fallbacks: [ANTHROPIC_IMAGE_FALLBACK]
	};
	return null;
}
function pickMaxBytes(cfg, maxBytesMb) {
	if (typeof maxBytesMb === "number" && Number.isFinite(maxBytesMb) && maxBytesMb > 0) return Math.floor(maxBytesMb * 1024 * 1024);
	const configured = cfg?.agents?.defaults?.mediaMaxMb;
	if (typeof configured === "number" && Number.isFinite(configured) && configured > 0) return Math.floor(configured * 1024 * 1024);
}
function buildImageContext(prompt, images) {
	const content = [{
		type: "text",
		text: prompt
	}];
	for (const img of images) content.push({
		type: "image",
		data: img.base64,
		mimeType: img.mimeType
	});
	return { messages: [{
		role: "user",
		content,
		timestamp: Date.now()
	}] };
}
async function resolveSandboxedImagePath(params) {
	const normalize = (p) => p.startsWith("file://") ? p.slice(7) : p;
	const filePath = normalize(params.imagePath);
	try {
		return { resolved: params.sandbox.bridge.resolvePath({
			filePath,
			cwd: params.sandbox.root
		}).hostPath };
	} catch (err) {
		const name = path.basename(filePath);
		const candidateRel = path.join("media", "inbound", name);
		try {
			if (!await params.sandbox.bridge.stat({
				filePath: candidateRel,
				cwd: params.sandbox.root
			})) throw err;
		} catch {
			throw err;
		}
		return {
			resolved: params.sandbox.bridge.resolvePath({
				filePath: candidateRel,
				cwd: params.sandbox.root
			}).hostPath,
			rewrittenFrom: filePath
		};
	}
}
async function runImagePrompt(params) {
	const effectiveCfg = params.cfg ? {
		...params.cfg,
		agents: {
			...params.cfg.agents,
			defaults: {
				...params.cfg.agents?.defaults,
				imageModel: params.imageModelConfig
			}
		}
	} : void 0;
	await ensureOpenClawModelsJson(effectiveCfg, params.agentDir);
	const authStorage = discoverAuthStorage(params.agentDir);
	const modelRegistry = discoverModels(authStorage, params.agentDir);
	const result = await runWithImageModelFallback({
		cfg: effectiveCfg,
		modelOverride: params.modelOverride,
		run: async (provider, modelId) => {
			const model = modelRegistry.find(provider, modelId);
			if (!model) throw new Error(`Unknown model: ${provider}/${modelId}`);
			if (!model.input?.includes("image")) throw new Error(`Model does not support images: ${provider}/${modelId}`);
			const apiKey = requireApiKey(await getApiKeyForModel({
				model,
				cfg: effectiveCfg,
				agentDir: params.agentDir
			}), model.provider);
			authStorage.setRuntimeApiKey(model.provider, apiKey);
			if (model.provider === "minimax") {
				const first = params.images[0];
				const imageDataUrl = `data:${first.mimeType};base64,${first.base64}`;
				return {
					text: await minimaxUnderstandImage({
						apiKey,
						prompt: params.prompt,
						imageDataUrl,
						modelBaseUrl: model.baseUrl
					}),
					provider: model.provider,
					model: model.id
				};
			}
			return {
				text: coerceImageAssistantText({
					message: await complete(model, buildImageContext(params.prompt, params.images), {
						apiKey,
						maxTokens: resolveImageToolMaxTokens(model.maxTokens)
					}),
					provider: model.provider,
					model: model.id
				}),
				provider: model.provider,
				model: model.id
			};
		}
	});
	return {
		text: result.result.text,
		provider: result.result.provider,
		model: result.result.model,
		attempts: result.attempts.map((attempt) => ({
			provider: attempt.provider,
			model: attempt.model,
			error: attempt.error
		}))
	};
}
function createImageTool(options) {
	const agentDir = options?.agentDir?.trim();
	if (!agentDir) {
		const explicit = coerceImageModelConfig(options?.config);
		if (explicit.primary?.trim() || (explicit.fallbacks?.length ?? 0) > 0) throw new Error("createImageTool requires agentDir when enabled");
		return null;
	}
	const imageModelConfig = resolveImageModelConfigForTool({
		cfg: options?.config,
		agentDir
	});
	if (!imageModelConfig) return null;
	const description = options?.modelHasVision ? "Analyze one or more images with a vision model. Use image for a single path/URL, or images for multiple (up to 20). Only use this tool when images were NOT already provided in the user's message. Images mentioned in the prompt are automatically visible to you." : "Analyze one or more images with the configured image model (agents.defaults.imageModel). Use image for a single path/URL, or images for multiple (up to 20). Provide a prompt describing what to analyze.";
	const localRoots = (() => {
		const roots = getDefaultLocalRoots();
		const workspaceDir = normalizeWorkspaceDir(options?.workspaceDir);
		if (!workspaceDir) return roots;
		return Array.from(new Set([...roots, workspaceDir]));
	})();
	return wrapToolWithTracking({
		label: "Image",
		name: "image",
		description,
		parameters: Type.Object({
			prompt: Type.Optional(Type.String()),
			image: Type.Optional(Type.String({ description: "Single image path or URL." })),
			images: Type.Optional(Type.Array(Type.String(), { description: "Multiple image paths or URLs (up to maxImages, default 20)." })),
			model: Type.Optional(Type.String()),
			maxBytesMb: Type.Optional(Type.Number()),
			maxImages: Type.Optional(Type.Number())
		}),
		execute: async (_toolCallId, args) => {
			const record = args && typeof args === "object" ? args : {};
			const imageCandidates = [];
			if (typeof record.image === "string") imageCandidates.push(record.image);
			if (Array.isArray(record.images)) imageCandidates.push(...record.images.filter((v) => typeof v === "string"));
			const seenImages = /* @__PURE__ */ new Set();
			const imageInputs = [];
			for (const candidate of imageCandidates) {
				const trimmedCandidate = candidate.trim();
				const normalizedForDedupe = trimmedCandidate.startsWith("@") ? trimmedCandidate.slice(1).trim() : trimmedCandidate;
				if (!normalizedForDedupe || seenImages.has(normalizedForDedupe)) continue;
				seenImages.add(normalizedForDedupe);
				imageInputs.push(trimmedCandidate);
			}
			if (imageInputs.length === 0) throw new Error("image required");
			const maxImagesRaw = typeof record.maxImages === "number" ? record.maxImages : void 0;
			const maxImages = typeof maxImagesRaw === "number" && Number.isFinite(maxImagesRaw) && maxImagesRaw > 0 ? Math.floor(maxImagesRaw) : DEFAULT_MAX_IMAGES;
			if (imageInputs.length > maxImages) return {
				content: [{
					type: "text",
					text: `Too many images: ${imageInputs.length} provided, maximum is ${maxImages}. Please reduce the number of images.`
				}],
				details: {
					error: "too_many_images",
					count: imageInputs.length,
					max: maxImages
				}
			};
			const promptRaw = typeof record.prompt === "string" && record.prompt.trim() ? record.prompt.trim() : DEFAULT_PROMPT;
			const modelOverride = typeof record.model === "string" && record.model.trim() ? record.model.trim() : void 0;
			const maxBytesMb = typeof record.maxBytesMb === "number" ? record.maxBytesMb : void 0;
			const maxBytes = pickMaxBytes(options?.config, maxBytesMb);
			const sandboxConfig = options?.sandbox && options?.sandbox.root.trim() ? {
				root: options.sandbox.root.trim(),
				bridge: options.sandbox.bridge
			} : null;
			const loadedImages = [];
			for (const imageRawInput of imageInputs) {
				const trimmed = imageRawInput.trim();
				const imageRaw = trimmed.startsWith("@") ? trimmed.slice(1).trim() : trimmed;
				if (!imageRaw) throw new Error("image required (empty string in array)");
				const looksLikeWindowsDrivePath = /^[a-zA-Z]:[\\/]/.test(imageRaw);
				const hasScheme = /^[a-z][a-z0-9+.-]*:/i.test(imageRaw);
				const isFileUrl = /^file:/i.test(imageRaw);
				const isHttpUrl = /^https?:\/\//i.test(imageRaw);
				const isDataUrl = /^data:/i.test(imageRaw);
				if (hasScheme && !looksLikeWindowsDrivePath && !isFileUrl && !isHttpUrl && !isDataUrl) return {
					content: [{
						type: "text",
						text: `Unsupported image reference: ${imageRawInput}. Use a file path, a file:// URL, a data: URL, or an http(s) URL.`
					}],
					details: {
						error: "unsupported_image_reference",
						image: imageRawInput
					}
				};
				if (sandboxConfig && isHttpUrl) throw new Error("Sandboxed image tool does not allow remote URLs.");
				const resolvedImage = (() => {
					if (sandboxConfig) return imageRaw;
					if (imageRaw.startsWith("~")) return resolveUserPath(imageRaw);
					return imageRaw;
				})();
				const resolvedPathInfo = isDataUrl ? { resolved: "" } : sandboxConfig ? await resolveSandboxedImagePath({
					sandbox: sandboxConfig,
					imagePath: resolvedImage
				}) : { resolved: resolvedImage.startsWith("file://") ? resolvedImage.slice(7) : resolvedImage };
				const resolvedPath = isDataUrl ? null : resolvedPathInfo.resolved;
				const media = isDataUrl ? decodeDataUrl(resolvedImage) : sandboxConfig ? await loadWebMedia(resolvedPath ?? resolvedImage, {
					maxBytes,
					sandboxValidated: true,
					readFile: (filePath) => sandboxConfig.bridge.readFile({
						filePath,
						cwd: sandboxConfig.root
					})
				}) : await loadWebMedia(resolvedPath ?? resolvedImage, {
					maxBytes,
					localRoots
				});
				if (media.kind !== "image") throw new Error(`Unsupported media type: ${media.kind}`);
				const mimeType = "contentType" in media && media.contentType || "mimeType" in media && media.mimeType || "image/png";
				const base64 = media.buffer.toString("base64");
				loadedImages.push({
					base64,
					mimeType,
					resolvedImage,
					...resolvedPathInfo.rewrittenFrom ? { rewrittenFrom: resolvedPathInfo.rewrittenFrom } : {}
				});
			}
			const result = await runImagePrompt({
				cfg: options?.config,
				agentDir,
				imageModelConfig,
				modelOverride,
				prompt: promptRaw,
				images: loadedImages.map((img) => ({
					base64: img.base64,
					mimeType: img.mimeType
				}))
			});
			const imageDetails = loadedImages.length === 1 ? {
				image: loadedImages[0].resolvedImage,
				...loadedImages[0].rewrittenFrom ? { rewrittenFrom: loadedImages[0].rewrittenFrom } : {}
			} : { images: loadedImages.map((img) => ({
				image: img.resolvedImage,
				...img.rewrittenFrom ? { rewrittenFrom: img.rewrittenFrom } : {}
			})) };
			return {
				content: [{
					type: "text",
					text: result.text
				}],
				details: {
					model: `${result.provider}/${result.model}`,
					...imageDetails,
					attempts: result.attempts
				}
			};
		}
	}, "builtin:image");
}

//#endregion
export { image_tool_exports as n, createImageTool as t };