Skip to content

Commit 8db9f27

Browse files
committed
fix(responses): insert developer message before compaction_trigger
injectDeveloperMessage was appending to raw.input after the compaction_trigger item, causing ChatGPT backend to reject with 'compaction_trigger must be the final input item' when multi-agent guidance fired on ultra/max effort models. Now splices the developer message before compaction_trigger when present.
1 parent 73b5a9b commit 8db9f27

2 files changed

Lines changed: 24 additions & 1 deletion

File tree

src/server/responses.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,15 @@ export function injectDeveloperMessage(parsed: OcxParsedRequest, text: string):
122122
parsed.context.messages.push({ role: "developer", content: text, timestamp: Date.now() });
123123
const raw = parsed._rawBody as { input?: unknown } | undefined;
124124
if (raw && Array.isArray(raw.input)) {
125-
raw.input.push({ type: "message", role: "developer", content: [{ type: "input_text", text }] });
125+
const devItem = { type: "message", role: "developer", content: [{ type: "input_text", text }] };
126+
// compaction_trigger must remain the final input item (codex-rs + ChatGPT backend both
127+
// validate this). Insert the developer message BEFORE the trigger when present.
128+
const last = raw.input[raw.input.length - 1];
129+
if (last && typeof last === "object" && (last as { type?: string }).type === "compaction_trigger") {
130+
raw.input.splice(raw.input.length - 1, 0, devItem);
131+
} else {
132+
raw.input.push(devItem);
133+
}
126134
}
127135
}
128136

tests/multi-agent-compat.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,21 @@ describe("injectDeveloperMessage", () => {
100100
expect((parsed._rawBody as { input: unknown }).input).toBe("plain");
101101
expect(parsed.context.messages.at(-1)!.content).toBe("note");
102102
});
103+
104+
test("inserts BEFORE compaction_trigger so it stays the final input item", () => {
105+
const parsed = parsedFixture({ reasoning: "max" });
106+
const rawBody = parsed._rawBody as { input: unknown[] };
107+
rawBody.input = [
108+
{ type: "message", role: "user", content: "long conversation" },
109+
{ type: "compaction_trigger" },
110+
];
111+
injectDeveloperMessage(parsed, "guidance text");
112+
const input = rawBody.input;
113+
expect(input).toHaveLength(3);
114+
expect((input[1] as { type: string }).type).toBe("message");
115+
expect((input[1] as { role: string }).role).toBe("developer");
116+
expect((input[2] as { type: string }).type).toBe("compaction_trigger");
117+
});
103118
});
104119

105120
describe("sanitizeEncryptedContentInPlace", () => {

0 commit comments

Comments
 (0)