Skip to content

fix(openai-chat): Pi developer role and duplicated serialized tool calls (lane F2) - #5674

Merged
lidge-jun merged 2 commits into
devfrom
codex/260923-bundle-f2-openai-chat-compat
Sep 23, 2026
Merged

lidge-jun merged 2 commits into
devfrom
codex/260923-bundle-f2-openai-chat-compat

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Sep 23, 2026

Copy link
Copy Markdown
Owner

Summary

Lane F2 (openai-chat compatibility) of the 260923 PR-consolidation round. Two commits:

  • Pi/omo developer role (Pi export: openai-chat providers should emit supportsDeveloperRole: false (or demote developer→system in chat dialect) #5664). Pi sends its system prompt as developer for reasoning models, and the native /v1/chat/completions route forwards roles verbatim unless a destination records foldDeveloperRoleToSystem, so upstreams that reject the role (DashScope compatible-mode: developer is not one of [...]) failed every request. Each export rewrites the whole provider block, so a hand-set fix did not survive. The Pi export (and omo, which stays byte-identical to Pi) now writes compat.supportsDeveloperRole: false on both the export and the managed-contribution path. The key is in Pi's model-config schema (checked in the installed pi-coding-agent 0.83.0) and senpi documents it (docs/models.md). Prime and Aside keep their compat-free block. The Pi guide is updated in all 8 locales.
  • Duplicated serialized tool calls (carries fix(codex-home): avoid WSL config import cycle #5548). Some Chat gateways send one call twice: as a bare <tool_call><function=…>…</function></tool_call> block in content and as a structured tool_calls entry, sometimes with the block body also prefixed onto the JSON arguments. Codex showed the raw call syntax as the answer. The adapter now holds a possible bare block and removes it only when the function name and freeform body match a structured call in the same response; the argument prefix is repaired only for that exact shape. Quoted, fenced, inline, indented and mismatched markup stays byte-exact. Rebuilt on dev's inline <think> splitter: reconciliation sees answer text only, reasoning that arrives behind a held block is queued in place with a heartbeat (order kept, no early leak, no stall), and buffered responses replay through the same buffer. The glue lives in src/adapters/openai-chat/serialized-tool-call-content.ts so openai-chat.ts stays under its ratchet (816/822). Decision record renumbered ADR-5548 (ADR-0099 is taken). fix(codex-home): avoid WSL config import cycle #5548's unrelated src/codex/home.ts import-cycle, WSL, and service-test edits are not carried.

Closes #5664

Partially carries #5548, which stays open (PARTIAL: the src/codex/home.ts import-cycle fix, its WSL test and structure/codex-home.md note, and the Docker/systemd service-test edits were left out as unrelated to this defect; they need their own review before #5548 is closed)

Co-authored-by: Vadevious 56196048+Vadevious@users.noreply.github.com

Assessed and excluded

Item Outcome
#5539 effort wire mapper Excluded: owner decision. Real gap, but it reverses two tested contracts (native Chat preserves an unqualified caller spelling; unconfigured routed ultra passes through, openai-responses-passthrough.test.ts:867) and drops provider spellings; the 400 is unreproduced. Needs a destination-policy design.
#5204 inline <think> Already on dev: e9643875f0 (#5619) added the opt-in inlineThinkTagModels splitting this issue asks for.
#5421 DeepSeek reasoning passback Partly on dev (e9643875f0 widened the placeholder gate); deepseek-v4-pro is deliberately retired from the thinking sets; the remaining resumed-history case needs a reproduction.
#5499 MiMo XML envelope Partial overlap: this PR removes the envelope when the gateway also sends the matching structured call. The <parameter=input> residue inside custom_tool_call.input and the intermittent leak need a reproduction; src/adapters/command-code-tool-text.ts is the model for a model-scoped parser.
#5501 MiMo web_search Excluded: the reporter has not supplied the model, tool declaration, or a capability reference the maintainer asked for.

Verification

The owner reserves the full local suite for after every lane in this round has landed, so it was not run here. Focused runs on the final tree:

  • bun test tests/adapters/openai tests/responses tests/config/client-config-export.test.ts tests/clients/prime-client.test.ts tests/clients/omo-client.test.ts tests/test-layout.test.ts tests/test-layout-tooling.test.ts tests/ci-workflows/structure-ssot.test.ts tests/ci-workflows/file-size-ratchet.test.ts — 3718 pass, 0 fail (171 files).
  • bun run typecheck, bun run structure:check, bun run privacy:scan, git diff --check origin/dev...HEAD — pass.
  • New regressions in tests/adapters/openai/openai-chat-serialized-tool-call-think.test.ts: duplicate removal behind a think block (streamed + buffered), different-input blocks stay byte-exact, same-line prose across a think section, event order across interleaved think sections, a duplicate between two think sections with a late structured call, context after a drain, and heartbeats while reasoning is queued.
  • tests/clients/client-lifecycle-lock.test.ts fails in this worktree only because it lives under the real ~/.codex and the test-home cleanup guard refuses to delete there; it touches nothing in this PR. CI covers it.
  • Review: gpt-6-sol soundness reviews of fix(codex-home): avoid WSL config import cycle #5548 and [WRONG BRANCH] fix(effort): run wire mapper on unpinned/unconfigured effort values #5539, then four adversarial rounds on the lane diff; every finding (buffered false suppression, event ordering, early duplicate leak, context after drain, stall heartbeat, EOF) was fixed with a regression test. Final verdict: PASS.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Summary by CodeRabbit

  • Bug Fixes
    • Prevented matching serialized tool-call markup from appearing alongside the corresponding structured tool call in chat responses. Unmatched or ambiguous content remains visible, and held text is retained when a stream ends with an error.
  • New Features
    • Pi provider exports now configure system prompts to use the system role instead of developer, improving compatibility with upstreams that reject the developer role.
  • Documentation
    • Updated Pi integration guides across supported languages to explain the new compatibility setting.

lidge-jun and others added 2 commits September 23, 2026 19:02
)

Pi sends its system prompt as `developer` for reasoning models. The native
Chat Completions route forwards caller roles verbatim unless a destination
has recorded foldDeveloperRoleToSystem, so upstreams that reject the role
(DashScope compatible-mode answers "developer is not one of [...]") failed
every request. Users could not work around it: every export rewrites the
whole provider block and drops a hand-set compat key.

The Pi export now writes compat.supportsDeveloperRole: false next to the
session-affinity key, on both the export and the managed-contribution path.
omo keeps its byte-identical-to-Pi contract because senpi documents the same
key. Prime and Aside keep their compat-free block.
…ble text (#5548)

Some Chat gateways send one model-produced call twice: as a bare
<tool_call><function=...>...</function></tool_call> block in content and as a
structured tool_calls entry, sometimes with the block body prefixed onto the
JSON arguments too. Codex then showed the raw call syntax as the answer and
could receive malformed arguments.

The adapter now holds a possible bare block (outside Markdown fences, quotes and
inline code) and removes it only when its function name and freeform body match
a structured call in the same response; the argument prefix is repaired only for
that exact duplicated shape. Mismatched markup stays byte-exact, held text is
drained on every terminal path, and the held bytes use the translator budget.

Carried from #5548 and rebuilt on dev's inline <think> splitter: reconciliation
sees only answer text; reasoning that arrives behind a held block is queued in
place (with a heartbeat) so order is kept and the duplicate is not exposed
early; buffered responses replay through the same buffer so both paths share
one rule set. The adapter glue lives in serialized-tool-call-content.ts to stay
under the file-size ratchet. #5548's unrelated codex/home.ts import-cycle,
WSL test, and service test edits are not carried.

Co-authored-by: Vadevious <56196048+Vadevious@users.noreply.github.com>
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner September 23, 2026 10:17
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 23, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-23T10:22:47.988709Z caf43c9 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions github-actions Bot added the intake: hygiene-blocked Deterministic PR hygiene checks failed label Sep 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Deterministic hygiene checks failed.

  • missing_coauthor_credit — This pull request says it reimplements, supersedes, carries, or rebases another author's pull request, but no Co-authored-by trailer names that author. Prose in a commit body is not read by anything; the trailer is what GitHub counts. Add it to the description or a commit, or obtain attribution-approved. Paths: #5205, #5566.

@github-actions github-actions Bot added the bug Something isn't working label Sep 23, 2026
@github-actions

github-actions Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

✅ READY

  • all PR quality gates passed.

Hygiene

Deterministic PR hygiene checks passed.

@coderabbitai

coderabbitai Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Repository: lidge-jun/opencodex/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 528ac363-a956-427e-a2c3-878f26faeb81

📥 Commits

Reviewing files that changed from the base of the PR and between 685321e and caf43c9.

📒 Files selected for processing (23)
  • docs-site/src/content/docs/fr/guides/pi.md
  • docs-site/src/content/docs/guides/pi.md
  • docs-site/src/content/docs/ja/guides/pi.md
  • docs-site/src/content/docs/ko/guides/pi.md
  • docs-site/src/content/docs/ru/guides/pi.md
  • docs-site/src/content/docs/tr/guides/pi.md
  • docs-site/src/content/docs/zh-cn/guides/pi.md
  • docs-site/src/content/docs/zh-tw/guides/pi.md
  • scripts/test-layout/layout.json
  • src/adapters/openai-chat.ts
  • src/adapters/openai-chat/serialized-tool-call-content.ts
  • src/clients/config-export.ts
  • structure/decisions/ADR-5548-serialized-tool-call-content.md
  • structure/providers-and-adapters.md
  • structure/providers/chat-compat.md
  • tests/adapters/openai/openai-chat-sanitization-review-regressions.test.ts
  • tests/adapters/openai/openai-chat-serialized-tool-call-content.test.ts
  • tests/adapters/openai/openai-chat-serialized-tool-call-think.test.ts
  • tests/clients/omo-client.test.ts
  • tests/clients/prime-client.test.ts
  • tests/config/client-config-export.test.ts
  • tests/fixtures/test-layout-expected.json
  • tests/responses/responses-chat-tool-call-content.test.ts
 ___________________________________________________________________________________________________
< Test your software, or your users will. Test ruthlessly. Don't make your users find bugs for you. >
 ---------------------------------------------------------------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions
github-actions Bot marked this pull request as draft September 23, 2026 10:17
@github-actions github-actions Bot removed the intake: hygiene-blocked Deterministic PR hygiene checks failed label Sep 23, 2026
@github-actions
github-actions Bot marked this pull request as ready for review September 23, 2026 10:19

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: caf43c9a52

ℹ️ 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".

Comment on lines +131 to +132
const nextBytes = Buffer.byteLength(next);
const reservation = this.budget.reserveTransient(nextBytes, { kind: "live_transient" });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reserve only the growth when completing a split header

When a serialized-call header is split before its closing > (for example, "<tool_call><fun" followed by "ction=exec>"), the first fragment is already retained, but replace reserves the entire combined buffer before releasing those old bytes. The translator budget therefore temporarily counts oldBytes + nextBytes; with a 26-byte budget, the 15-byte and 11-byte chunks already reproduce a translation_buffer_limit even though the final retained buffer is exactly 26 bytes. Larger valid streams can consequently return a 502 solely because of transport chunk boundaries. Use the append/growth accounting path when next extends the retained prefix, while retaining atomic replacement for unrelated text.

AGENTS.md reference: src/AGENTS.md:L17-L19

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant