Skip to content

chore: merge train 260821 — land #2270 (apply_patch on routed Responses destinations) - #2327

Merged
lidge-jun merged 9 commits into
devfrom
codex/merge-train-260821
Aug 22, 2026
Merged

chore: merge train 260821 — land #2270 (apply_patch on routed Responses destinations)#2327
lidge-jun merged 9 commits into
devfrom
codex/merge-train-260821

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Aug 22, 2026

Copy link
Copy Markdown
Owner

Summary

Fifth leg of the merge train: lands #2270 — fix(responses): make apply_patch work on routed Responses destinations plus a boundary hardening test.

Review chain:

  • Adversarial review (inherited model, Bohr) on head 398b7ad: GO-WITH-FIXES (blockers=0), one P2 — the canonical-forward lowering boundary rested on code reading only
  • Hardening commit ec32a8d: negative pin proving the exact canonical Codex forward surface ignores supportsResponsesCustomTools: false and keeps custom tools verbatim
  • Same reviewer re-verdict on ec32a8d: PASS
  • Review confirmed: the reorder fixes a real latent bug (compaction-replayed custom_tool_call reached strict upstreams unlowered); byte-identical non-compaction pin intact; restoration fail-closed

Verification

  • bun test tests/custom-tool-compat.test.ts tests/namespace-tool-compat.test.ts tests/openai-responses-passthrough.test.ts tests/responses-custom-tool-repair.test.ts — 138/138 pass (+ pin file 100/100)
  • bun run typecheck / privacy:scan — pass
  • Full suite on lidge at 668512a: 14233 pass / 16 skip / 0 fail across 893 files, exit 0

Checklist

  • Local CI green (full suite executed remotely on lidge at exact head)
  • Branch includes the latest dev commit (train tracks origin/dev)
  • No open Codex/CodeRabbit findings against this head
  • Ready for review

Summary by CodeRabbit

  • New Features

    • Improved compatibility when routing custom tools, including apply_patch, to providers with different tool capabilities.
    • Providers that do not support native custom tools now receive compatible function-based requests, while supported destinations preserve native tool calls.
    • Improved handling of tool calls and namespaces during request compaction and response restoration.
  • Bug Fixes

    • Corrected provider capability detection and routing behavior for noncanonical destinations.
    • Preserved tool-call history and streamed responses across compatibility transformations.

olddonkey and others added 9 commits August 21, 2026 01:34
`ROUTED_CUSTOM_TOOL_PASSTHROUGH` exempted `apply_patch` from routed
custom-tool lowering unconditionally, so it reached every routed destination
as a `type: "custom"` tool with `custom_tool_call` items. xAI's Responses
endpoint rejects that item type:

  422 Failed to deserialize the JSON body into the target type:
      input[5]: invalid "custom_tool_call" item: missing field `id`

The message is misleading — the id is present. Instrumenting the adapter
showed the item leaving as
`{"type":"custom_tool_call","id":"ctc_abc123","call_id":"c1",...}`; xAI
reports the first field its own parser cannot satisfy rather than the real
problem, which is that it does not accept the item type. Same class as its
"Could not decode the compaction blob" message for a reasoning field, so the
fix is not to generate or preserve ids.

Live A/B against the endpoint — identical body, identical id, only the tool
name differs:

  apply_patch      (exempt from lowering)  -> 422
  my_custom_thing  (lowered to a function) -> 200

Lowering is what makes it work; the exemption is what breaks it. It surfaces
on Codex's compact turn because a real session always contains apply_patch
calls, but a plain replay reproduces it too.

The exemption is not wrong everywhere — the canonical ChatGPT surface speaks
custom_tool_call natively and lowering there would regress it. The defect is
that one unconditional rule about "routed providers" encoded a claim about a
single destination's capability. Add `supportsResponsesCustomTools`,
following the existing `supportsOpenAiWebSearchToolFields` shape: declared on
the registry row and the provider config, filled only when unset, and
consumed as an explicit denial. Absent or true keeps today's behaviour
byte-identical; only xAI declares false.

The response path needed no special case: it is name-generic, so once
apply_patch joins the converted set the existing repair restores the
function_call and its streaming argument events to a custom_tool_call with
the original call id.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every routed lowering step derives its plan from the tool declarations, and
the compaction body build deletes them. It ran first, so on a compaction turn
the plan was empty and replayed call items reached the wire in their private
shapes. Against xAI:

  422 Failed to deserialize the JSON body into the target type:
      input[5]: invalid "custom_tool_call" item: missing field `id`

The id is present; xAI reports the first field its own parser cannot satisfy
rather than the real problem, which is that it does not accept the item type.

