Skip to content

[Claude Code - Stage 2] Server-side gateway tool loop for Messages #115

Description

@ashwing

Part of #113 (Claude Code integration). This is the core value-add.

When the model emits a gateway-owned tool_use (web_search, MCP), the gateway must execute it server-side and continue the loop — never handing it back to Claude Code. The spike proved this is the only mechanism that works: Claude Code errors on any tool it didn't declare, so gateway tools must stay invisible to the client (see #98 → Verification).

Scope:

  • Map Anthropic tool_use/tool_result content blocks to the shared tool framework's gateway-owned vs. client-owned classification.
  • Run the hide-the-call loop (run_until_gateway_tools_complete, landed in refactor: drive gateway tool loop with LoopDecision (PR B) #83) on the Messages path: inject the gateway tool into the upstream call only, execute it, append the tool_result, loop, surface only the final assistant message.
  • Emit correct Anthropic SSE for surfaced calls and suppress hidden gateway calls (reuses the existing emit_gateway_*_events logic on main; keep block index contiguous).
  • Handle parallel tool_use, the full stop_reason set (incl. pause_turn), and streaming input_json_delta args.
  • Record the acceptance cassette — the upstream Messages turns (turn 0: model emits the gateway tool_use; turn 1: final text after the tool_result) so the loop is covered hermetically in CI, not only live. This records the upstream side, so it reuses the test: record real gateway tool loop cassettes (OpenAI + vLLM) #94 recorder pattern and doesn't depend on the loop existing — it can start immediately. May need a small recorder change for the Messages response shape.
  • Acceptance test: a real gateway-owned web_search turn completes end-to-end through /v1/messages with the tool hidden from the client, replayed from that cassette.

Dependency: the shared loop is currently typed to RequestPayload/ResponsePayload; Stage 2's clean form needs it factored to an API-agnostic shape. That factoring is owned by the pending Layering ADR (tool-framework.md Future Work), so this should sequence after / alongside that decision, not reshape the core unilaterally. (The cassette recording sub-task above has no such dependency and can begin right away.)

Happy to take this one.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions