Skip to content

feat(router): add stateful tool bootstrap with integ tests#1401

Open
RohanSogani wants to merge 8 commits into
lightseekorg:mainfrom
RohanSogani:feat/stateful-tool-bootstrap-tests
Open

feat(router): add stateful tool bootstrap with integ tests#1401
RohanSogani wants to merge 8 commits into
lightseekorg:mainfrom
RohanSogani:feat/stateful-tool-bootstrap-tests

Conversation

@RohanSogani
Copy link
Copy Markdown
Contributor

@RohanSogani RohanSogani commented Apr 27, 2026

Description

Resolves #1297

Problem

SMG’s OpenAI-compatible Responses path did not have a generic way to prepare request-scoped state for stateful built-in tools before the first model call.

That left a gap for tools like:

  • code_interpreter
  • shell

Before this change, the router could not cleanly do all of the following in one place:

  • Detect that a request declares a stateful tool.
  • Run one bootstrap step exactly once per request.
  • Validate the provider request before any bootstrap side effects.
  • Inject bootstrap-generated model context into the outbound request before the first upstream call.
  • Carry prepared tool state through the request lifecycle for both streaming and non-streaming flows.
  • Persist only caller-owned input/metadata instead of replay-expanded or bootstrap-expanded execution input.

Without this layer, the next provider-specific/runtime integration would either have to:

  • Duplicate lifecycle/bootstrap logic across tool paths.
  • Leak provider-specific container/session semantics into upstream SMG.

That would make the upstream design brittle and tightly coupled to one provider/runtime implementation.

Solution

Add a generic request-scoped stateful tool bootstrap layer to the OpenAI Responses router.

This change introduces:

  • A StatefulToolBootstrapper interface.
  • A request-scoped StatefulToolBootstrapState.
  • One-time bootstrap execution in the Responses route after history/memory preparation and provider validation preflight, but before the first downstream model call.
  • Support for bootstrap-generated input item injection into the outbound request.
  • Support for storing opaque prepared tool state keyed by generic tool kind.
  • Propagation of bootstrap state across both streaming and non-streaming request paths.
  • Safe separation between client_body, request_body, and persistence_body.
  • A default no-op bootstrapper so existing behavior remains unchanged until a real provider-specific implementation is plugged in.

Request-body roles:

  • client_body: the original caller request and metadata.
  • request_body: the normalized execution request sent upstream.
  • persistence_body: the storage request that keeps caller input/metadata without replay-expanded or bootstrap-expanded context.

The important architectural boundary is preserved:

  • Upstream SMG stays generic.
  • Provider-specific/runtime-specific lifecycle logic remains provider-owned and can be implemented later behind the bootstrapper interface.

Changes

  • Added model_gateway/src/routers/openai/stateful_tools.rs.
  • Defined generic stateful tool kinds: code_interpreter and shell.
  • Added PreparedToolState with opaque JSON payload storage.
  • Added StatefulToolBootstrapState to track whether bootstrap has executed and prepared tool state entries for the request.
  • Added StatefulToolBootstrapContext so bootstrap implementations receive request headers, storage request context, memory execution context, and tenant request metadata.
  • Added StatefulToolBootstrapper async trait.
  • Added NoOpStatefulToolBootstrapper as the default safe upstream implementation.
  • Added stateful tool detection based on declared ResponsesRequest.tools.
  • Integrated bootstrap execution into model_gateway/src/routers/openai/responses/route.rs.
  • Placed bootstrap after history loading, memory injection, replay sanitization, and provider validation preflight, but before final downstream dispatch.
  • Added request input prepending logic so bootstrap can inject hidden/request-scoped model context ahead of the original user input.
  • Extended ResponsesComponents to carry the bootstrapper.
  • Extended ResponsesPayloadState to carry bootstrap state.
  • Threaded bootstrap state through RequestContext and OwnedStreamingContext.
  • Seeded RequestContext from the original client request so caller metadata is preserved for response patching and persistence.
  • Updated persistence request construction to preserve conversation, previous_response_id, store, and user from the caller request.
  • Updated persistence request construction to store caller input instead of replay-expanded or bootstrap-expanded execution input.
  • Updated streaming context request ownership to avoid deep-cloning full request bodies for stream lifetime.
  • Preserved existing production behavior by wiring the router to the no-op bootstrapper by default.
  • Added router-level integration coverage for non-streaming bootstrap input injection, streaming bootstrap input injection, skip behavior when no stateful tools are declared, bootstrap failure before
    downstream worker call, provider validation before bootstrap side effects, and persistence behavior for previous_response_id and root requests.

