fix: Image Agent 模式下图片不回显、不调用生成工具的问题

三个连锁 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 <noreply@anthropic.com>
This commit is contained in:
7418andClaude Opus 4.6 committed 2026-02-27 15:45:49 +08:00
1 parent d13a0396f9
commit b5233d0257
3 files changed
+19 -9

No files matched your search

+2 -1
View File
@@ -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,
+16 -8
View File
@@ -279,6 +279,7 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream<strin
toolTimeoutSeconds = 0,
conversationHistory,
onRuntimeStatusChange,
imageAgentMode,
} = options;
return new ReadableStream<string>({
@@ -612,14 +613,21 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream<strin
}
if (imageFiles.length > 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 } }
+1
View File
@@ -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 */