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
110 changes: 75 additions & 35 deletions src/server/responses/core-opaque-recovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,80 @@ export function isReasoningBlobCallerMismatchMessage(message: string): boolean {
}


function liteLlmEmbeddedErrorPayload(message: string): unknown {
if (!message.startsWith("litellm.BadRequestError:")) return undefined;
const marker = "OpenAIException - ";
const markerIndex = message.indexOf(marker);
if (markerIndex < 0) return undefined;
const start = message.indexOf("{", markerIndex + marker.length);
if (start < 0) return undefined;

let depth = 0;
let inString = false;
let escaped = false;
for (let index = start; index < message.length; index += 1) {
const character = message[index]!;
if (inString) {
if (escaped) escaped = false;
else if (character === "\\") escaped = true;
else if (character === '"') inString = false;
continue;
}
if (character === '"') { inString = true; continue; }
if (character === "{") depth += 1;
else if (character === "}") {
depth -= 1;
if (depth === 0) {
try { return JSON.parse(message.slice(start, index + 1)) as unknown; } catch { return undefined; }
}
}
}
return undefined;
}


function isOpaqueBlobErrorPayload(payload: unknown, allowLiteLlmWrapper: boolean): boolean {
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return false;
const record = payload as { code?: unknown; type?: unknown; message?: unknown; error?: unknown };

if (record.error && typeof record.error === "object" && !Array.isArray(record.error)) {
const error = record.error as { type?: unknown; code?: unknown; message?: unknown };
if (error.type === "invalid_request_error") {
if (error.code === "invalid_encrypted_content") return true;
if (
(error.code === null || error.code === undefined)
&& typeof error.message === "string"
&& error.message.startsWith("The encrypted content ")
&& error.message.endsWith(
" could not be verified. Reason: Encrypted content could not be decrypted or parsed.",
)
) return true;
// #4469: the caller-mismatch wording arrives without a dedicated code, so the
// message itself is the identity. It is not gated on code being null — the upstream
// may attach a generic code — because the anchored phrase is already specific.
if (typeof error.message === "string" && isReasoningBlobCallerMismatchMessage(error.message)) {
return true;
}
}
if (allowLiteLlmWrapper && typeof error.message === "string") {
return isOpaqueBlobErrorPayload(liteLlmEmbeddedErrorPayload(error.message), false);
}
}

// The flat stream-error envelope carries type/message at the top level rather than under
// an error object; the same anchored identity applies there.
if (
record.type === "invalid_request_error"
&& typeof record.message === "string"
&& isReasoningBlobCallerMismatchMessage(record.message)
) return true;

if (record.code !== "invalid-argument" || typeof record.error !== "string") return false;
return record.error.startsWith("Could not decode the compaction blob")
|| record.error.startsWith("Could not decrypt the provided encrypted_content");
}