Test Plan

Repro from repo root:

cargo +nightly fmt --all
cargo clippy --all-targets --all-features -- -D warnings
cargo test -p smg --test api_tests stateful_tool_bootstrap -- --nocapture
cargo test

Scenarios verified:

1. A Responses request that declares shell and/or code_interpreter triggers bootstrap before the first downstream worker call.
2. Provider validation/preflight happens before bootstrap, so invalid requests do not allocate prepared runtime state.
3. Bootstrap-generated input items are injected into the outbound request payload before the original user input.
4. The same request-scoped bootstrap behavior works in both non-streaming and streaming flows.
5. Requests without stateful tools do not invoke bootstrap.
6. Bootstrap failure returns stateful_tool_bootstrap_failed and prevents any downstream worker request.
7. Bootstrap failure is non-mutating: request input is preserved, bootstrap_state.executed remains false, and prepared state is not recorded.
8. Conversation and previous_response_id metadata are preserved from the original caller request.
9. Persistence stores only caller input, preventing replay-expanded history duplication and stale bootstrap context replay.
10. Existing production behavior remains unchanged with the default no-op bootstrapper.
<details>
<summary>Checklist</summary>

- [x] cargo +nightly fmt passes
- [x] cargo clippy --all-targets --all-features -- -D warnings passes
- [x] cargo test -p smg --test api_tests stateful_tool_bootstrap -- --nocapture passes
- [x] cargo test passes
- [ ] (Optional) Documentation updated
- [ ] (Optional) Please join us on Slack #sig-smg to discuss, review, and merge PRs
</details>

Signed-off-by: Rohan Sogani <ronsogani@gmail.com>
@github-actions github-actions Bot added tests Test changes model-gateway Model gateway crate changes openai OpenAI router changes labels Apr 27, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a bootstrapping mechanism for stateful tools, such as Code Interpreter and Shell, within the OpenAI Responses API. The changes include a new stateful_tools module, updates to the request context to track bootstrap state, and comprehensive integration tests. The review feedback identifies critical issues where the RequestContext is initialized with the original request body rather than the modified version containing history and memory context. Furthermore, the feedback highlights that the context must be updated after bootstrapping to ensure injected items are preserved across tool call iterations. Finally, it is recommended to use unique UUIDs for injected message identifiers to avoid potential issues with downstream systems.

Comment thread model_gateway/src/routers/openai/responses/route.rs Outdated
Comment thread model_gateway/src/routers/openai/responses/route.rs
Comment thread model_gateway/src/routers/openai/stateful_tools.rs Outdated
Copy link
Copy Markdown

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

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: dd0844cefc

ℹ️ 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 thread model_gateway/src/routers/openai/responses/route.rs Outdated
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 27, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Router path for OpenAI Responses now prepares and runs a per-request stateful-tool bootstrap before request transformation, threads bootstrap state through request/context, updates streaming and non-streaming paths to use separate request_body vs client_body, and persists bootstrapped state into MCP/tool-loop and persistence payloads.

Changes

