Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **fix(sse):** stop misclassifying a truncated Anthropic-compatible `max_tokens` probe response (`content:[{type:"text",text:""}]`) as an empty upstream response (#12968) — thanks @pranay-gpt
23 changes: 15 additions & 8 deletions open-sse/utils/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,9 @@ export function detectMalformedNonStream(resp: unknown): MalformedReason | null
// 1) A block IS present but invalid (e.g. text:"", a lone "(empty response)"
// sentinel, or only null entries) — the model genuinely produced no
// usable output. That is a MALFORMED-200 empty_choices regardless of
// stop_reason (parity with the OpenAI content:"" path).
// stop_reason (parity with the OpenAI content:"" path) — UNLESS the
// terminal stop_reason is one of the legitimate truncated-completion
// exemptions below (#12968).
// 2) `content: []` — no block at all. #9971: a truncated / non-terminal
// body (no stop_reason) must not become empty_choices. A terminal
// stop_reason with no output usually is empty_choices — except the
Expand All @@ -265,12 +267,16 @@ export function detectMalformedNonStream(resp: unknown): MalformedReason | null
// return content:[] + stop_reason max_tokens. Treating that as
// empty_choices turns a valid 200 into MALFORMED-200 → 502 even
// though errorClassifier would have let it through.
if (content.length === 0) {
const stopReason = typeof body.stop_reason === "string" ? body.stop_reason : "";
if (stopReason.length === 0) return null;
if (stopReason === "max_tokens" || stopReason === "tool_use") return null;
return "empty_choices";
}
const stopReason = typeof body.stop_reason === "string" ? body.stop_reason : "";
// #12968: the #9971 exemption above only fired when `content` was a
// completely empty array. A tiny `max_tokens` probe against an
// Anthropic-compatible shim can instead return content:[{type:"text",
// text:""}] — one block, just with no visible text — which is the exact
// same legitimate truncated-completion shape, so the exemption must apply
// whenever there is no visible output, not only when content is [].
if (stopReason === "max_tokens" || stopReason === "tool_use") return null;
// content:[] with no stop_reason at all is non-terminal, not empty (#9971).
if (content.length === 0 && stopReason.length === 0) return null;
return "empty_choices";
}

Expand Down Expand Up @@ -323,7 +329,8 @@ export function describeMalformedNonStream(
): { message: string; code: string; type: string } {
const body = resp && typeof resp === "object" ? (resp as Record<string, unknown>) : null;
if (body?.object === "response" && body.status === "failed") {
const err = body.error && typeof body.error === "object" ? (body.error as Record<string, unknown>) : null;
const err =
body.error && typeof body.error === "object" ? (body.error as Record<string, unknown>) : null;
const rawMessage =
typeof err?.message === "string" && err.message.trim().length > 0 ? err.message.trim() : null;
return {
Expand Down
39 changes: 39 additions & 0 deletions tests/unit/issue-12968-anthropic-shim-empty-text-block.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import test from "node:test";
import assert from "node:assert/strict";
import { detectMalformedNonStream } from "../../open-sse/utils/diagnostics.ts";

// Exact upstream body captured in the issue (freeaiapikey.com probe response).
const upstreamBody = {
content: [{ text: "", type: "text" }],
id: "msg_4d5e123dda0d4eda8055cd21",
model: "anthropic/claude-sonnet-5",
role: "assistant",
stop_reason: "max_tokens",
stop_sequence: null,
type: "message",
usage: {
cache_creation_input_tokens: 2623,
cache_read_input_tokens: 0,
input_tokens: 2234,
output_tokens: 1,
},
};

test("#12968 max_tokens probe with content:[{text:''}] must NOT be flagged empty_choices", () => {
const reason = detectMalformedNonStream(upstreamBody);
assert.equal(
reason,
null,
`expected legitimate truncated-probe response to pass through, got reason=${reason}`
);
});

test("#12968 control — content:[] + max_tokens already exempted (#9971)", () => {
const reason = detectMalformedNonStream({ ...upstreamBody, content: [] });
assert.equal(reason, null);
});

test("#12968 control — empty text block with end_turn stop_reason stays flagged", () => {
const reason = detectMalformedNonStream({ ...upstreamBody, stop_reason: "end_turn" });
assert.equal(reason, "empty_choices");
});
Loading