function resolveImageToolMaxTokens(modelMaxTokens: number | undefined, requestedMaxTokens = 4096) { if ( typeof modelMaxTokens !== "number" ||
!Number.isFinite(modelMaxTokens) ||
modelMaxTokens <= 0
) { return requestedMaxTokens;
} return Math.min(requestedMaxTokens, modelMaxTokens);
}
/** *Resolvetheeffectiveimagemodelconfigforthe`image`tool. * *-Preferexplicitconfig(`agents.defaults.imageModel`). *-Otherwise,tryto"pair"theprimarymodelwithanimage-capablemodel: *-sameprovider(besteffort) *-fallbacktoOpenAI/Anthropicwhenavailable
*/
export function resolveImageModelConfigForTool(params: {
cfg?: OpenClawConfig;
agentDir: string;
}): ImageModelConfig | null { // Note: We intentionally do NOT gate based on primarySupportsImages here. // Even when the primary model supports images, we keep the tool available // because images are auto-injected into prompts (see attempt.ts detectAndLoadPromptImages). // The tool description is adjusted via modelHasVision to discourage redundant usage. const explicit = coerceImageModelConfig(params.cfg); if (hasToolModelConfig(explicit)) { return explicit;
}
export function createImageTool(options?: {
config?: OpenClawConfig;
agentDir?: string;
workspaceDir?: string;
sandbox?: ImageSandboxConfig;
fsPolicy?: ToolFsPolicy; /** If true, the model has native vision capability and images in the prompt are auto-injected */
modelHasVision?: boolean;
}): AnyAgentTool | null { const agentDir = options?.agentDir?.trim(); if (!agentDir) { const explicit = coerceImageModelConfig(options?.config); if (hasToolModelConfig(explicit)) { thrownew Error("createImageTool requires agentDir when enabled");
} returnnull;
} const imageModelConfig = resolveImageModelConfigForTool({
cfg: options?.config,
agentDir,
}); if (!imageModelConfig) { returnnull;
} const remoteMediaSsrfPolicy = resolveRemoteMediaSsrfPolicy(options?.config);
// If model has native vision, images in the prompt are auto-injected // so this tool is only needed when image wasn't provided in the prompt 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.";
// MARK: - Normalize image + images input and dedupe while preserving order const imageCandidates: string[] = []; if (typeof record.image === "string") {
imageCandidates.push(record.image);
} if (Array.isArray(record.images)) {
imageCandidates.push(...record.images.filter((v): v is string => typeof v === "string"));
}
const seenImages = new Set<string>(); const imageInputs: string[] = []; 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) { thrownew Error("image required");
}
// MARK: - Enforce max images cap const maxImagesRaw = typeof record.maxImages === "number" ? record.maxImages : undefined; 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 },
};
}
// The tool accepts file paths, file/data URLs, or http(s) URLs. In some // agent/model contexts, images can be referenced as pseudo-URIs like // `image:0` (e.g. "first image in the prompt"). We don't have access to a // shared image registry here, so fail gracefully instead of attempting to // `fs.readFile("image:0")` and producing a noisy ENOENT. const refInfo = classifyMediaReferenceSource(normalizedRef); const { isDataUrl, isFileUrl, isHttpUrl } = refInfo; if (refInfo.hasUnsupportedScheme) { 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) { thrownew Error("Sandboxed image tool does not allow remote URLs.");
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.