Cohort / File(s) Summary
Core context & router
model_gateway/src/routers/openai/context.rs, model_gateway/src/routers/openai/router.rs, model_gateway/src/routers/openai/mod.rs
Add stateful_tool_bootstrapper to ResponsesComponents and accessor; add ResponsesPayloadState fields (client_request, stateful_tool_bootstrap); change OwnedStreamingContext to carry request_body + client_body and bootstrap state; add router constructors that accept or default a bootstrapper and export stateful_tools module.
Stateful tools feature
model_gateway/src/routers/openai/stateful_tools.rs
New module implementing stateful tool kinds, prepared-state representation, bootstrapper trait, helpers to detect declared stateful tools, ensure_stateful_tool_bootstrap that injects input items and records prepared states, plus NoOp bootstrapper and tests.
Responses routing logic
model_gateway/src/routers/openai/responses/route.rs
Build RequestContext earlier, initialize responses payload (including bootstrap state), run ensure_stateful_tool_bootstrap before serialization/transform; short-circuit with stateful_tool_bootstrap_failed on errors or missing bootstrapper.
Non-streaming & streaming handlers
model_gateway/src/routers/openai/responses/non_streaming.rs, model_gateway/src/routers/openai/responses/streaming.rs
Switch to dual-body model: request_body (executed/provider view) vs client_body (caller view). Use client_body for restore/metadata/persistence and request_body for MCP/tool orchestration and metrics. Thread stateful_tool_bootstrap into ToolLoopExecutionContext and create ToolLoopState with bootstrap. Use build_persistence_request_body for persistence payload.
MCP tool-loop
model_gateway/src/routers/openai/mcp/tool_loop.rs
Add stateful_tool_bootstrap to ToolLoopState, add new_with_bootstrap constructor, and extend ToolLoopExecutionContext to reference bootstrap state; use new constructor when starting tool loop.
Persistence helper
model_gateway/src/routers/openai/responses/utils.rs
Add build_persistence_request_body(request_body, client_body) to compose a persistence-specific ResponsesRequest merging executed/provider request with caller-controlled metadata.
History deserialization
model_gateway/src/routers/openai/responses/history.rs
Exclude reasoning-typed ResponseInputOutputItem entries from deserialized history; add unit test.
Tests & mocks
model_gateway/tests/api/responses_api_test.rs, model_gateway/tests/common/mock_worker.rs, model_gateway/tests/routing/test_openai_routing.rs
Add TestBootstrapper and extensive integration tests for bootstrap behavior, add per-worker recorded responses store and MockWorker.port(), update streaming test expectations and helpers, and assertions for bootstrap-injected items and failure short-circuiting.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client
  participant Router as Router
  participant Bootstrapper as StatefulToolBootstrapper
  participant Worker as Downstream Worker
  participant ToolLoop as MCP ToolLoop
  participant Persistence as Persistence

  Client->>Router: POST /v1/responses (original request)
  Router->>Router: inject history/memory, build RequestContext
  Router->>Bootstrapper: ensure_stateful_tool_bootstrap(request, bootstrap_state, context)
  Bootstrapper-->>Router: prepared_states + injected_input_items
  Router->>Router: prepend injected items into request.input (client_body/request_body)
  Router->>Worker: send transformed worker request (uses request_body for MCP, client_body for persistence metadata)
  Worker->>ToolLoop: trigger MCP/tool-loop when tools present (ToolLoopState includes bootstrap_state)
  ToolLoop->>ToolLoop: execute tool loop using prepared state
  Router->>Persistence: persist conversation items using build_persistence_request_body(request_body, client_body)
  Note right of Router: bootstrap_state persisted and threaded through tool loop
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • CatherineSue
  • key4ng
  • slin1237

Poem

"I hopped through code at break of dawn,
Prepared some tools and fixed a yawn,
I stitched the inputs, snug and warm,
So bootstraps run before the storm.
Huzzah — a rabbit's tiny charm!" 🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 67.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title accurately describes the main change: adding stateful tool bootstrap support with integration tests.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
model_gateway/tests/common/mock_worker.rs (1)

1240-1286: ⚠️ Potential issue | 🟡 Minor

The recorded-request store isn't isolated across worker lifetimes.

