From b5233d025704b6567cda8ac4ae20705edda05a22 Mon Sep 17 00:00:00 2001 From: 7418 <7418@openclaw.ai> Date: Fri, 27 Feb 2026 15:45:49 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20Image=20Agent=20=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E4=B8=8B=E5=9B=BE=E7=89=87=E4=B8=8D=E5=9B=9E=E6=98=BE=E3=80=81?= =?UTF-8?q?=E4=B8=8D=E8=B0=83=E7=94=A8=E7=94=9F=E6=88=90=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 三个连锁 Bug 的根因修复: 1. route.ts — 图片 base64 数据保留 - 之前:文件存盘后一律清空 base64 data(包括图片) - 现在:只对非图片文件清空 data,图片始终保留 base64 - 确保 SDK 收到有效的 image content block 用于 vision 2. types/index.ts — ClaudeStreamOptions 增加 imageAgentMode 标记 - 新增 imageAgentMode?: boolean 字段 - 由 route.ts 根据 systemPromptAppend 是否存在来设置 3. claude-client.ts — buildFinalPrompt 在 imageAgentMode 下跳过文件路径引用 - imageAgentMode=true 时不添加 [User attached image: /path] 文本 - 防止 Claude 看到路径后调用 analyze_image 等内置工具 - Claude 仅通过 vision(base64 content blocks)看到图片 - 遵循 IMAGE_AGENT_SYSTEM_PROMPT 输出 image-gen-request 块 - 普通模式行为不变,仍保留路径引用供 skill/工具使用 Co-Authored-By: Claude Opus 4.6 --- src/app/api/chat/route.ts | 3 ++- src/lib/claude-client.ts | 24 ++++++++++++++++-------- src/types/index.ts | 1 + 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/app/api/chat/route.ts b/src/app/api/chat/route.ts index a557c88..0d65a35 100644 --- a/src/app/api/chat/route.ts +++ b/src/app/api/chat/route.ts @@ -149,7 +149,7 @@ export async function POST(request: NextRequest) { name: f.name, type: f.type, size: f.size, - data: meta?.filePath ? '' : f.data, // Skip base64 data if file is already saved to disk + data: (meta?.filePath && !f.type.startsWith('image/')) ? '' : f.data, // Keep base64 for images (needed for vision); clear for non-images (read from disk) filePath: meta?.filePath, }; }) @@ -189,6 +189,7 @@ export async function POST(request: NextRequest) { abortController, permissionMode, files: fileAttachments, + imageAgentMode: !!systemPromptAppend, toolTimeoutSeconds: toolTimeout || 300, provider: resolvedProvider, conversationHistory: historyMsgs, diff --git a/src/lib/claude-client.ts b/src/lib/claude-client.ts index 5ab38af..c623af2 100644 --- a/src/lib/claude-client.ts +++ b/src/lib/claude-client.ts @@ -279,6 +279,7 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream({ @@ -612,14 +613,21 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream 0) { - // Append image disk paths to the text prompt so Claude knows where - // the files are on disk (enables skills to reference them by path). - const workDir = workingDirectory || os.homedir(); - const imagePaths = getUploadedFilePaths(imageFiles, workDir); - const imageReferences = imagePaths - .map((p, i) => `[User attached image: ${p} (${imageFiles[i].name})]`) - .join('\n'); - const textWithImageRefs = `${imageReferences}\n\n${textPrompt}`; + // In imageAgentMode, skip file path references so Claude doesn't + // try to use built-in tools to analyze images from disk. It will + // see the images via vision (base64 content blocks) and follow the + // IMAGE_AGENT_SYSTEM_PROMPT to output image-gen-request blocks. + // In normal mode, append disk paths so skills can reference them. + const textWithImageRefs = imageAgentMode + ? textPrompt + : (() => { + const workDir = workingDirectory || os.homedir(); + const imagePaths = getUploadedFilePaths(imageFiles, workDir); + const imageReferences = imagePaths + .map((p, i) => `[User attached image: ${p} (${imageFiles[i].name})]`) + .join('\n'); + return `${imageReferences}\n\n${textPrompt}`; + })(); const contentBlocks: Array< | { type: 'image'; source: { type: 'base64'; media_type: string; data: string } } diff --git a/src/types/index.ts b/src/types/index.ts index 5634cd4..32ec413 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -644,6 +644,7 @@ export interface ClaudeStreamOptions { abortController?: AbortController; permissionMode?: string; files?: FileAttachment[]; + imageAgentMode?: boolean; toolTimeoutSeconds?: number; provider?: ApiProvider; /** Recent conversation history from DB — used as fallback context when SDK resume is unavailable or fails */