mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 10:27:38 +00:00
fix(agents): restore multi-image image tool schema contract
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import path from "node:path";
|
||||
import { type Api, type Context, complete, type Model } from "@mariozechner/pi-ai";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import path from "node:path";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import type { SandboxFsBridge } from "../sandbox/fs-bridge.js";
|
||||
import type { AnyAgentTool } from "./common.js";
|
||||
import { resolveUserPath } from "../../utils.js";
|
||||
import { getDefaultLocalRoots, loadWebMedia } from "../../web/media.js";
|
||||
import { ensureAuthProfileStore, listProfilesForProvider } from "../auth-profiles.js";
|
||||
@@ -12,9 +14,7 @@ import { runWithImageModelFallback } from "../model-fallback.js";
|
||||
import { resolveConfiguredModelRef } from "../model-selection.js";
|
||||
import { ensureOpenClawModelsJson } from "../models-config.js";
|
||||
import { discoverAuthStorage, discoverModels } from "../pi-model-discovery.js";
|
||||
import type { SandboxFsBridge } from "../sandbox/fs-bridge.js";
|
||||
import { normalizeWorkspaceDir } from "../workspace-dir.js";
|
||||
import type { AnyAgentTool } from "./common.js";
|
||||
import {
|
||||
coerceImageAssistantText,
|
||||
coerceImageModelConfig,
|
||||
@@ -358,8 +358,8 @@ export function createImageTool(options?: {
|
||||
// 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. Pass a single image path/URL or an array of 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). Pass a single image path/URL or an array of up to 20. Provide a prompt describing what to analyze.";
|
||||
? "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();
|
||||
@@ -376,7 +376,12 @@ export function createImageTool(options?: {
|
||||
description,
|
||||
parameters: Type.Object({
|
||||
prompt: Type.Optional(Type.String()),
|
||||
image: Type.String({ description: "Image path or URL (pass multiple as comma-separated)" }),
|
||||
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()),
|
||||
@@ -384,17 +389,28 @@ export function createImageTool(options?: {
|
||||
execute: async (_toolCallId, args) => {
|
||||
const record = args && typeof args === "object" ? (args as Record<string, unknown>) : {};
|
||||
|
||||
// MARK: - Normalize image input (string | string[])
|
||||
const rawImageInput = record.image;
|
||||
const imageInputs: string[] = (() => {
|
||||
if (typeof rawImageInput === "string") {
|
||||
return [rawImageInput];
|
||||
// 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;
|
||||
}
|
||||
if (Array.isArray(rawImageInput)) {
|
||||
return rawImageInput.filter((v): v is string => typeof v === "string");
|
||||
}
|
||||
return [];
|
||||
})();
|
||||
seenImages.add(normalizedForDedupe);
|
||||
imageInputs.push(trimmedCandidate);
|
||||
}
|
||||
if (imageInputs.length === 0) {
|
||||
throw new Error("image required");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user