This global store is keyed only by port, but MockWorker uses ephemeral ports and never clears the slot on start()/stop(). If the OS reuses a port across tests, take_recorded_responses_requests_for_port() can pick up stale payloads from an earlier worker and make these new assertions flaky. Clearing the entry on startup/shutdown, or keying by a per-worker UUID instead of the TCP port, would avoid that cross-test leakage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@model_gateway/tests/common/mock_worker.rs` around lines 1240 - 1286, The
recorded-request store RESPONSES_REQUEST_STORE is keyed only by port which
allows stale data to leak across worker lifetimes; update the design to isolate
by a per-worker UUID or clear the stored entry when a MockWorker starts/stops.
Concretely, replace or augment RESPONSES_REQUEST_STORE so keys are a worker_id
(Uuid) instead of u16 (modify get_responses_request_store,
record_responses_request_for_port, take_recorded_responses_requests_for_port to
accept and use a Uuid worker_id or provide a clear_recorded_responses_for_port
function called from MockWorker::start()/stop), ensure MockWorker generates and
exposes a UUID on creation/start and uses that UUID when recording/reading
requests, and remove reliance on TCP port as the sole key to prevent cross-test
leakage.
🤖 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/responses/non_streaming.rs`:
- Around line 41-45: The destructure of ResponsesPayloadState currently discards
stateful_tool_bootstrap (via stateful_tool_bootstrap: _stateful_tool_bootstrap)
which prevents PreparedToolState produced by route_responses from reaching
execute_tool_loop and later non-streaming tool handling; change the destructure
to preserve the stateful_tool_bootstrap value (e.g., bind it to
stateful_tool_bootstrap) and thread that preserved PreparedToolState through the
non-streaming execution path—ensure
ctx.take_responses_payload().unwrap_or_default() returns the preserved state and
pass stateful_tool_bootstrap into whatever function or context (such as
execute_tool_loop or subsequent non-streaming tool handling code) expects
per-tool state so bootstrap-produced state is available downstream.

In `@model_gateway/src/routers/openai/responses/streaming.rs`:
- Line 686: The prepared bootstrap state (req.stateful_tool_bootstrap) is being
discarded by binding it to _stateful_tool_bootstrap; instead capture it (e.g.,
let stateful_tool_bootstrap = req.stateful_tool_bootstrap) and thread that
PreparedToolState into the streaming execution context used by the spawned tool
loop so the tool loop can access the per-tool prepared state; update the
spawning call and any context/struct (used by route_responses /
ResponsesPayloadState) to accept and propagate stateful_tool_bootstrap rather
than dropping it.

In `@model_gateway/src/routers/openai/stateful_tools.rs`:
- Line 122: The function signature parameters using "bootstrapper: &(dyn
StatefulToolBootstrapper + Send + Sync)" are redundant because
StatefulToolBootstrapper already requires Send + Sync; remove the explicit "+
Send + Sync" and change the parameter to "bootstrapper: &dyn
StatefulToolBootstrapper" in the affected functions (the parameter named
bootstrapper at lines around the definitions that include this type, e.g., where
the function/impl accepts &dyn StatefulToolBootstrapper).
- Around line 62-66: StatefulToolBootstrapResult currently embeds a
StatefulToolBootstrapState whose executed flag is never used because
ensure_stateful_tool_bootstrap always sets bootstrap_state.executed = true and
never reads result.prepared_state.executed; change the result type to return
only the prepared tools (e.g., Vec<ResponseInputOutputItem> or a new
PreparedState struct without the executed bool) instead of
StatefulToolBootstrapState, update ensure_stateful_tool_bootstrap to populate
that narrowed result, and adjust CountingBootstrapper and affected unit-test
fixtures to produce/expect the new simplified result shape.
- Around line 148-158: The Text normalization branch in stateful_tools.rs
currently assigns an empty id (id: String::new()) when converting
ResponseInput::Text into a ResponseInputOutputItem::Message; replace that empty
id with a generated id using generate_id("msg") so the created Message has a
unique non-empty id. Update the match arm in the function handling ResponseInput
(the ResponseInput::Text branch) to set id: generate_id("msg") and ensure the
module imports or can access the generate_id function used elsewhere in the
router so compilation succeeds.

---

Outside diff comments:
In `@model_gateway/tests/common/mock_worker.rs`:
- Around line 1240-1286: The recorded-request store RESPONSES_REQUEST_STORE is
keyed only by port which allows stale data to leak across worker lifetimes;
update the design to isolate by a per-worker UUID or clear the stored entry when
a MockWorker starts/stops. Concretely, replace or augment
RESPONSES_REQUEST_STORE so keys are a worker_id (Uuid) instead of u16 (modify
get_responses_request_store, record_responses_request_for_port,
take_recorded_responses_requests_for_port to accept and use a Uuid worker_id or
provide a clear_recorded_responses_for_port function called from
MockWorker::start()/stop), ensure MockWorker generates and exposes a UUID on
creation/start and uses that UUID when recording/reading requests, and remove
reliance on TCP port as the sole key to prevent cross-test leakage.
🪄 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: 3af1949d-aa09-41a9-840a-3ec8d9562293

