fix(ai): encode empty successful tool_result content as empty string

Strict Anthropic-compatible endpoints (Z.AI GLM at api.z.ai/api/anthropic)
reject a whole request when a tool_result block carries content: [] ,
returning 400 code 1213 "The prompt parameter was not received normally".
The official API accepts both shapes, so the empty array only surfaced on
compatible endpoints once a tool returned empty output on a vision-capable
model (text-only models already encode the joined empty string).

Normalize the empty array to "" at encode time, alongside the existing
error-placeholder normalization from #2250.
This commit is contained in:
Peter Gagarinov
2026-08-16 23:44:15 +01:00
parent 37eee71978
commit 4025b27c15
3 changed files with 37 additions and 0 deletions
+4
View File
@@ -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
+6
View File
@@ -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",
@@ -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("");
});
});