Skip to content

Commit 706eb2f

Browse files
author
Codex
committed
Fix native reasoning passthrough history
1 parent efe2a74 commit 706eb2f

2 files changed

Lines changed: 70 additions & 1 deletion

File tree

src/adapters/openai-responses.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,26 @@ export const FORWARD_HEADERS = [
2222
"x-responsesapi-include-timing-metrics",
2323
];
2424

25+
function sanitizeReasoningInputContent(body: unknown): unknown {
26+
if (!body || typeof body !== "object" || Array.isArray(body)) return body;
27+
const raw = body as Record<string, unknown>;
28+
if (!Array.isArray(raw.input)) return body;
29+
30+
let changed = false;
31+
const input = raw.input.map(item => {
32+
if (!item || typeof item !== "object" || Array.isArray(item)) return item;
33+
const rec = item as Record<string, unknown>;
34+
if (rec.type !== "reasoning" || !Array.isArray(rec.content) || rec.content.length === 0) return item;
35+
changed = true;
36+
// Routed models can produce raw `reasoning_text` output items. Codex echoes those in later
37+
// native GPT requests, but ChatGPT's Responses backend accepts reasoning input only with empty
38+
// `content`; keep summaries/ids and drop the raw content so native passthrough does not 400.
39+
return { ...rec, content: [] };
40+
});
41+
42+
return changed ? { ...raw, input } : body;
43+
}
44+
2545
export function createResponsesPassthroughAdapter(provider: OcxProviderConfig): ProviderAdapter & { passthrough: true } {
2646
return {
2747
name: "openai-responses",
@@ -49,7 +69,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
4969
url,
5070
method: "POST",
5171
headers,
52-
body: JSON.stringify(parsed._rawBody),
72+
body: JSON.stringify(sanitizeReasoningInputContent(parsed._rawBody)),
5373
};
5474
},
5575

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { describe, expect, test } from "bun:test";
2+
import { createResponsesPassthroughAdapter } from "../src/adapters/openai-responses";
3+
4+
const provider = {
5+
adapter: "openai-responses",
6+
baseUrl: "https://chatgpt.example/backend-api/codex",
7+
authMode: "forward" as const,
8+
};
9+
10+
describe("OpenAI Responses passthrough sanitization", () => {
11+
test("drops raw reasoning input content before native GPT passthrough", () => {
12+
const adapter = createResponsesPassthroughAdapter(provider);
13+
const request = adapter.buildRequest({
14+
modelId: "gpt-5.5",
15+
context: { messages: [] },
16+
stream: true,
17+
options: {},
18+
_rawBody: {
19+
model: "gpt-5.5",
20+
input: [
21+
{
22+
type: "reasoning",
23+
id: "rs_1",
24+
summary: [],
25+
content: [{ type: "reasoning_text", text: "raw routed reasoning" }],
26+
},
27+
{
28+
type: "message",
29+
role: "user",
30+
content: [{ type: "input_text", text: "hi" }],
31+
},
32+
],
33+
},
34+
}, { headers: new Headers({ authorization: "Bearer token" }) });
35+
const body = JSON.parse(request.body) as { input: Record<string, unknown>[] };
36+
37+
expect(body.input[0]).toMatchObject({
38+
type: "reasoning",
39+
id: "rs_1",
40+
summary: [],
41+
content: [],
42+
});
43+
expect(body.input[1]).toMatchObject({
44+
type: "message",
45+
role: "user",
46+
content: [{ type: "input_text", text: "hi" }],
47+
});
48+
});
49+
});

0 commit comments

Comments
 (0)