📥 Commits

Reviewing files that changed from the base of the PR and between a99af9c and dd0844c.

📒 Files selected for processing (9)
  • model_gateway/src/routers/openai/context.rs
  • model_gateway/src/routers/openai/mod.rs
  • model_gateway/src/routers/openai/responses/non_streaming.rs
  • model_gateway/src/routers/openai/responses/route.rs
  • model_gateway/src/routers/openai/responses/streaming.rs
  • model_gateway/src/routers/openai/router.rs
  • model_gateway/src/routers/openai/stateful_tools.rs
  • model_gateway/tests/api/responses_api_test.rs
  • model_gateway/tests/common/mock_worker.rs

Comment thread model_gateway/src/routers/openai/responses/non_streaming.rs
Comment thread model_gateway/src/routers/openai/responses/streaming.rs Outdated
Comment thread model_gateway/src/routers/openai/stateful_tools.rs
Comment thread model_gateway/src/routers/openai/stateful_tools.rs Outdated
Comment thread model_gateway/src/routers/openai/stateful_tools.rs
Signed-off-by: Rohan Sogani <ronsogani@gmail.com>
Copy link
Copy Markdown

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

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: 3209bc94f0

ℹ️ 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 thread model_gateway/src/routers/openai/responses/route.rs
Comment thread model_gateway/src/routers/openai/responses/route.rs Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
model_gateway/src/routers/openai/mcp/tool_loop.rs (1)

812-850: ⚠️ Potential issue | 🟠 Major

Prepared bootstrap state does not reach tool execution handlers.

StatefulToolBootstrapState is stored in ToolLoopState and has a prepared_tool() accessor, but that accessor is never called in production code. The tool execution chain (execute_tool_resolved_resultexecute_tool_entry_result) does not receive StatefulToolBootstrapState, and ToolExecutionInput (passed to handlers) contains only call_id, tool_name, and arguments—no prepared state. The only production reference is the startup log at line 849.

🤖 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_loop.rs` around lines 812 - 850,
The prepared StatefulToolBootstrapState is never passed into the tool execution
handlers; update the plumbing so the prepared state is propagated from
ToolLoopState into the handler input: call ToolLoopState.prepared_tool() where
you build per-call execution context in execute_tool_loop and include that
prepared state in the ToolExecutionInput (extend ToolExecutionInput to hold an
Option<StatefulToolBootstrapState> or reference), then thread that field through
execute_tool_resolved_result and execute_tool_entry_result so handlers receive
the prepared state; ensure all call sites constructing ToolExecutionInput (and
tests) are updated to supply the prepared state from the ToolLoopState.
♻️ Duplicate comments (1)
model_gateway/src/routers/openai/stateful_tools.rs (1)

149-159: 🧹 Nitpick | 🔵 Trivial

Avoid the unnecessary text.clone() in the Text branch.

*input is unconditionally replaced two lines below, so the original String inside ResponseInput::Text(_) will be dropped. You can move it into the new InputText part rather than cloning.

♻️ Optional refactor
         ResponseInput::Text(text) => {
+            let text = std::mem::take(text);
             injected_items.push(ResponseInputOutputItem::Message {
                 id: generate_id("msg"),
                 role: "user".to_string(),
-                content: vec![ResponseContentPart::InputText { text: text.clone() }],
+                content: vec![ResponseContentPart::InputText { text }],
                 status: None,
                 phase: None,
             });
             *input = ResponseInput::Items(injected_items);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@model_gateway/src/routers/openai/stateful_tools.rs` around lines 149 - 159,
