diff --git a/src/routes/chatStream.ts b/src/routes/chatStream.ts index 9676382..63a9ed0 100644 --- a/src/routes/chatStream.ts +++ b/src/routes/chatStream.ts @@ -772,6 +772,9 @@ export const streamPromptResponse = async ({ detail: buildToolProgressDetail( part.tool, part.state.status, + toolParams, + reason, + part.state.status === "error" ? part.state.error : undefined, ), }); if ( diff --git a/src/routes/chatStreamEvents.ts b/src/routes/chatStreamEvents.ts index 58b2a03..10cf841 100644 --- a/src/routes/chatStreamEvents.ts +++ b/src/routes/chatStreamEvents.ts @@ -356,6 +356,37 @@ export const normalizeToolStatus = (status: string) => { return "running"; }; +const formatProgressValue = (value: unknown): string => { + if (typeof value === "string") { + return value.length > 120 ? `${value.slice(0, 117)}...` : value; + } + if ( + typeof value === "number" || + typeof value === "boolean" || + value === null || + value === undefined + ) { + return String(value); + } + try { + const serialized = JSON.stringify(value); + return serialized.length > 120 ? `${serialized.slice(0, 117)}...` : serialized; + } catch { + return "[unserializable]"; + } +}; + +const summarizeToolParams = (params: Record) => { + const ignoredKeys = new Set(["reason", "request_reason", "why", "purpose", "rationale"]); + const summary = Object.entries(params) + .filter(([key]) => !ignoredKeys.has(key)) + .slice(0, 4) + .map(([key, value]) => `${key}=${formatProgressValue(value)}`) + .join(", "); + + return summary || "无附加参数"; +}; + export const buildSessionStatusDetail = (status: { type: string; message?: string }) => { if (status.type === "retry") { return status.message @@ -382,19 +413,25 @@ export const buildReasoningProgressDetail = ( export const buildToolProgressDetail = ( tool: string, status: string, + params: Record, + reason: string, + error?: string, ) => { const toolName = toolLabels[tool] ?? tool; + const reasonText = reason ? `;调用原因:${reason}` : ""; + const paramsText = `;关键参数:${summarizeToolParams(params)}`; if (status === "error") { - return `${toolName} 调用失败。`; + const errorText = error ? `;错误:${error}` : ""; + return `${toolName} 调用失败${reasonText}${paramsText}${errorText}`; } if (status === "completed") { - return `${toolName} 已执行完成。`; + return `${toolName} 已执行完成${reasonText}${paramsText}`; } if (status === "pending") { - return `${toolName} 等待执行。`; + return `${toolName} 已进入待执行状态${reasonText}${paramsText}`; } - return `${toolName} 正在执行。`; + return `${toolName} 正在执行${reasonText}${paramsText}`; }; export const getToolProgressTitle = (tool: string, status: string) => { diff --git a/tests/routes/chatStream.test.ts b/tests/routes/chatStream.test.ts index e72393e..0466da5 100644 --- a/tests/routes/chatStream.test.ts +++ b/tests/routes/chatStream.test.ts @@ -168,7 +168,7 @@ describe("streamPromptResponse", () => { ).toBe("最终分析结果。"); }); - it("keeps reasoning, tool parameters, and raw errors out of progress details", async () => { + it("keeps reasoning generic while preserving tool execution details", async () => { const runtime = { subscribeEvents: async () => createEventStream([ @@ -246,7 +246,9 @@ describe("streamPromptResponse", () => { (item) => item.event === "progress" && item.data.id === "tool-part-1", ); expect(reasoningProgress?.data.detail).toBe("分析步骤已整理完成。"); - expect(toolProgress?.data.detail).toBe("tjwater_cli 调用失败。"); + expect(toolProgress?.data.detail).toBe( + "tjwater_cli 调用失败;调用原因:尝试突破分页限制;关键参数:command=network get-all-pipes-properties --limit 5000;错误:HTTP_422 raw backend payload with trace_id=secret-trace", + ); }); it("forwards opencode permission requests as SSE payloads", async () => {