diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 86d99d599..3972c0586 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed Anthropic-compatible endpoints with strict prompt validation (e.g. Z.AI GLM `api.z.ai/api/anthropic`, which rejects the whole request with `400 code 1213 "The prompt parameter was not received normally"`) failing sessions once a tool returned empty output on a vision-capable model: empty successful `tool_result` blocks now encode as `content: ""` instead of `content: []`, which both the official API and strict compatible endpoints accept. + ## [17.3.5] - 2026-08-16 ### Added diff --git a/packages/ai/src/providers/anthropic.ts b/packages/ai/src/providers/anthropic.ts index d8c631308..9f3dd5965 100644 --- a/packages/ai/src/providers/anthropic.ts +++ b/packages/ai/src/providers/anthropic.ts @@ -3548,6 +3548,12 @@ function buildToolResultBlock( } content = content.filter(block => block.type === "text"); } + // An empty array is valid for the official API, but strict Anthropic-compatible + // endpoints (Z.AI GLM: 400 code 1213 "The prompt parameter was not received + // normally") reject it; the empty-string form is accepted by both. + if (Array.isArray(content) && content.length === 0) { + content = ""; + } content = ensureErrorToolResultWireContent(content, msg.isError); const block: ContentBlockParam = { type: "tool_result", diff --git a/packages/ai/test/anthropic-empty-error-tool-result.test.ts b/packages/ai/test/anthropic-empty-error-tool-result.test.ts index c14b9a70d..b5e72799e 100644 --- a/packages/ai/test/anthropic-empty-error-tool-result.test.ts +++ b/packages/ai/test/anthropic-empty-error-tool-result.test.ts @@ -20,6 +20,13 @@ const anthropicModel: Model<"anthropic-messages"> = buildModel({ baseUrl: "https://api.anthropic.com", }); +const visionModel: Model<"anthropic-messages"> = buildModel({ + ...baseModel, + input: ["text", "image"], + provider: "anthropic", + baseUrl: "https://api.anthropic.com", +}); + const user: UserMessage = { role: "user", content: "run the tool", @@ -95,4 +102,24 @@ describe("anthropic empty error tool_result encoding", () => { expect(block.is_error).toBe(false); expect(block.content).toBe(""); }); + + it("encodes empty successful tool results as empty string, not empty array, on image-capable models", () => { + // Regression: vision-capable models serialize whitespace-only successful + // tool results as `content: []` on the wire. Official Anthropic accepts it, + // but strict Anthropic-compatible endpoints (Z.AI GLM, api.z.ai/api/anthropic) + // reject the whole request with 400 code 1213 ("The prompt parameter was + // not received normally"). `content: ""` is accepted by both. + const toolResult: ToolResultMessage = { + role: "toolResult", + toolCallId: "toolu_empty_error", + toolName: "write", + content: [{ type: "text", text: "" }], + isError: false, + timestamp: Date.now(), + }; + + const block = getToolResultBlock(visionModel, toolResult); + expect(block.is_error).toBe(false); + expect(block.content).toBe(""); + }); });