The Text branch currently clones the string (`text.clone()`); instead take
ownership of the String instead of cloning it: extract the owned String from
`input` (for example via `let text = match std::mem::take(input) {
ResponseInput::Text(t) => t, other => { *input = other; return; } };`) and then
push the `ResponseInputOutputItem::Message` using
`ResponseContentPart::InputText { text }` (no clone), finally set `*input =
ResponseInput::Items(injected_items)` as before; update the
`ResponseInput::Text(text)` handling so the original String is moved into
`InputText` rather than cloned.
🤖 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_loop.rs`:
- Around line 62-75: ToolLoopState::new is currently cfg(test) and requires
(original_input, prior_mcp_list_tools_labels), but production code still calls
ToolLoopState::new(original_request.input.clone()); update those production call
sites that pass a single argument to call
ToolLoopState::new_with_bootstrap(original_request.input.clone(),
prior_mcp_list_tools_labels, StatefulToolBootstrapState::default()) (or supply
the correct prior_mcp_list_tools_labels value available in that context), or
alternatively add a public single-argument constructor that forwards to
new_with_bootstrap using StatefulToolBootstrapState::default() to restore a
production-safe path; key symbols: ToolLoopState::new,
ToolLoopState::new_with_bootstrap, StatefulToolBootstrapState::default(), and
the production call sites invoking
ToolLoopState::new(original_request.input.clone()).

In `@model_gateway/src/routers/openai/stateful_tools.rs`:
- Around line 120-139: Add an explicit INVARIANT comment to
ensure_stateful_tool_bootstrap documenting that bootstrap_state.executed is
intentionally only set to true after a successful bootstrap (i.e., after
bootstrapper.bootstrap(...) returns Ok), so a failing bootstrap leaves
executed=false and allows callers to re-run the function; reference
bootstrap_state.executed, bootstrapper.bootstrap, and the fact that injected
items and prepared tools are only applied on success to make the
single-attempt-per-success-per-request contract explicit for future maintainers.

In `@model_gateway/tests/api/responses_api_test.rs`:
- Around line 161-172: The test is brittle because bootstrap_payload_texts
collects every "text" under input[*].content[*] regardless of message role;
update bootstrap_payload_texts to first filter input array items by role (only
include items where item.get("role") is "system" or "user") before extracting
content texts, and update the resume tests that asserted exact equality to
either assert the bootstrap text is present (contains) or assert the first
collected text equals the expected "bootstrap context" so the tests no longer
fail when assistant-function mixing occurs; refer to the bootstrap_payload_texts
function and the resume tests that assert bootstrap payload texts.

---

Outside diff comments:
In `@model_gateway/src/routers/openai/mcp/tool_loop.rs`:
- Around line 812-850: The prepared StatefulToolBootstrapState is never passed
into the tool execution handlers; update the plumbing so the prepared state is
propagated from ToolLoopState into the handler input: call
ToolLoopState.prepared_tool() where you build per-call execution context in
execute_tool_loop and include that prepared state in the ToolExecutionInput
(extend ToolExecutionInput to hold an Option<StatefulToolBootstrapState> or
reference), then thread that field through execute_tool_resolved_result and
execute_tool_entry_result so handlers receive the prepared state; ensure all
call sites constructing ToolExecutionInput (and tests) are updated to supply the
prepared state from the ToolLoopState.

---

Duplicate comments:
In `@model_gateway/src/routers/openai/stateful_tools.rs`:
- Around line 149-159: The Text branch currently clones the string
(`text.clone()`); instead take ownership of the String instead of cloning it:
extract the owned String from `input` (for example via `let text = match
std::mem::take(input) { ResponseInput::Text(t) => t, other => { *input = other;
return; } };`) and then push the `ResponseInputOutputItem::Message` using
`ResponseContentPart::InputText { text }` (no clone), finally set `*input =
ResponseInput::Items(injected_items)` as before; update the
`ResponseInput::Text(text)` handling so the original String is moved into
`InputText` rather than cloned.
🪄 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: 6f1f789a-a30b-4f4a-92f7-af2d3333ad9d

📥 Commits

Reviewing files that changed from the base of the PR and between dd0844c and 3209bc9.

📒 Files selected for processing (7)
  • model_gateway/src/routers/openai/mcp/tool_loop.rs
  • model_gateway/src/routers/openai/responses/non_streaming.rs
  • model_gateway/src/routers/openai/responses/route.rs
  • model_gateway/src/routers/openai/responses/streaming.rs
  • model_gateway/src/routers/openai/stateful_tools.rs
  • model_gateway/tests/api/responses_api_test.rs
  • model_gateway/tests/routing/test_openai_routing.rs

Comment thread model_gateway/src/routers/openai/mcp/tool_loop.rs
Comment thread model_gateway/src/routers/openai/stateful_tools.rs
Comment thread model_gateway/tests/api/responses_api_test.rs
Signed-off-by: Rohan Sogani <ronsogani@gmail.com>
Copy link
Copy Markdown

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

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: 4584e63a8e

ℹ️ 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 thread model_gateway/src/routers/openai/responses/route.rs
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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/context.rs`:
- Around line 278-286: The OwnedStreamingContext currently stores two full
ResponsesRequest structs (request_body and client_body) which causes deep clones
for the entire stream; change those fields to Arc<ResponsesRequest> in the
OwnedStreamingContext and update all sites that construct or assign
OwnedStreamingContext (the places around the other occurrences you noted) to
wrap the existing ResponsesRequest in Arc::new(...) and pass Arc clones
(Arc::clone) instead of cloning the whole struct—this preserves the
client/upstream split with cheap reference clones and avoids keeping two
deep-copied payloads for the stream lifetime.

