backfill encrypted_content from response.completed for missing reasoning blocks (#6608)

fixes #6409
This commit is contained in:
David Brailovsky
2026-07-13 17:27:36 +02:00
committed by GitHub
parent b084d2fb39
commit 1f0dbc008c
2 changed files with 157 additions and 0 deletions
@@ -337,6 +337,7 @@ export async function processResponsesStream<TApi extends Api>(
): Promise<void> {
let sawTerminalResponseEvent = false;
const outputSlots = new Map<number, ResponsesOutputSlot>();
const reasoningBlocksById = new Map<string, ThinkingContent>();
const getSlot = <TType extends ResponsesOutputSlot["type"]>(
outputIndex: number,
type: TType,
@@ -388,10 +389,29 @@ export async function processResponsesStream<TApi extends Api>(
const getOrCreateSlot = (outputIndex: number, item: ResponseOutputItem): ResponsesOutputSlot | undefined => {
return outputSlots.get(outputIndex) ?? createSlot(outputIndex, item);
};
// Azure OpenAI can omit reasoning.encrypted_content from response.output_item.done
// and provide it only in response.completed.response.output. Backfill the
// persisted reasoning signature from the terminal response to keep store:false
// multi-turn replay stateless. See https://github.com/earendil-works/pi/issues/6409.
const backfillReasoningSignatures = (responseOutput: ResponseOutputItem[]): void => {
for (const item of responseOutput) {
if (item.type !== "reasoning" || !item.encrypted_content) continue;
const block = reasoningBlocksById.get(item.id);
if (!block?.thinkingSignature) continue;
const storedItem = JSON.parse(block.thinkingSignature) as ResponseReasoningItem;
if (storedItem.encrypted_content) continue;
block.thinkingSignature = JSON.stringify({
...storedItem,
encrypted_content: item.encrypted_content,
});
}
};
const finalizeResponse = (
response: Extract<ResponseStreamEvent, { type: "response.completed" | "response.incomplete" }>["response"],
): void => {
sawTerminalResponseEvent = true;
backfillReasoningSignatures(response.output ?? []);
if (response?.id) {
output.responseId = response.id;
}
@@ -519,6 +539,7 @@ export async function processResponsesStream<TApi extends Api>(
const contentText = item.content?.map((c) => c.text).join("\n\n") || "";
slot.block.thinking = summaryText || contentText || slot.block.thinking;
slot.block.thinkingSignature = JSON.stringify(item);
reasoningBlocksById.set(item.id, slot.block);
stream.push({
type: "thinking_end",
contentIndex: slot.contentIndex,