refactor(openai-bridge): close last new-builtin coupling leaks (descriptor + connect wrapper)#1455
Conversation
`tool_handler.rs` carried a hand-maintained `TOOL_CALL_ITEM_TYPES` const enumerating the function-call variants plus the four hosted tool-call families. Adding a new hosted format silently broke the `output_item.done` suppression gate (the upstream's duplicate umbrella would slip through ahead of `<type>.completed`, violating the spec ordering invariant) and the const was the kind of thing nothing flagged at review time. Replace the const + helper with `is_function_call_type(s) || openai_bridge::is_hosted_tool_call_item_type(s)`. The new bridge helper sources the hosted set from `format_from_type_str` + `ResponseFormat::to_builtin_tool_type`, so the descriptor table is the single source of truth — adding a hosted format flips the predicate automatically with no edit in `tool_handler.rs`. Locks the set with a regression test naming all 6 hits + 5 misses. Signed-off-by: Simo Lin <[email protected]>
Two production sites — the dynamic-connect path in `mcp_utils.rs` and the static-connect step in the workflow — both call `McpOrchestrator::connect_*` and then `FormatRegistry::populate_from_server_config` back-to-back. Forgetting the second call leaves hosted-tool dispatch silently downgraded to `mcp_call`. The asymmetric-miss debug log added in 283f27f fingerprints the bug at runtime, but a wrapper makes the mistake structurally impossible. Add `openai_bridge::connect_static_server` and `connect_dynamic_server` that perform the connect + registry mirror as a single fallible operation, and migrate the two existing call sites. Future connect paths only need to know about the wrapper. Signed-off-by: Simo Lin <[email protected]>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughThis PR moves FormatRegistry synchronization into new openai_bridge connect helpers and replaces a static tool-call item-type list with a dynamic check that recognizes hosted tool-call formats alongside function_call types; tests and module re-exports updated accordingly. ChangesBridge + Tool-Call Integration
Sequence Diagram(s)sequenceDiagram
participant Router as OpenAI Router
participant Bridge as openai_bridge
participant Orch as McpOrchestrator
participant Reg as FormatRegistry
Router->>Bridge: connect_dynamic_server(config)
Bridge->>Orch: orchestrator.connect_dynamic_server(config.clone())
Orch-->>Bridge: server_key
Bridge->>Reg: populate_from_server_config(&config)
Bridge-->>Router: server_key
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Clean refactor — wrapping connect + registry-populate into bridge helpers eliminates a class of silent-downgrade bugs, and deriving the tool-call suppression set from the descriptor table keeps the single source of truth. All gateway-layer call sites migrated, test coverage pins the expected set. LGTM.
There was a problem hiding this comment.
Code Review
This pull request introduces atomic helper functions in a new openai_bridge::connect module to ensure that the McpOrchestrator and FormatRegistry remain synchronized during MCP server connections. It also refactors tool-call item type checking to use centralized logic in openai_bridge, replacing hardcoded lists in the tool handler. Feedback suggests optimizing connect_dynamic_server by reordering operations to avoid an unnecessary clone of the server configuration.
| pub async fn connect_dynamic_server( | ||
| orchestrator: &McpOrchestrator, | ||
| registry: &FormatRegistry, | ||
| config: McpServerConfig, | ||
| ) -> McpResult<String> { | ||
| let server_key = orchestrator.connect_dynamic_server(config.clone()).await?; | ||
| registry.populate_from_server_config(&config); | ||
| Ok(server_key) | ||
| } |
There was a problem hiding this comment.
To avoid an unnecessary clone of McpServerConfig, which can be a relatively large struct containing multiple HashMaps and Strings, you can populate the FormatRegistry before moving the configuration into the orchestrator. Since FormatRegistry::populate_from_server_config is synchronous and lookup_tool_format (the primary consumer) verifies tool existence against the session inventory, populating the registry slightly earlier is safe even if the subsequent connection attempt fails.
pub async fn connect_dynamic_server(
orchestrator: &McpOrchestrator,
registry: &FormatRegistry,
config: McpServerConfig,
) -> McpResult<String> {
registry.populate_from_server_config(&config);
orchestrator.connect_dynamic_server(config).await
}The two prior commits doubled the same rationale across docs, helper docs, and call sites. Keep one canonical home for each: - Drop the "use the wrapper because…" call-site comments in `mcp_utils.rs` and `mcp_registration.rs`. The module doc on `connect.rs` already owns that rationale. - Shrink `is_hosted_tool_call_item_type` to a one-line definition. The spec invariant lives at the consumer (`is_tool_call_item_type` in `tool_handler.rs`), not the bridge helper. - Trim `is_tool_call_item_type` from 11 lines to 5 — keep the spec invariant, drop the descriptive list of variants (which the linked helper already covers). - Drop `connect_static_server`'s self-evident doc; keep one line on `connect_dynamic_server` for its non-obvious return value. Signed-off-by: Simo Lin <[email protected]>
Summary
openai_bridge/:tool_handler.rsno longer hand-maintainsTOOL_CALL_ITEM_TYPES. The suppression gate now sources its hosted set fromopenai_bridge::is_hosted_tool_call_item_type, which derives from the descriptor table.McpOrchestrator::connect_*+FormatRegistry::populate_from_server_configpair is collapsed into one wrapper (openai_bridge::connect_static_server/connect_dynamic_server) so a future connect path can't silently leave the registry behind.Motivation
After PR #1450 merged, two residual leaks were left from the
mcp-refactordesign:tool_handler.rs:23TOOL_CALL_ITEM_TYPES— a hand-maintained list of every item type whose upstreamoutput_item.doneumbrella the streaming tool loop suppresses. Adding a new hosted family without remembering to update this const lets the upstream's duplicate umbrella slip through ahead of<type>.completed, breaking the spec's "umbrella is the LAST event" invariant.populate_from_server_configparity — connect paths inmcp_utils.rsandworkflow/mcp_registration.rseach had to remember to callformat_registry.populate_from_server_configafterorchestrator.connect_*. Forgetting the second call silently downgrades hosted-tool dispatch tomcp_call. The 283f27f debug log catches it at runtime, but a wrapper makes the bug structurally impossible.Both were the kind of thing reviewers would not catch — neither is a compile error.
Changes
openai_bridge/format_descriptor.rs— addis_hosted_tool_call_item_typederived fromformat_from_type_str.openai_bridge/connect.rs— new module withconnect_static_server+connect_dynamic_serverwrappers.openai_bridge/mod.rs— re-exports.openai/mcp/tool_handler.rs— replace const + helper with the descriptor-derived check; add a regression test pinning the 6 hits + 5 misses.routers/common/mcp_utils.rs— migrate dynamic-connect site to the wrapper.workflow/mcp_registration.rs— migrate static-connect site to the wrapper.Test plan
cargo test -p smg --lib— 751 passedcargo clippy -p smg --lib --tests -- -D warnings— cleancargo fmt -p smg— cleanis_tool_call_item_type_covers_function_call_and_hosted_builtinslocks the 6-item suppression set against silent driftSummary by CodeRabbit