fix(openai): retain cache markers and pairing overrides

This commit is contained in:
Alexander Kirilin
2026-07-23 17:04:03 -04:00
parent 7b0af840a4
commit a08f7b2cc2
@@ -1360,13 +1360,6 @@ describe("OpenAI responses history payload", () => {
const model = getOpenAIReasoningModel("openai", "gpt-5-mini");
const payload = (await captureResponsesPayload(model, context)) as { input?: unknown[] };
const nonStrictDefaultModel: Model<"openai-responses"> = {
...model,
compat: { ...model.compat, strictResponsesPairing: false },
};
const overriddenPayload = (await captureResponsesPayload(nonStrictDefaultModel, context, undefined, {
strictResponsesPairing: true,
})) as { input?: unknown[] };
const orphanSurvivors = (payload.input ?? []).filter(item => {
if (!item || typeof item !== "object") return false;
@@ -1374,12 +1367,6 @@ describe("OpenAI responses history payload", () => {
return candidate.type === "function_call_output" && candidate.call_id === orphanCallId;
});
expect(orphanSurvivors).toEqual([]);
const overriddenOrphanSurvivors = (overriddenPayload.input ?? []).filter(item => {
if (!item || typeof item !== "object") return false;
const candidate = item as { type?: unknown; call_id?: unknown };
return candidate.type === "function_call_output" && candidate.call_id === orphanCallId;
});
expect(overriddenOrphanSurvivors).toEqual([]);
const pairedOutputs = (payload.input ?? []).filter(item => {
if (!item || typeof item !== "object") return false;
@@ -1400,4 +1387,63 @@ describe("OpenAI responses history payload", () => {
}) as { content?: string } | undefined;
expect(note?.content).toContain(orphanOutput);
});
it("honors strict pairing overrides against opposite catalog defaults", async () => {
const orphanCallId = "call_override_orphan";
const orphanOutput = "orphan result";
const context: Context = {
messages: [
{
role: "assistant",
content: [{ type: "toolCall", id: orphanCallId, name: "read", arguments: { path: "README.md" } }],
api: "openai-responses",
provider: "openai",
model: "gpt-5-mini",
usage: issue5002ZeroUsage,
stopReason: "toolUse",
providerPayload: createOpenAIResponsesHistoryPayload(
"openai",
[{ type: "message", role: "assistant", content: [{ type: "output_text", text: "snapshot" }] }],
false,
),
timestamp: Date.now(),
},
{
role: "toolResult",
toolCallId: orphanCallId,
toolName: "read",
content: [{ type: "text", text: orphanOutput }],
isError: false,
timestamp: Date.now(),
},
{ role: "user", content: "Continue", timestamp: Date.now() },
],
};
const catalogNonStrictModel = getOpenAIReasoningModel("openai", "gpt-5-mini");
const catalogStrictModel: Model<"openai-responses"> = {
...catalogNonStrictModel,
compat: { ...catalogNonStrictModel.compat, strictResponsesPairing: true },
};
const strictOverridePayload = (await captureResponsesPayload(catalogNonStrictModel, context, undefined, {
strictResponsesPairing: true,
})) as { input?: unknown[] };
const nonStrictOverridePayload = (await captureResponsesPayload(catalogStrictModel, context, undefined, {
strictResponsesPairing: false,
})) as { input?: unknown[] };
const orphanNote = (input: unknown[] | undefined): { content?: string } | undefined =>
input?.find(item => {
if (!item || typeof item !== "object") return false;
const candidate = item as { type?: unknown; role?: unknown; content?: unknown };
return (
candidate.type === "message" &&
candidate.role === "assistant" &&
typeof candidate.content === "string" &&
candidate.content.includes(orphanCallId) &&
candidate.content.includes(orphanOutput)
);
}) as { content?: string } | undefined;
expect(orphanNote(strictOverridePayload.input)?.content).toContain("[Orphan read result;");
expect(orphanNote(nonStrictOverridePayload.input)?.content).toContain("[Orphan tool result;");
});
});