Instrumented the adapter to pin the mechanism: with declarations present the
call item is converted; with them absent, or on a compaction turn, it goes out
raw. Reordering locally produced `function_call` / `function_call_output` with
`tools` still absent and the compact prompt still appended.

This is the second time this exact shape has been fixed here — a replayed
namespace key survived for the same reason. That fix taught one lowering step
to cope; this one fixes the pipeline, so the next private field added does not
need its own workaround. The invariant is now stated at the call site: the
compaction body build removes the tool surface and must be the last routed
transform.

Two effects beyond the call items, both improvements: `promoteClientLoadedTools`
could previously reintroduce top-level `tools` after compaction had removed
them, which running compaction last now prevents; and namespace-collision
validation runs before the declarations are deleted. Non-compaction output is
byte-identical, pinned by an exact comparison test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 59d0cde7f75f0e645a12ec44a388609dfba50ce6)
The namespace-replay restore test verified the restored custom_tool_call events
but never checked that the stream still ends with data: [DONE], so a regression
that drops the terminal marker would have passed. The sibling lowering test
already asserts it; match that.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017zpLCh4eEms6un3VjapRgL
Forward auth is not an OpenAI-destination identity. A noncanonical
forward provider that denies native custom tools must still convert
apply_patch. Pin the adapter serialization and the handleResponses path.
…t explicit denial

Adversarial review P2: the lowering boundary rested on code reading only —
add a negative pin proving the exact canonical Codex forward surface ignores
supportsResponsesCustomTools: false and keeps custom tools verbatim.
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner August 22, 2026 00:51
@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the chore Maintenance, CI, tests, refactors, or build changes (not a user-facing bug or feature). label Aug 22, 2026
@lidge-jun
lidge-jun merged commit 7d1b2ca into dev Aug 22, 2026
8 of 9 checks passed
@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 157237c1-3a16-462e-9530-23440ff3d1a5

📥 Commits

Reviewing files that changed from the base of the PR and between 6e2a6b6 and c7f341a.

📒 Files selected for processing (14)
  • devlog/_plan/260821_bug_merge_train/000_triage_matrix.md
  • devlog/_plan/260821_bug_merge_train/060_merge_2270.md
  • src/adapters/openai-responses.ts
  • src/providers/derive.ts
  • src/providers/registry.ts
  • src/responses/custom-tool-compat.ts
  • src/responses/namespace-tool-compat.ts
  • src/router.ts
  • src/types/provider.ts
  • structure/04_transports-and-sidecars.md
  • tests/custom-tool-compat.test.ts
  • tests/namespace-tool-compat.test.ts
  • tests/openai-responses-passthrough.test.ts
  • tests/responses-custom-tool-repair.test.ts

📝 Walkthrough

Walkthrough

The change adds provider capability routing for Responses custom tools. It gates apply_patch passthrough, reorders routed compaction transformations, preserves restoration aliases, and adds unit and integration coverage.

Changes

Responses custom-tool capability routing

Layer / File(s) Summary
Provider capability contract and resolution
src/types/provider.ts, src/providers/registry.ts, src/providers/derive.ts, src/router.ts
Providers can declare supportsResponsesCustomTools. Registry capabilities populate unresolved provider configuration, and xAI disables native Responses custom tools.
Capability-gated tool transformation flow
src/responses/custom-tool-compat.ts, src/adapters/openai-responses.ts, src/responses/namespace-tool-compat.ts, structure/04_transports-and-sidecars.md
Custom-tool passthrough now depends on provider support. Routed compaction lowers tools and namespaces before removing the tool surface.
Routing, restoration, and validation coverage
tests/custom-tool-compat.test.ts, tests/openai-responses-passthrough.test.ts, tests/responses-custom-tool-repair.test.ts, tests/namespace-tool-compat.test.ts, devlog/_plan/260821_bug_merge_train/*
Tests cover capability inheritance, tool lowering, compaction serialization, streamed restoration, and forward-auth routing. Merge records document the validation results.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant routeModel
  participant openaiResponses
  participant Upstream
  Client->>routeModel: submit Responses request
  routeModel->>openaiResponses: resolve provider capability
  openaiResponses->>openaiResponses: lower unsupported custom tools
  openaiResponses->>Upstream: send transformed request
  Upstream-->>openaiResponses: stream function-call events
  openaiResponses-->>Client: restore custom-tool events
Loading

Suggested reviewers: ingwannu

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/merge-train-260821

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.

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

Labels

chore Maintenance, CI, tests, refactors, or build changes (not a user-facing bug or feature).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants