Skip to content
Closed
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
4 changes: 3 additions & 1 deletion src/protocols/encoders/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
anthropicUsage,
boundedReasoningIdentity,
collectAnthropicMessage,
isContextOverflowError,
isClaudeWebSearchToolName,
messageSnapshot,
sanitizeWebSearchInput,
Expand Down Expand Up @@ -389,8 +390,9 @@ function createAnthropicMessageWriter(
}
const message = typeof error.message === "string" ? error.message : "upstream request failed";
const deterministic = error.code === UNDECLARED_TOOL_CALL_ERROR_CODE;
const overflow = isContextOverflowError(error, message);
fail(anthropicFailedStatus(error, message), message, !deterministic,
deterministic || error.code === "context_length_exceeded" ? error.code as string : undefined);
deterministic ? error.code as string : overflow ? "context_length_exceeded" : undefined);
},
overflow() {
fail(413, "upstream translation buffer exceeded the safe limit", false, "translation_buffer_limit");
Expand Down
10 changes: 5 additions & 5 deletions structure/data-planes/protocol-paths.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,11 +223,11 @@ the wire-silence heartbeat and live-destination stall watchdog ([timeout policy]
`chat.ts` (`encodeChatCompletionSse`, `foldChatCompletion`) and `messages.ts`
(`encodeAnthropicMessageSse`, `foldAnthropicMessage`) are the writers; the Chat one also emits `reasoning_details` ([#7429](chat-reasoning-details.md)). They reuse the
converters' own helpers, exported from `src/chat/outbound.ts` and `src/claude/outbound.ts`
(ids, chunk and frame builders, usage mapping, `chatCompletionsStreamErrorPayload`,
`chatCompletionsFailedResponse`, `chatCompletionsIncompleteOutcome`,
`anthropicIncompleteOutcome`, `anthropicFailedStatus`, the message snapshot, the web-search pair),
so the frames a client receives are the converters' frames. A fold is the encoded stream read by
the existing collector. Only producer-marked watchdog/EOF terminals retain local Chat wording; upstream copies of their reason or message follow [client error normalization](error-detail.md#client-error-bodies-chat). Explicit incomplete reasons use the [shared normalization contract](../transports/responses-wire-shapes.md#adapter-incomplete-reasons) before the writers classify the outcome.
(ids, chunk and frame builders, usage mapping, `chatCompletionsStreamErrorPayload`, `chatCompletionsFailedResponse`, `chatCompletionsIncompleteOutcome`,
`anthropicIncompleteOutcome`, `anthropicFailedStatus`, `isContextOverflowError`, the message snapshot, the web-search pair),
so the frames a client receives are the converters' frames. A fold is the encoded stream read by the existing collector.
Messages terminal errors use the shared context-overflow verdict for their code as well as their status: definite generic-coded refusals receive `context_length_exceeded` and `prompt is too long` wording even when marked retryable, while the deterministic undeclared-tool error keeps its code. `tests/responses/protocol-direct-encoders-messages.test.ts` pins stream and fold parity.
Only producer-marked watchdog/EOF terminals retain local Chat wording; upstream copies of their reason or message follow [client error normalization](error-detail.md#client-error-bodies-chat). Explicit incomplete reasons use the [shared normalization contract](../transports/responses-wire-shapes.md#adapter-incomplete-reasons) before the writers classify the outcome.

Deliberate equivalences, pinned by the parity tests: a Chat function call is delivered as one
complete tool-call chunk when it completes, as the converter always did; Messages streams
Expand Down
14 changes: 14 additions & 0 deletions tests/responses/protocol-direct-encoders-messages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,20 @@ describe("direct Messages encoder matches bridge + converter (stream)", () => {
});

describe("direct Messages fold matches bridge + collector (non-stream)", () => {
test.each(["server_error", "upstream_error", "api_error", "internal_error"])(
"generic-coded %s context overflow triggers client compaction on both paths", async code => {
const events: AdapterEvent[] = [{ type: "error", errorType: "server_error",
code, message: "Your input exceeds the context window of this model", retryable: true }];
const frames = await expectStreamParity(events);
expect(frames).toEqual([{ event: "error", data: { type: "error", error: {
type: "invalid_request_error", code: "context_length_exceeded",
message: "prompt is too long: Your input exceeds the context window of this model",
} } }]);
const response = await expectFoldParity(events);
expect(response.status).toBe(400);
},
);

test.each([400, 413, undefined])("context rejection remains terminal with upstream status %s", async status => {
const events: AdapterEvent[] = [{ type: "error", status, errorType: "invalid_request_error",
code: "context_length_exceeded", message: "Synthetic input limit", retryable: false }];
Expand Down
Loading