Skip to content

Commit 92d492a

Browse files
committed
fix(agents): pin codex reasoning replay provenance
1 parent 1e18d6f commit 92d492a

11 files changed

Lines changed: 335 additions & 38 deletions

src/agents/openai-transport-stream.test.ts

Lines changed: 176 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -324,10 +324,17 @@ describe("openai transport stream", () => {
324324
output,
325325
{ push: vi.fn() },
326326
model,
327-
{ sessionId: "session-123" },
327+
{ authProfileId: "openai-codex:oauth", sessionId: "session-123" },
328328
);
329329

330-
const thinkingBlock = output.content[0] as { thinkingSignature?: string };
330+
const expectedReplayMetadata = testing.buildOpenAIResponsesReasoningReplayMetadata(model, {
331+
authProfileId: "openai-codex:oauth",
332+
sessionId: "session-123",
333+
});
334+
const thinkingBlock = output.content[0] as {
335+
thinkingSignature?: string;
336+
openclawReasoningReplay?: unknown;
337+
};
331338
const replayItem = JSON.parse(thinkingBlock.thinkingSignature ?? "{}") as Record<
332339
string,
333340
unknown
@@ -336,10 +343,9 @@ describe("openai transport stream", () => {
336343
type: "reasoning",
337344
id: "rs_123",
338345
encrypted_content: "ciphertext",
339-
__openclaw_replay: testing.buildOpenAIResponsesReasoningReplayMetadata(model, {
340-
sessionId: "session-123",
341-
}),
342346
});
347+
expect(replayItem).not.toHaveProperty("__openclaw_replay");
348+
expect(thinkingBlock.openclawReasoningReplay).toEqual(expectedReplayMetadata);
343349
});
344350

345351
it("clamps Responses cached prompt usage at zero", async () => {
@@ -2308,16 +2314,17 @@ describe("openai transport stream", () => {
23082314
{
23092315
type: "thinking",
23102316
thinking: "Need a tool.",
2311-
thinkingSignature: JSON.stringify(
2312-
testing.tagOpenAIResponsesReasoningReplayItem(
2313-
{
2314-
type: "reasoning",
2315-
id: "rs_prior",
2316-
encrypted_content: "ciphertext",
2317-
},
2318-
model,
2319-
{ sessionId: "session-123" },
2320-
),
2317+
thinkingSignature: JSON.stringify({
2318+
type: "reasoning",
2319+
id: "rs_prior",
2320+
encrypted_content: "ciphertext",
2321+
}),
2322+
openclawReasoningReplay: testing.buildOpenAIResponsesReasoningReplayMetadata(
2323+
model,
2324+
{
2325+
authProfileId: "openai-codex:oauth",
2326+
sessionId: "session-123",
2327+
},
23212328
),
23222329
},
23232330
{
@@ -2340,7 +2347,7 @@ describe("openai transport stream", () => {
23402347
],
23412348
tools: [],
23422349
} as never,
2343-
{ sessionId: "session-123" },
2350+
{ authProfileId: "openai-codex:oauth", sessionId: "session-123" },
23442351
) as {
23452352
input?: Array<{
23462353
type?: string;
@@ -2390,6 +2397,152 @@ describe("openai transport stream", () => {
23902397
maxTokens: 8192,
23912398
} satisfies Model<"openai-codex-responses">;
23922399

2400+
const params = buildOpenAIResponsesParams(
2401+
model,
2402+
{
2403+
systemPrompt: "system",
2404+
messages: [
2405+
{
2406+
role: "assistant",
2407+
api: "openai-codex-responses",
2408+
provider: "openai-codex",
2409+
model: "gpt-5.4",
2410+
usage: {
2411+
input: 0,
2412+
output: 0,
2413+
cacheRead: 0,
2414+
cacheWrite: 0,
2415+
totalTokens: 0,
2416+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
2417+
},
2418+
stopReason: "toolUse",
2419+
timestamp: 1,
2420+
content: [
2421+
{
2422+
type: "thinking",
2423+
thinking: "Need a tool.",
2424+
thinkingSignature: JSON.stringify({
2425+
type: "reasoning",
2426+
id: "rs_prior",
2427+
encrypted_content: "ciphertext",
2428+
}),
2429+
openclawReasoningReplay: testing.buildOpenAIResponsesReasoningReplayMetadata(
2430+
model,
2431+
{
2432+
authProfileId: "openai-codex:oauth",
2433+
sessionId: "different-session",
2434+
},
2435+
),
2436+
},
2437+
],
2438+
},
2439+
],
2440+
tools: [],
2441+
} as never,
2442+
{ authProfileId: "openai-codex:oauth", sessionId: "session-123" },
2443+
) as {
2444+
input?: Array<{
2445+
type?: string;
2446+
id?: string;
2447+
encrypted_content?: string;
2448+
}>;
2449+
};
2450+
2451+
const reasoningItem = params.input?.find((item) => item.type === "reasoning");
2452+
expectRecordFields(reasoningItem, {
2453+
type: "reasoning",
2454+
id: "rs_prior",
2455+
});
2456+
expect(reasoningItem).not.toHaveProperty("encrypted_content");
2457+
});
2458+
2459+
it("strips encrypted reasoning replay when the auth profile provenance changes", () => {
2460+
const model = {
2461+
id: "gpt-5.4",
2462+
name: "GPT-5.4",
2463+
api: "openai-codex-responses",
2464+
provider: "openai-codex",
2465+
baseUrl: "https://proxy.example.com/v1",
2466+
reasoning: true,
2467+
input: ["text"],
2468+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
2469+
contextWindow: 200000,
2470+
maxTokens: 8192,
2471+
} satisfies Model<"openai-codex-responses">;
2472+
2473+
const params = buildOpenAIResponsesParams(
2474+
model,
2475+
{
2476+
systemPrompt: "system",
2477+
messages: [
2478+
{
2479+
role: "assistant",
2480+
api: "openai-codex-responses",
2481+
provider: "openai-codex",
2482+
model: "gpt-5.4",
2483+
usage: {
2484+
input: 0,
2485+
output: 0,
2486+
cacheRead: 0,
2487+
cacheWrite: 0,
2488+
totalTokens: 0,
2489+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
2490+
},
2491+
stopReason: "toolUse",
2492+
timestamp: 1,
2493+
content: [
2494+
{
2495+
type: "thinking",
2496+
thinking: "Need a tool.",
2497+
thinkingSignature: JSON.stringify({
2498+
type: "reasoning",
2499+
id: "rs_prior",
2500+
encrypted_content: "ciphertext",
2501+
}),
2502+
openclawReasoningReplay: testing.buildOpenAIResponsesReasoningReplayMetadata(
2503+
model,
2504+
{
2505+
authProfileId: "openai-codex:old-oauth",
2506+
sessionId: "session-123",
2507+
},
2508+
),
2509+
},
2510+
],
2511+
},
2512+
],
2513+
tools: [],
2514+
} as never,
2515+
{ authProfileId: "openai-codex:new-oauth", sessionId: "session-123" },
2516+
) as {
2517+
input?: Array<{
2518+
type?: string;
2519+
id?: string;
2520+
encrypted_content?: string;
2521+
}>;
2522+
};
2523+
2524+
const reasoningItem = params.input?.find((item) => item.type === "reasoning");
2525+
expectRecordFields(reasoningItem, {
2526+
type: "reasoning",
2527+
id: "rs_prior",
2528+
});
2529+
expect(reasoningItem).not.toHaveProperty("encrypted_content");
2530+
});
2531+
2532+
it("keeps embedded replay provenance as a compatibility fallback", () => {
2533+
const model = {
2534+
id: "gpt-5.4",
2535+
name: "GPT-5.4",
2536+
api: "openai-codex-responses",
2537+
provider: "openai-codex",
2538+
baseUrl: "https://proxy.example.com/v1",
2539+
reasoning: true,
2540+
input: ["text"],
2541+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
2542+
contextWindow: 200000,
2543+
maxTokens: 8192,
2544+
} satisfies Model<"openai-codex-responses">;
2545+
23932546
const params = buildOpenAIResponsesParams(
23942547
model,
23952548
{
@@ -2422,7 +2575,10 @@ describe("openai transport stream", () => {
24222575
encrypted_content: "ciphertext",
24232576
},
24242577
model,
2425-
{ sessionId: "different-session" },
2578+
{
2579+
authProfileId: "openai-codex:oauth",
2580+
sessionId: "session-123",
2581+
},
24262582
),
24272583
),
24282584
},
@@ -2431,7 +2587,7 @@ describe("openai transport stream", () => {
24312587
],
24322588
tools: [],
24332589
} as never,
2434-
{ sessionId: "session-123" },
2590+
{ authProfileId: "openai-codex:oauth", sessionId: "session-123" },
24352591
) as {
24362592
input?: Array<{
24372593
type?: string;
@@ -2444,8 +2600,9 @@ describe("openai transport stream", () => {
24442600
expectRecordFields(reasoningItem, {
24452601
type: "reasoning",
24462602
id: "rs_prior",
2603+
encrypted_content: "ciphertext",
24472604
});
2448-
expect(reasoningItem).not.toHaveProperty("encrypted_content");
2605+
expect(reasoningItem).not.toHaveProperty("__openclaw_replay");
24492606
});
24502607

24512608
it("strips nested encrypted reasoning content from retry payloads without changing ids", () => {

0 commit comments

Comments
 (0)