In `@model_gateway/src/routers/openai/responses/history.rs`:
- Around line 364-385: The test
deserialize_items_from_array_drops_reasoning_replay_items currently may pass due
to deserialization failure rather than filtering; update the fixture so the
second "reasoning" item matches the verified summary-based wire shape that
successfully deserializes (so deserialize_items_from_array actually produces a
reasoning variant), then assert that the resulting vector from
deserialize_items_from_array contains only the Message (e.g., parsed.len() == 1
and matches!(parsed[0], ResponseInputOutputItem::Message { .. })), proving the
item was removed by the filter rather than dropped during parse; reference the
test function deserialize_items_from_array_drops_reasoning_replay_items, the
deserializer deserialize_items_from_array, and the
ResponseInputOutputItem::Message variant when making the change.

In `@model_gateway/src/routers/openai/responses/route.rs`:
- Around line 157-165: The early-return when
ctx.components.stateful_tool_bootstrapper() is None in route_responses currently
skips router-error telemetry; before returning from that None branch (where
stateful_tool_bootstrapper is checked), call Metrics::record_router_error(...)
via the router metrics handle (e.g., ctx.metrics() or the existing Metrics
accessor) with an appropriate error identifier (e.g.,
"stateful_tool_bootstrapper_missing") and any relevant tags, then proceed to
return the internal_error as before; update the branch around the
stateful_tool_bootstrapper check to emit this metric prior to the
error::internal_error(...) return.

In `@model_gateway/src/routers/openai/stateful_tools.rs`:
- Around line 234-330: Add a unit test that verifies a failing bootstrap leaves
state and request unchanged: call ensure_stateful_tool_bootstrap with a
bootstrapper that returns an Err, using a non-empty request.tools and a baseline
ResponsesRequest.input (e.g., Text or Items), then assert
bootstrap_state.executed is false and request.input equals the original value;
reference ensure_stateful_tool_bootstrap, StatefulToolBootstrapState,
ResponsesRequest and the bootstrapper type used in other tests (e.g.,
CountingBootstrapper or a new FailingBootstrapper) to locate where to add the
test and how to simulate the error path.
🪄 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: c6ca67d9-9231-4ee6-b1ec-36e8a99b3c9b

📥 Commits

Reviewing files that changed from the base of the PR and between 3209bc9 and 4584e63.

📒 Files selected for processing (6)
  • model_gateway/src/routers/openai/context.rs
  • model_gateway/src/routers/openai/responses/history.rs
  • model_gateway/src/routers/openai/responses/non_streaming.rs
  • model_gateway/src/routers/openai/responses/route.rs
  • model_gateway/src/routers/openai/responses/streaming.rs
  • model_gateway/src/routers/openai/stateful_tools.rs

Comment thread model_gateway/src/routers/openai/context.rs
Comment thread model_gateway/src/routers/openai/responses/history.rs
Comment thread model_gateway/src/routers/openai/responses/route.rs
Comment thread model_gateway/src/routers/openai/stateful_tools.rs
Signed-off-by: Rohan Sogani <ronsogani@gmail.com>
Copy link
Copy Markdown

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

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: 78703031ed

ℹ️ 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 thread model_gateway/src/routers/openai/responses/route.rs
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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/responses/utils.rs`:
- Around line 31-40: The persistence request built in
build_persistence_request_body currently clones request_body then copies
conversation, store, and user from client_body, but it omits copying
client_body.previous_response_id so the persisted record can lose its resume
link; update build_persistence_request_body to also copy
persistence_body.previous_response_id = client_body.previous_response_id (or use
clone_from if it's an Option/String type) immediately after copying
conversation/store/user so the persisted ResponsesRequest preserves the caller's
previous_response_id.
🪄 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: c6457b46-b9da-437b-9192-46a4c58e3b00

📥 Commits

Reviewing files that changed from the base of the PR and between 4584e63 and 7870303.

📒 Files selected for processing (3)
  • model_gateway/src/routers/openai/responses/non_streaming.rs
  • model_gateway/src/routers/openai/responses/streaming.rs
  • model_gateway/src/routers/openai/responses/utils.rs

Comment thread model_gateway/src/routers/openai/responses/utils.rs
Signed-off-by: Rohan Sogani <ronsogani@gmail.com>
Copy link
Copy Markdown

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

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: 38bfae040c

ℹ️ 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 thread model_gateway/tests/common/mock_worker.rs
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
model_gateway/src/routers/openai/responses/utils.rs (1)

31-49: ⚠️ Potential issue | 🟠 Major

Restore previous_response_id here as well.

This helper reattaches caller-owned persistence metadata, but it still drops previous_response_id. That breaks the resume link for persisted requests in both streaming and non-streaming paths.

🩹 Proposed fix
     persistence_body
         .conversation
         .clone_from(&client_body.conversation);
     persistence_body.store = client_body.store;
+    persistence_body
+        .previous_response_id
+        .clone_from(&client_body.previous_response_id);
     persistence_body.user.clone_from(&client_body.user);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@model_gateway/src/routers/openai/responses/utils.rs` around lines 31 - 49,
build_persistence_request_body currently copies conversation, store, user, and
input from client_body but omits restoring previous_response_id, breaking resume
linking; modify build_persistence_request_body to also copy previous_response_id
from client_body into persistence_body (i.e., set
persistence_body.previous_response_id = client_body.previous_response_id or use
clone_from as with user/conversation) so persisted requests retain the
caller-owned resume link.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@model_gateway/src/routers/openai/responses/utils.rs`:
- Around line 31-49: build_persistence_request_body currently copies
conversation, store, user, and input from client_body but omits restoring
previous_response_id, breaking resume linking; modify
build_persistence_request_body to also copy previous_response_id from
client_body into persistence_body (i.e., set
persistence_body.previous_response_id = client_body.previous_response_id or use
clone_from as with user/conversation) so persisted requests retain the
caller-owned resume link.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4e65997c-ea8c-405b-83da-16b504913048

📥 Commits

Reviewing files that changed from the base of the PR and between 7870303 and 38bfae0.

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

@RohanSogani RohanSogani changed the title feat(router): add stateful tool bootstrap integration tests feat(router): add stateful tool bootstrap with integ tests Apr 28, 2026
Signed-off-by: Rohan Sogani <ronsogani@gmail.com>
Copy link
Copy Markdown

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

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: 4413a2b735

ℹ️ 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 thread model_gateway/src/routers/openai/responses/utils.rs Outdated
Signed-off-by: Rohan Sogani <ronsogani@gmail.com>
Copy link
Copy Markdown

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

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: 769a2e7014

ℹ️ 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 thread model_gateway/src/routers/openai/responses/utils.rs Outdated
Signed-off-by: Rohan Sogani <ronsogani@gmail.com>
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 tests Test changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Support reusable execution sessions for Responses tools like code interpreter and shell

1 participant