Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit f8a2873

Browse files
committed
Prevent xAI replay from exposing persisted Anthropic reasoning
The xAI Responses path reuses convertToResponsesApiInput() when replaying persisted conversation history. Anthropic thinking blocks were being flattened into ordinary assistant-visible output_text entries, which changes conversation semantics and leaks hidden reasoning into later prompt context. This keeps the fix intentionally narrow: persisted Anthropic thinking blocks are now skipped for Responses replay, and a regression test covers the behavior. Constraint: Keep the change scoped to the replay transform without changing provider selection or task persistence Rejected: Preserve thinking by converting it to [Thinking] output_text | turns hidden reasoning into visible assistant content Confidence: medium Scope-risk: narrow Reversibility: clean Directive: If a provider-specific hidden reasoning replay format is added later, prefer that over flattening Anthropic thinking into visible text Tested: pnpm --dir src exec vitest run api/transform/__tests__/responses-api-input.spec.ts api/providers/__tests__/xai.spec.ts Not-tested: Full end-to-end provider-switch replay with live xAI credentials
1 parent cb83656 commit f8a2873

2 files changed

Lines changed: 27 additions & 8 deletions

File tree

src/api/transform/__tests__/responses-api-input.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,28 @@ describe("convertToResponsesApiInput", () => {
280280
}),
281281
)
282282
})
283+
284+
it("should not replay Anthropic thinking blocks as assistant-visible text", () => {
285+
const messages: Anthropic.Messages.MessageParam[] = [
286+
{
287+
role: "assistant",
288+
content: [
289+
{ type: "thinking", thinking: "SECRET_CHAIN_OF_THOUGHT", signature: "sig-1" } as any,
290+
{ type: "text", text: "visible answer" },
291+
],
292+
},
293+
]
294+
295+
const result = convertToResponsesApiInput(messages)
296+
297+
expect(result).toEqual([
298+
{
299+
type: "message",
300+
role: "assistant",
301+
content: [{ type: "output_text", text: "visible answer" }],
302+
},
303+
])
304+
})
283305
})
284306

285307
describe("multi-turn conversations", () => {

src/api/transform/responses-api-input.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,11 @@ export function convertToResponsesApiInput(messages: Anthropic.Messages.MessageP
5353
})
5454
break
5555
case "thinking":
56-
// Include reasoning if it has content
57-
if ((part as any).thinking && (part as any).thinking.trim().length > 0) {
58-
input.push({
59-
type: "message",
60-
role: "assistant",
61-
content: [{ type: "output_text", text: `[Thinking] ${(part as any).thinking}` }],
62-
})
63-
}
56+
// Anthropic thinking blocks represent hidden reasoning. The
57+
// Responses API input format does not have a compatible hidden
58+
// reasoning representation for these persisted blocks, so replaying
59+
// them as normal assistant-visible text changes conversation
60+
// semantics. Skip them instead of flattening them into output_text.
6461
break
6562
}
6663
}

0 commit comments

Comments
 (0)