export function isSelfIdentifiedOpaqueBlobRejection(bodyText: string): boolean {
if (isEncryptedFunctionOutputRejection(bodyText)) return true;
try {
Expand All @@ -127,41 +201,7 @@ export function isSelfIdentifiedOpaqueBlobRejection(bodyText: string): boolean {
}
try {
const payload = JSON.parse(bodyText) as unknown;
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return false;
const record = payload as { code?: unknown; type?: unknown; message?: unknown; error?: unknown };

if (record.error && typeof record.error === "object" && !Array.isArray(record.error)) {
const error = record.error as { type?: unknown; code?: unknown; message?: unknown };
if (error.type === "invalid_request_error") {
if (error.code === "invalid_encrypted_content") return true;
if (
(error.code === null || error.code === undefined)
&& typeof error.message === "string"
&& error.message.startsWith("The encrypted content ")
&& error.message.endsWith(
" could not be verified. Reason: Encrypted content could not be decrypted or parsed.",
)
) return true;
// #4469: the caller-mismatch wording arrives without a dedicated code, so the
// message itself is the identity. It is not gated on code being null — the upstream
// may attach a generic code — because the anchored phrase is already specific.
if (typeof error.message === "string" && isReasoningBlobCallerMismatchMessage(error.message)) {
return true;
}
}
}

// The flat stream-error envelope carries type/message at the top level rather than under
// an error object; the same anchored identity applies there.
if (
record.type === "invalid_request_error"
&& typeof record.message === "string"
&& isReasoningBlobCallerMismatchMessage(record.message)
) return true;

if (record.code !== "invalid-argument" || typeof record.error !== "string") return false;
return record.error.startsWith("Could not decode the compaction blob")
|| record.error.startsWith("Could not decrypt the provided encrypted_content");
return isOpaqueBlobErrorPayload(payload, true);
} catch {
return false;
}
Expand Down
3 changes: 3 additions & 0 deletions structure/transports/responses.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,9 @@ turn and the item outlives the failure in the client transcript, repeating on ev
including the compaction turn the proxy itself drives. With `store: false`, request sanitization
strips ids from every input item, including compact-wire items, matching codex-rs
(`core/src/client.rs:918-925`). Compact-wire items remain exempt from response-side field backfill.
If LiteLLM wraps an upstream OpenAI error as prose in its outer `error.message`, reactive recovery
parses only the bounded `OpenAIException - {JSON}` payload and applies the existing recognized
opaque-rejection identities to that inner payload; other LiteLLM 400 responses do not retry.

For replayed `encrypted_content` slots whose minting provenance is unavailable after a restart or
full-history resend, the plaintext-compatibility boundary requires canonical key-independent Fernet
Expand Down
43 changes: 42 additions & 1 deletion tests/responses/responses-opaque-blob-recovery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ const OPENAI_BLOB_ERROR = JSON.stringify({
code: "invalid_encrypted_content",
},
});
const LITELLM_BLOB_ERROR = JSON.stringify({
error: {
message: "litellm.BadRequestError: OpenAIException - " + OPENAI_BLOB_ERROR
+ " This error occurs when load balancing Responses API across deployments with different API keys.",
type: "invalid_request_error",
param: null,
code: "400",
},
});
const CHATGPT_UNVERIFIABLE_BLOB_ERROR = JSON.stringify({
error: {
message: "The encrypted content 6871-test-ef-0 could not be verified. Reason: Encrypted content could not be decrypted or parsed.",
Expand Down Expand Up @@ -430,8 +439,9 @@ describe("opaque blob recovery trigger", () => {
alreadyAttempted: false,
};

test("accepts OpenAI and both xAI opaque-state rejection identities", () => {
test("accepts OpenAI, LiteLLM-wrapped OpenAI, and both xAI rejection identities", () => {
expect(shouldAttemptOpaqueBlobRecovery(base)).toBe(true);
expect(shouldAttemptOpaqueBlobRecovery({ ...base, errorBody: LITELLM_BLOB_ERROR })).toBe(true);
expect(shouldAttemptOpaqueBlobRecovery({
...base,
errorBody: CHATGPT_UNVERIFIABLE_BLOB_ERROR,
Expand All @@ -447,6 +457,18 @@ describe("opaque blob recovery trigger", () => {
error: { type: "invalid_request_error", code: "unknown_parameter", message: "Unknown parameter" },
}),
})).toBe(false);
expect(shouldAttemptOpaqueBlobRecovery({
...base,
errorBody: JSON.stringify({
error: {
type: "invalid_request_error",
code: "400",
message: "litellm.BadRequestError: OpenAIException - " + JSON.stringify({
error: { type: "invalid_request_error", code: "unknown_parameter", message: "Unknown parameter" },
}),
},
}),
})).toBe(false);
expect(shouldAttemptOpaqueBlobRecovery({ ...base, status: 500 })).toBe(false);
expect(shouldAttemptOpaqueBlobRecovery({
...base,
Expand Down Expand Up @@ -596,6 +618,25 @@ describe("opaque blob recovery through /v1/responses", () => {
}]);
});

test("recovers LiteLLM-wrapped invalid encrypted content with one sanitized resend", async () => {
const outbound: Array<Record<string, unknown>> = [];
globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => {
outbound.push(JSON.parse(String(init?.body)) as Record<string, unknown>);
return outbound.length === 1 ? rejection(LITELLM_BLOB_ERROR) : success("resp-litellm-recovered");
}) as typeof fetch;
const logCtx: RequestLogContext = { model: "", provider: "" };

const response = await handleResponses(request(), config(), logCtx);
expect(response.status).toBe(200);
await response.text();

expect(outbound).toHaveLength(2);
expect(hasBlob(outbound[0]!)).toBe(true);
expect(hasBlob(outbound[1]!)).toBe(false);
expect(logCtx.activeAttempt?.sendCount).toBe(2);
expect(logCtx.activeAttempt?.recoveryKinds).toEqual(["opaque-blob-rejection"]);
});

test("recovers a zero-output streamed function-output decrypt failure before client relay", async () => {
const outbound: Array<Record<string, unknown>> = [];
globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => {
Expand Down
Loading