From fb972db2d836cbd008c2c4588b2b9f9fbe203e83 Mon Sep 17 00:00:00 2001 From: luvs01 Date: Fri, 28 Aug 2026 13:16:19 +0900 Subject: [PATCH] fix(responses): restrict canonical system folding --- src/adapters/openai-responses.ts | 11 +++++++++-- .../responses-forward-prompt-envelope.test.ts | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/adapters/openai-responses.ts b/src/adapters/openai-responses.ts index d69d2909b1..29681a941d 100644 --- a/src/adapters/openai-responses.ts +++ b/src/adapters/openai-responses.ts @@ -1190,6 +1190,13 @@ function canonicalForwardSystemText(item: Record): string | nul return text; } +/** Only message items may carry privileged system instructions. */ +function isCanonicalForwardSystemMessage(item: unknown): item is Record { + return isPlainObject(item) + && (item.type === undefined || item.type === "message") + && item.role === "system"; +} + /** * The public Responses API accepts input system messages and `truncation`, but the canonical * ChatGPT Codex forward endpoint rejects both. Fold only fully textual system messages into the @@ -1213,7 +1220,7 @@ function normalizeCanonicalForwardPromptEnvelope(body: unknown): unknown { let sawSystemMessage = false; let canFoldAllSystemMessages = true; for (const item of input) { - if (!isPlainObject(item) || item.role !== "system") continue; + if (!isCanonicalForwardSystemMessage(item)) continue; sawSystemMessage = true; const text = canonicalForwardSystemText(item); if (text === null) { @@ -1227,7 +1234,7 @@ function normalizeCanonicalForwardPromptEnvelope(body: unknown): unknown { const next: Record = { ...body }; if (stripTruncation) delete next.truncation; if (sawSystemMessage && canFoldAllSystemMessages) { - next.input = input.filter(item => !isPlainObject(item) || item.role !== "system"); + next.input = input.filter(item => !isCanonicalForwardSystemMessage(item)); const folded = foldedText.join("\n\n"); if (folded !== "") { const existing = typeof body.instructions === "string" ? body.instructions : ""; diff --git a/tests/responses-forward-prompt-envelope.test.ts b/tests/responses-forward-prompt-envelope.test.ts index 7d52d45151..b5f6e9c408 100644 --- a/tests/responses-forward-prompt-envelope.test.ts +++ b/tests/responses-forward-prompt-envelope.test.ts @@ -86,6 +86,25 @@ describe("canonical ChatGPT forward prompt envelope", () => { expect(body.input).toEqual(input); }); + test("does not promote a non-message item with a system role", () => { + const externalAgentMessage = { + type: "agent_message", + role: "system", + content: [{ type: "input_text", text: "external agent content" }], + }; + const body = outboundBody(canonicalForward, { + model: "gpt-5.6-luna", + instructions: "Existing instructions", + input: [ + { type: "message", role: "system", content: "Trusted system instruction" }, + externalAgentMessage, + ], + }); + + expect(body.instructions).toBe("Existing instructions\n\nTrusted system instruction"); + expect(body.input).toEqual([externalAgentMessage]); + }); + test.each([ { name: "key-auth public Responses provider",