diff --git a/src/adapters/openai-chat/messages.ts b/src/adapters/openai-chat/messages.ts index 8e88dabc6b0..6226e4bed2d 100644 --- a/src/adapters/openai-chat/messages.ts +++ b/src/adapters/openai-chat/messages.ts @@ -123,20 +123,15 @@ export function messagesToChatFormat(parsed: OcxParsedRequest, provider: OcxProv const nativeOpenAI = isNativeOpenAIChatTarget(provider); // Hoisting a newly appended reminder rewrites the reusable prompt prefix. // Keep this compatibility exception on the destination/model tested with OCG. - const chronologicalSystem = parsed.modelId === "deepseek-v4.1-flash" - && registryEntryForProviderDestination(provider)?.id === "opencode-go"; - const toolCatalogNudge = shouldInjectNonOpenAIToolCatalogNudge(provider) - ? buildNonOpenAIToolCatalogNudgeForTools(context.tools, options.toolChoice) - : undefined; - const developerSystemParts = nativeOpenAI || chronologicalSystem - ? [] - : context.messages - .map(developerSystemText) - .filter((part): part is string => part !== undefined && part.length > 0); - const systemParts = [ - ...(context.systemPrompt ?? []), - ...developerSystemParts, - ...(toolCatalogNudge ? [toolCatalogNudge] : []), + // Preserving chronological order on the Chat Completions wire prevents in-conversation + // instructions from being moved out of the timeline into the prompt preamble (#5213). + const toolCatalogNudge = shouldInjectNonOpenAIToolCatalogNudge(provider) + ? buildNonOpenAIToolCatalogNudgeForTools(context.tools, options.toolChoice) + : undefined; + const systemParts = [ + ...(context.systemPrompt ?? []), + ...(toolCatalogNudge ? [toolCatalogNudge] : []), + ]; ]; if (systemParts.length > 0) { const wireModelId = provider.modelSuffixBracketStrip @@ -154,7 +149,6 @@ export function messagesToChatFormat(parsed: OcxParsedRequest, provider: OcxProv const hasImages = parts?.some(p => p.type === "image") ?? false; let chatMsg: Record; if (msg.role === "developer" && !hasImages) { - if (!nativeOpenAI && !chronologicalSystem) break; const text = typeof msg.content === "string" ? msg.content : parts!.map(p => (p as OcxTextContent).text).join(""); @@ -344,3 +338,4 @@ export function safeToolName(name: string | undefined): string { export function emptyAssistantContent(provider: OcxProviderConfig): string | { type: "text"; text: string }[] { return isVolcengineArkPaygChatTarget(provider) ? [{ type: "text", text: "" }] : ""; } + diff --git a/tests/adapters/openai/openai-chat-system-order.test.ts b/tests/adapters/openai/openai-chat-system-order.test.ts index def3179d814..d96e27f0874 100644 --- a/tests/adapters/openai/openai-chat-system-order.test.ts +++ b/tests/adapters/openai/openai-chat-system-order.test.ts @@ -21,7 +21,7 @@ function buildMessages(context: OcxParsedRequest["context"]): Array { - test("folds interleaved developer reminders into one leading system message", () => { + test("preserves developer reminder position as chronological system message on non-native targets", () => { const messages = buildMessages({ systemPrompt: ["base instructions"], messages: [ @@ -44,13 +44,14 @@ describe("openai-chat system message ordering", () => { expect(messages[0]).toEqual({ role: "system", - content: "base instructions\n\nfirst reminder\n\nsecond reminder", + content: "base instructions", }); - expect(messages.slice(1).map(message => message.role)).toEqual(["user", "assistant", "user"]); - expect(messages.slice(1).some(message => message.role === "system")).toBe(false); + expect(messages.map(message => message.role)).toEqual(["system", "user", "system", "assistant", "system", "user"]); + expect(messages[2]).toEqual({ role: "system", content: "first reminder" }); + expect(messages[4]).toEqual({ role: "system", content: "second reminder" }); }); - test("keeps tool calls and results adjacent when a developer reminder follows the call", () => { + test("preserves developer reminders in chronological order around tool calls on non-native targets", () => { const messages = buildMessages({ messages: [ { role: "user", content: "inspect", timestamp: 0 }, @@ -72,8 +73,8 @@ describe("openai-chat system message ordering", () => { ], }); - expect(messages[0]).toEqual({ role: "system", content: "remember the policy" }); - expect(messages.map(message => message.role)).toEqual(["system", "user", "assistant", "tool"]); + expect(messages.map(message => message.role)).toEqual(["user", "assistant", "system", "tool"]); + expect(messages[2]).toEqual({ role: "system", content: "remember the policy" }); expect(messages[3]).toMatchObject({ role: "tool", tool_call_id: "call_1" }); }); @@ -216,3 +217,4 @@ describe("OpenCode Go DeepSeek chronological system messages", () => { .toEqual([{ role: "user", content: "Inspect the synthetic project." }]); }); }); +