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
10 changes: 8 additions & 2 deletions src/chat/inbound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,10 +310,16 @@ export function chatCompletionsToResponsesBody(raw: unknown): Rec {
// Responses assistant item schema admits only output content blocks, so there
// is no attachment point on the message itself, and the parser buffers a
// reasoning item and prepends it to the NEXT assistant message. Emitting it
// here keeps that adjacency intact.
// here keeps that adjacency intact. OpenAI requires `summary` on replayed
// reasoning items; keep it empty because this is raw provider reasoning, not
// a caller-authorized user-visible summary.
const reasoningText = assistantReasoningText(msg);
if (reasoningText !== undefined) {
input.push({ type: "reasoning", content: [{ type: "reasoning_text", text: reasoningText }] });
input.push({
type: "reasoning",
summary: [],
content: [{ type: "reasoning_text", text: reasoningText }],
});
}
const blocks = assistantContentToBlocks(msg.content);
if (blocks.length > 0) input.push({ type: "message", role: "assistant", content: blocks });
Expand Down
4 changes: 3 additions & 1 deletion structure/data-planes/inbound-compat.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,9 @@ An assistant turn's `reasoning_content` or `reasoning_details` is carried into t
projection as a `reasoning` input item emitted immediately before its assistant
message, matching the parser's buffer-and-prepend adjacency. Only representable
plaintext crosses: no signature, encrypted payload or provider item id is
reconstructed, because those attest to content this proxy never received. Opaque
reconstructed, because those attest to content this proxy never received. The item
always carries `summary: []`, which satisfies the Responses replay shape without
publishing raw provider reasoning as a user-visible summary. Opaque
reasoning replay across a Chat boundary remains unimplemented by design.
`presence_penalty` and `frequency_penalty` are carried too; per-model
`noPenaltyModels` opt-outs still apply at the adapter.
Expand Down
4 changes: 4 additions & 0 deletions tests/responses/chat-inbound-reasoning-replay.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ describe("F6 assistant reasoning survives translation", () => {
const idx = out.findIndex(i => i.type === "reasoning");

expect(idx).toBeGreaterThanOrEqual(0);
expect(out[idx]!.summary).toEqual([]);
expect(out[idx]!.content).toEqual([{ type: "reasoning_text", text: "prior analysis" }]);
// Adjacency matters: the parser prepends a buffered reasoning item to the NEXT
// assistant message, so it must sit immediately before it.
Expand All @@ -57,6 +58,9 @@ describe("F6 assistant reasoning survives translation", () => {
test("no signature, encrypted payload or item id is forged", () => {
const item = items(body([USER, { role: "assistant", content: "a", reasoning_content: "t" }])).find(i => i.type === "reasoning")!;

// Raw provider reasoning must not be reclassified as a visible summary merely to
// satisfy the Responses wire requirement.
expect(item.summary).toEqual([]);
expect(item.signature).toBeUndefined();
expect(item.encrypted_content).toBeUndefined();
expect(item.id).toBeUndefined();
Expand Down
Loading