Skip to content

fix(openai-router): suppress duplicate output-item envelopes on native passthrough (R6.7c) - #1376

Merged
slin1237 merged 3 commits into
mainfrom
fix/r6-07c-native-passthrough-gate
Apr 24, 2026
Merged

slin1237 merged 3 commits into
mainfrom
fix/r6-07c-native-passthrough-gate

Conversation

@slin1237

@slin1237 slin1237 commented Apr 24, 2026 •

Copy link
Copy Markdown
Member

Description

Problem

R6.7b (PR #1371) tightened the streaming output_item.done suppression gate in the OpenAI responses router with a belongs_to_pending_call guard so hosted-tool items the handler never registered could not have their umbrella silently dropped (the tool loop only re-emits umbrellas for items it actually dispatched).

That guard is correct for the MCP-dispatch path, but it silently regresses the OpenAI cloud "native passthrough" case — where upstream emits a hosted tool-call item (image_generation_call, web_search_call, code_interpreter_call, file_search_call) end-to-end without our router wrapping it as a function_call. Because handle_output_item_added only registers function_call / function_tool_call items in pending_calls, belongs_to_pending_call is always false for native passthrough, and the mis-ordered upstream output_item.done (which the cloud emits BEFORE response.<type>.completed) leaked to the wire, breaking the spec invariant that output_item.done must be the LAST event for a given item.

R6.5 integration tests in the cloud streaming matrix surface this gap as an ordering-assertion failure for the native passthrough scenarios.

Solution

Extend the suppression gate with an OR-condition so it fires under EITHER of two conditions:

  1. (a) MCP-dispatched call (R6.7b, unchanged) — the done event's output_index matches a pending call tracked in pending_calls, has a complete name, and the item type is a known tool-call kind. Returns StreamAction::ExecuteTools { forward_triggering_event: false } so the tool loop kicks in and emits its own correctly-ordered umbrella.

  2. (b) Native passthrough (R6.7c, new) — the done event's output_index was recorded in a new native_passthrough_tool_call_indices: HashSet<usize> when upstream emitted output_item.added for a non-function hosted tool-call type, and the item type is still a known tool-call kind. Returns a new StreamAction::Drop so the caller drops the envelope without running the tool loop.

The implementation is purely state-driven on the handler itself — no caller-side native_passthrough: bool needs to be threaded through. The R6.7b MCP-dispatch arm is completely untouched. The new R6.7c arm only fires when the passthrough output_item.added was actually observed first, so spurious output_item.done events still forward rather than being silently swallowed.

A new StreamAction::Drop variant is introduced rather than reusing Buffer, so the caller contract is explicit: consume the event, do not forward, do not dispatch.

Changes

  • model_gateway/src/routers/openai/mcp/tool_handler.rs

    • Add native_passthrough_tool_call_indices: HashSet<usize> field to StreamingToolHandler.
    • In handle_output_item_added, when is_tool_call_item_type(ty) && !is_function_call_type(ty), record the output_index in that set.
    • Extend the OutputItemEvent::DONE gate with the new R6.7c arm that returns StreamAction::Drop when is_tool_call_done && is_native_passthrough_item.
    • Add StreamAction::Drop variant with a documentation comment explaining the native passthrough semantics.
    • Update the R6.7b "unregistered hosted tool output_index forwards" test comment to reflect that forwarding is only correct when the output_item.added was NEVER observed (rename: output_item_done_for_unobserved_hosted_tool_output_index_forwards).
    • Add six new R6.7c unit tests:
      • output_item_done_dropped_for_native_passthrough_{image_generation,web_search,code_interpreter,file_search}_call — one per hosted tool-call type.
      • native_passthrough_gate_coexists_with_mcp_dispatch_gate — mixed stream locks that the MCP-dispatch arm and the native-passthrough arm fire on the correct items (function_call → ExecuteTools; hosted-tool → Drop).
      • native_passthrough_done_drops_even_when_pending_function_call_complete — verifies the R6.7c arm fires independently of has_complete_calls().
      • output_item_added_for_native_passthrough_still_forwards — verifies the output_item.added itself is not accidentally swallowed.
  • model_gateway/src/routers/openai/responses/streaming.rs

    • Handle StreamAction::Drop in handle_streaming_with_tool_interception: drop the event without forwarding and without setting tool_calls_detected (the tool loop does not run for passthrough items).

Test Plan

  • cargo check -p smg — passes.
  • cargo clippy -p smg --all-targets -- -D warnings — passes with no warnings.
  • cargo test -p smg --lib 'routers::openai::mcp::tool_handler' — 16 tests pass (11 pre-existing + 5 new R6.7c + renamed R6.7b test).
  • cargo test -p smg --lib 'routers::openai' — 57 tests pass across the OpenAI router module; no regressions.
  • R6.5 cloud-streaming integration tests should now satisfy the ordering assertions for the hosted-tool native passthrough scenarios (image_generation, web_search, code_interpreter, file_search).

Notes / follow-ups

  • No source code outside model_gateway/src/routers/openai was touched. Protocol types, crates/mcp, crates/protocols, harmony/gRPC routers, and BUILTIN_TOOLS remain untouched.
  • E2E tests are not included here — per R6.5 work-split they live separately.

Refs: R6.5, R6.7, R6.7b (#1371)

Summary by CodeRabbit

  • Bug Fixes

    • Added a suppression path for mis-ordered hosted tool-call "done" events so out-of-order completions are not forwarded or re-triggered, preserving "done is final" ordering and allowing native passthrough items to coexist with dispatched tool-call flows.
  • Tests

    • Expanded test coverage for drop vs forward behavior, multi-type sequences, interaction with dispatched calls, and one-shot suppression/forward semantics.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@coderabbitai

coderabbitai Bot commented Apr 24, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 543d2bac-7015-4916-b448-fd2b1612438f

📥 Commits

Reviewing files that changed from the base of the PR and between e3dbc5a and e08f144.

📒 Files selected for processing (1)
  • model_gateway/src/routers/openai/mcp/tool_handler.rs

📝 Walkthrough

Walkthrough

Adds a new StreamAction::Drop and extends StreamingToolHandler to track hosted/native passthrough tool-call output indices; mis-ordered upstream output_item.done for those items is suppressed (dropped) one-shot, while existing MCP-dispatch ExecuteTools gating is preserved.

Changes

Cohort / File(s) Summary
Tool Handler
model_gateway/src/routers/openai/mcp/tool_handler.rs
Introduce tracking of output_index for hosted/native passthrough output_item.added; map mis-ordered output_item.done to StreamAction::Drop and remove index one-shot. Retain existing pending_calls behavior that yields ExecuteTools. Tests added/updated for drops, one-shot removal, multi-type coverage, and coexistence with MCP dispatch.
Streaming Router
model_gateway/src/routers/openai/responses/streaming.rs
Handle new StreamAction::Drop in streaming interception: dropped events are neither forwarded to client nor used to trigger tool execution; other branches (ExecuteTools, Forward) unchanged.

Sequence Diagram(s)

sequenceDiagram
    participant Upstream as Upstream Response
    participant Handler as StreamingToolHandler
    participant MCP as MCP-Dispatch
    participant Router as Streaming Router
    participant Client as Client

    Upstream->>Handler: output_item.added (hosted/native passthrough, index N)
    Handler->>Handler: record index N in native_passthrough set

    Upstream->>Handler: output_item.done (index N)
    Handler->>MCP: check pending_calls for index N
    alt pending_calls contains N
        Handler->>Router: StreamAction::ExecuteTools { forward_triggering_event: false }
        Router->>MCP: trigger tool execution (do not forward done)
    else native_passthrough observed
        Handler->>Router: StreamAction::Drop
        Router->>Router: suppress forwarding (no dispatch)
    else normal event
        Handler->>Router: Forward event
        Router->>Client: send output_item.done
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • key4ng
  • CatherineSue
  • claude

Poem

🐰 I hop where streams and indexes meet,
When "done" arrives too soon I keep it neat.
One quiet drop, a single gentle skip,
The flow stays right — I guard the ship. 🥕✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: suppressing duplicate output-item envelopes for native passthrough tool calls (R6.7c).
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 fix/r6-07c-native-passthrough-gate

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

@mergify

mergify Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Hi @slin1237, this PR has merge conflicts that must be resolved before it can be merged. Please rebase your branch:

git fetch origin main
git rebase origin/main
# resolve any conflicts, then:
git push --force-with-lease

@mergify mergify Bot added the needs-rebase PR has merge conflicts that need to be resolved label Apr 24, 2026
…ssthrough (R6.7c)

R6.7b tightened the streaming `output_item.done` suppression gate with a
`belongs_to_pending_call` guard so hosted-tool items that the handler
never registered could not have their umbrella dropped permanently
(the tool loop only re-emits umbrellas for items it executed). That
guard is correct for MCP-dispatched calls but silently regresses the
OpenAI cloud "native passthrough" case, where upstream emits a hosted
tool-call item (`image_generation_call`, `web_search_call`,
`code_interpreter_call`, `file_search_call`) end-to-end without our
router wrapping it as `function_call`. Because
`handle_output_item_added` only registers function-call types in
`pending_calls`, `belongs_to_pending_call` is false and the upstream
`output_item.done` — which arrives BEFORE `response.<type>.completed`
on the cloud path — leaked to the wire mis-ordered. That is the gap
R6.5 integration tests surface in the cloud streaming matrix.

Fix: extend the gate with an OR-condition so suppression fires when
EITHER (a) the item is a tracked MCP-dispatched call (R6.7b — kicks
the tool loop via `ExecuteTools { forward_triggering_event: false }`)
OR (b) the item is a known hosted tool-call kind that upstream
announced via `output_item.added` but the MCP-dispatch registry did
NOT capture (R6.7c native passthrough — drops the envelope via a new
`StreamAction::Drop` without running the tool loop). `Drop` is a
distinct variant rather than a reuse of `Buffer` to keep the caller
contract explicit: consume the event, do not forward, do not dispatch.

Implementation is purely state-driven on the handler — no new boolean
threads through from the caller. A new
`native_passthrough_tool_call_indices: HashSet<usize>` records every
output_index whose `output_item.added` carried
`is_tool_call_item_type(ty) && !is_function_call_type(ty)`. The
`output_item.done` gate checks this set alongside the existing
`belongs_to_pending_call` check. The R6.7b MCP-dispatch arm is
unchanged; the new R6.7c arm fires only when the passthrough
`output_item.added` was actually observed, so spurious
`output_item.done` events with no preceding `output_item.added` still
forward rather than being silently swallowed.

Files changed:
- model_gateway/src/routers/openai/mcp/tool_handler.rs: add
  `native_passthrough_tool_call_indices` field, record indices in
  `handle_output_item_added` for non-function hosted tool-call types,
  extend the `OutputItemEvent::DONE` gate with the new R6.7c arm,
  add `StreamAction::Drop` variant. New tests cover all four hosted
  tool-call types, coexistence with MCP-dispatch on a mixed stream,
  dropping while a function_call is in-flight at another index, and
  that the `output_item.added` itself still forwards.
- model_gateway/src/routers/openai/responses/streaming.rs: handle
  `StreamAction::Drop` in the streaming loop (drop without forwarding
  and without setting `tool_calls_detected`).

Refs: R6.5, R6.7, R6.7b
Signed-off-by: Simo Lin <linsimo.mark@gmail.com>
@github-actions github-actions Bot added model-gateway Model gateway crate changes openai OpenAI router changes labels Apr 24, 2026
@slin1237
slin1237 force-pushed the fix/r6-07c-native-passthrough-gate branch from a5bb21c to ced35ac Compare April 24, 2026 06:25

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@model_gateway/src/routers/openai/mcp/tool_handler.rs`:
- Around line 297-313: The native passthrough index set
(native_passthrough_tool_call_indices) is never cleared, so subsequent envelopes
still match is_native_passthrough_item and get StreamAction::Drop, preventing
the later output_item.done from reaching the client; fix by removing the index
from native_passthrough_tool_call_indices as soon as you take the Drop path
(i.e., in the branch that yields StreamAction::Drop where is_tool_call_done &&
is_native_passthrough_item), ensuring you identify and remove the specific
done_output_index (or its usize) from native_passthrough_tool_call_indices
before returning Drop so the later correctly ordered output_item.done will not
be dropped.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 637c34d9-ff65-48e7-8397-68dbeef07344

📥 Commits

Reviewing files that changed from the base of the PR and between ee01556 and a5bb21c.

📒 Files selected for processing (2)
  • model_gateway/src/routers/openai/mcp/tool_handler.rs
  • model_gateway/src/routers/openai/responses/streaming.rs

Comment thread model_gateway/src/routers/openai/mcp/tool_handler.rs Outdated

@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: a5bb21c67b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +307 to +313
} else if is_tool_call_done && is_native_passthrough_item {
// Native passthrough path (R6.7c): drop the upstream
// umbrella without running the tool loop. The
// downstream `<type>.completed` event still reaches
// the client, and upstream re-emits the umbrella in
// the correct position later in the stream.
StreamAction::Drop

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Drop only the first native passthrough output_item.done

This branch drops every response.output_item.done for a native-passthrough output_index, but the index is only ever inserted (handle_output_item_added) and never removed. In streams where upstream sends an early misordered umbrella and then a later correctly ordered umbrella for the same item (the behavior described in the surrounding comments), the second/final output_item.done is dropped too, so clients never receive a terminal umbrella for that tool item. This breaks the stated ordering contract rather than just suppressing the duplicate early envelope.

Useful? React with 👍 / 👎.

@mergify mergify Bot removed the needs-rebase PR has merge conflicts that need to be resolved label Apr 24, 2026

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

♻️ Duplicate comments (1)
model_gateway/src/routers/openai/mcp/tool_handler.rs (1)

296-312: ⚠️ Potential issue | 🔴 Critical

Native passthrough index must be removed after drop to allow the correctly-ordered umbrella through.

The native_passthrough_tool_call_indices set is only ever inserted into (line 347) and checked with contains() (line 297), but the index is never removed. According to the docstring (lines 55-56), upstream re-emits a correctly-ordered output_item.done later in the stream. With the current implementation, that second envelope will also match is_native_passthrough_item and get StreamAction::Drop, causing the client to never see any output_item.done for the native-passthrough item.

🐛 Proposed fix: remove the index when taking the Drop path
-                let is_native_passthrough_item = done_output_index
-                    .is_some_and(|idx| self.native_passthrough_tool_call_indices.contains(&idx));
                 if is_tool_call_done && belongs_to_pending_call && self.has_complete_calls() {
                     // MCP-dispatch path (R6.7b): suppress the upstream
                     // umbrella event and kick the tool loop — it will emit
                     // its own `output_item.done` at the correct position,
                     // AFTER `response.<type>.completed`.
                     StreamAction::ExecuteTools {
                         forward_triggering_event: false,
                     }
-                } else if is_tool_call_done && is_native_passthrough_item {
+                } else if is_tool_call_done
+                    && done_output_index.is_some_and(|idx| {
+                        self.native_passthrough_tool_call_indices.remove(&idx)
+                    })
+                {
                     // Native passthrough path (R6.7c): drop the upstream
                     // umbrella without running the tool loop. The
                     // downstream `<type>.completed` event still reaches
                     // the client, and upstream re-emits the umbrella in
                     // the correct position later in the stream.
                     StreamAction::Drop
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@model_gateway/src/routers/openai/mcp/tool_handler.rs` around lines 296 - 312,
The native_passthrough index is never removed so the later re-emitted umbrella
will also be dropped; in the branch that returns StreamAction::Drop (where
is_native_passthrough_item is true), remove the done_output_index from
self.native_passthrough_tool_call_indices before returning (i.e. if
done_output_index.is_some(), call
self.native_passthrough_tool_call_indices.remove(&idx)). This ensures the
downstream re-emitted output_item.done is not matched and can pass through.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@model_gateway/src/routers/openai/mcp/tool_handler.rs`:
- Around line 863-885: Add a test that verifies a mis-ordered first
output_item.done is dropped but a subsequent correctly-ordered output_item.done
is forwarded: instantiate StreamingToolHandler::with_starting_index(0), call
bootstrap_native_hosted_tool_added(...) for an ItemType (e.g.,
ItemType::IMAGE_GENERATION_CALL) with index 0, call
handler.process_event(Some("response.output_item.done"), &first_done) and assert
it yields StreamAction::Drop, then call
handler.process_event(Some("response.output_item.done"), &second_done) with the
identical payload and assert it yields StreamAction::Forward; this will ensure
native_passthrough_tool_call_indices is cleared so the second done is forwarded.

---

Duplicate comments:
In `@model_gateway/src/routers/openai/mcp/tool_handler.rs`:
- Around line 296-312: The native_passthrough index is never removed so the
later re-emitted umbrella will also be dropped; in the branch that returns
StreamAction::Drop (where is_native_passthrough_item is true), remove the
done_output_index from self.native_passthrough_tool_call_indices before
returning (i.e. if done_output_index.is_some(), call
self.native_passthrough_tool_call_indices.remove(&idx)). This ensures the
downstream re-emitted output_item.done is not matched and can pass through.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6cc60339-ad11-4ce9-bf02-3d6b5c837659

📥 Commits

Reviewing files that changed from the base of the PR and between a5bb21c and ced35ac.

📒 Files selected for processing (2)
  • model_gateway/src/routers/openai/mcp/tool_handler.rs
  • model_gateway/src/routers/openai/responses/streaming.rs

Comment thread model_gateway/src/routers/openai/mcp/tool_handler.rs
…umbrella forwards

Addresses CodeRabbit and chatgpt-codex-connector P1 feedback on PR
#1376. The initial R6.7c implementation only ever inserted into
`native_passthrough_tool_call_indices` and checked membership with
`contains()`, so every subsequent `output_item.done` at the same
output_index was also dropped. That regresses the spec invariant the
PR is meant to fix: after upstream emits the correctly-ordered
umbrella (after `response.<type>.completed`) the client must see it
as the terminal event for the item. With the unbounded membership
check the second umbrella also hit the `Drop` arm and the client
ended up seeing no terminal `output_item.done` at all.

Fix: change the passthrough-match condition from `contains(&idx)` to
`remove(&idx)`. The first `output_item.done` for the item atomically
removes the index and returns true (Drop fires). The second
`output_item.done` finds the index already gone, so `remove` returns
false, the R6.7c arm declines, and the envelope forwards through to
the client.

Added `native_passthrough_second_output_item_done_forwards_after_first_dropped`
test to lock the one-shot behaviour: feeds an `output_item.added`
plus two identical `output_item.done` events and asserts `Drop` then
`Forward`. This test fails against the `contains` form of the gate
and passes with the `remove` form.

Refs: #1376 review, R6.7c
Signed-off-by: Simo Lin <linsimo.mark@gmail.com>

@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: e3dbc5a474

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +315 to +317
&& done_output_index.is_some_and(|idx| {
self.native_passthrough_tool_call_indices.remove(&idx)
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid consuming passthrough index before terminal done is guaranteed

This remove-based gate drops the first native-passthrough response.output_item.done and immediately forgets the index, which assumes the later correctly ordered umbrella will definitely be observed in the same streaming pass. In mixed streams (the coexistence case already modeled in native_passthrough_gate_coexists_with_mcp_dispatch_gate), if a different MCP-dispatched call hits ExecuteTools first, the streaming loop exits to run tools before that later passthrough umbrella is read, so the client can end up seeing no terminal output_item.done for the hosted item at all.

Useful? React with 👍 / 👎.

The `unit-tests` CI job runs `cargo +nightly fmt -- --check`; the R6.7c
commits used more generous wrapping than rustfmt's nightly defaults
chose. Let rustfmt own the formatting so CI passes — no semantic
changes.

Refs: #1376 CI
Signed-off-by: Simo Lin <linsimo.mark@gmail.com>

@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: e08f1443b5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +314 to +317
} else if is_tool_call_done
&& done_output_index
.is_some_and(|idx| self.native_passthrough_tool_call_indices.remove(&idx))
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Gate native done-drop on proven duplicate ordering

This branch drops the first hosted-tool response.output_item.done whenever the index was previously seen in output_item.added, but it does not verify that a replacement terminal umbrella will arrive later. If upstream emits only a single (already correctly ordered) output_item.done for that item, StreamAction::Drop removes the only completion envelope and handle_streaming_with_tool_interception never forwards one, so clients can miss the terminal event for the tool item. The drop condition should depend on observing premature ordering (or otherwise guarantee a later done) rather than unconditionally consuming the first done.

Useful? React with 👍 / 👎.

@slin1237
slin1237 merged commit d81a79c into main Apr 24, 2026
37 of 38 checks passed
@slin1237
slin1237 deleted the fix/r6-07c-native-passthrough-gate branch April 24, 2026 07:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

model-gateway Model gateway crate changes openai OpenAI router changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant