fix(openai-chat): fold the developer role unless a destination records that it accepts it - #5334
Conversation
…s that it accepts it #5213 removed a hostname test that decided the wire role, which was right: a gateway proxying OpenAI accepts `developer` and the hostname cannot say so. The replacement default was wrong in the other direction. Forwarding to every destination assumed each one accepts a role until an operator marks it, so a gateway that rejects it answered `400 role 'developer' is not allowed` and the turn never started. Nothing in this repository could see that. Every test asserted the new default and passed; what broke was outside the tree. The key is now tri-state and the unset state is the safe one: absent folds to `system`, `true` records an upstream that rejects the role, `false` records one that accepts it and the role is forwarded. Placement is untouched in all three cases, which is the contract #5213 established and this change preserves. The regression fixes the gap directly: an undeclared destination must fold, and the role must still never be read from the hostname. The ordering suites declare their destinations rather than asserting the default, because they are about where a reminder sits, not which role carries it.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configurationConfiguration used: Repository: lidge-jun/opencodex/.coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (13)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6c1f859c5c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // what this destination accepts, so the role folds to `system`; `true` means it is known to | ||
| // reject the role; `false` means it is known to accept it and the role is forwarded. Either | ||
| // way the message keeps the slot it arrived in — only the role changes, never the position. | ||
| const developerWireRole = provider.foldDeveloperRoleToSystem === false ? "developer" : "system"; |
There was a problem hiding this comment.
Update the dangling-tool-call regression fixture
When foldDeveloperRoleToSystem is unset, this now emits system, but the shared provider in tests/adapters/openai/openai-chat-dangling-toolcalls.test.ts leaves it unset while lines 109, 170, and 187 still require developer. The focused adapter run therefore fails all three cases. Add foldDeveloperRoleToSystem: false to that placement-focused fixture, or change those assertions if folding is part of their contract, so the required test gate remains green.
AGENTS.md reference: src/AGENTS.md:L24-L26
Useful? React with 👍 / 👎.
| // what this destination accepts, so the role folds to `system`; `true` means it is known to | ||
| // reject the role; `false` means it is known to accept it and the role is forwarded. Either | ||
| // way the message keeps the slot it arrived in — only the role changes, never the position. | ||
| const developerWireRole = provider.foldDeveloperRoleToSystem === false ? "developer" : "system"; |
There was a problem hiding this comment.
Synchronize the authoritative developer-role contract
This reverses the unset configuration's meaning, but structure/providers/chat-compat.md lines 27-32 still states that every destination forwards developer unless the flag is set, and the public-field documentation in src/types/provider.ts lines 849-858 makes the same opposite promise. Because the source-to-doc map assigns src/adapters/ to that structure document, update both descriptions with the new tri-state semantics to prevent future adapter or configuration work from relying on the obsolete contract.
AGENTS.md reference: src/AGENTS.md:L10-L11
Useful? React with 👍 / 👎.
|
✅ Deterministic PR hygiene checks passed. |
…veloper role (#5341) #5334 folds the wire role unless a destination records acceptance. This vector is the one place that asserts the forwarded role, and it lives outside tests/, so the change missed it and dev went red with roles:value_mismatch. Co-authored-by: codex <codex@users.noreply.github.com>
…ling tool_calls suite lidge-jun#5334 made foldDeveloperRoleToSystem tri-state: absent folds the developer role to system on the wire, false records a destination known to accept it. The other ordering suites were updated to declare their destination; this one was missed, so its barrier-placement assertions read role "system" where they expect "developer". These cases are about where the barrier lands relative to repaired tool calls, not which role carries it, so declare foldDeveloperRoleToSystem: false on the shared provider fixture. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…rdering suites d6d8744 (lidge-jun#5334) made developer->system folding the default for destinations that do not record foldDeveloperRoleToSystem and updated the ordering suites it touched, but three suites that assert the developer role on the wire still ran with an undeclared destination, so the PR merge ref fails them. Declare foldDeveloperRoleToSystem: false in openai-chat-dangling-toolcalls, chat-completions-endpoint's caller-fields case, and chat-inline-document-bytes, matching the convention the fix commit established. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…forwarded role (lidge-jun#5334 follow-up) lidge-jun#5334 made the developer wire role tri-state: an undeclared destination folds it to system. Two suites asserting role:"developer" on the Chat wire were missed because they are about tool-result repair ordering and document parts, not role selection — declare the destination, per the convention the change established. Verified: both files fail on dev@600075d2 with system-for-developer wire roles and pass with the declaration. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…-document suite lidge-jun#5334 made foldDeveloperRoleToSystem tri-state: absent folds the developer role to system on the wire, false records a destination known to accept it. The inline-document bytes suite was missed, so its 'keeps its role' assertion read role "system" where it expects "developer". These cases are about the document reaching the wire intact, not which role carries it, so declare foldDeveloperRoleToSystem: false on the shared provider fixture. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ment suite lidge-jun#5334 made foldDeveloperRoleToSystem tri-state: unset now folds developer to system, and only an explicit false records a destination that accepts the role. This suite's chatProvider fixture declared nothing, so its developer-turn assertion folded and failed on the PR merge. Record acceptance, matching the conformance-fixture fix in lidge-jun#5341. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
lidge-jun#5334 folds developer to system for destinations that have not recorded whether they accept the role. Two suites still asserted the forwarded role on an unrecorded fixture: a document-carrying developer turn and the dangling tool_calls barrier placement. Declare foldDeveloperRoleToSystem: false on their destinations, matching the ordering suites, and pin the unrecorded fold on the document path. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ling tool_calls suite lidge-jun#5334 flipped foldDeveloperRoleToSystem to fold on undeclared and updated the other ordering suites to declare their destinations, but missed this file: its provider carried no key, so the deferred developer barriers folded to system and T1/T5/T6 failed on the merge into dev. Declare the destination accepts the role (foldDeveloperRoleToSystem: false); the assertions are about barrier placement, not the fold default. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…toolcalls suite lidge-jun#5334 made foldDeveloperRoleToSystem tri-state: unset now folds developer to system, and only an explicit false records a destination that accepts the role. This suite's provider fixture declared nothing, so its deferred-barrier assertions folded and T1/T5/T6 failed on the PR merge. Record acceptance, matching the inline-document and conformance-fixture updates. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Summary
developermessages are folded tosystemagain unless a destination records that it accepts the role. A gateway that does not accept it answers400 role 'developer' is not allowedand the turn never starts, which is what happened to live traffic after fix: preserve caller constraints on the request path (#5211, #5210, #5212, #5213) #5271.developermessages are folded into the system prompt on every Chat host except api.openai.com #5213 was right to delete the hostname test that decided the role: a gateway proxying OpenAI acceptsdeveloperand the hostname cannot say so. The replacement default was wrong in the other direction — forwarding everywhere assumed every destination accepts a standard role until an operator marks it.foldDeveloperRoleToSystemis now tri-state and the unset state is the safe one. Absent folds,truerecords an upstream that rejects the role,falserecords one that accepts it and the role is forwarded. Placement is unchanged in all three cases, so the contractdevelopermessages are folded into the system prompt on every Chat host except api.openai.com #5213 established still holds.Verification
tests/adapters/openai/openai-chat-developer-position.test.tsnow holds the gap that let this ship: an undeclared destination must fold, and the role must still never be read from the hostname. Placement is asserted across all three declaration states so the role decision can never move a message.Checklist