Skip to main content
router.fetch() accepts two HTTP protocols and normalizes both into a RouterRequest before the policy or executor sees them. Requests must be POST with a JSON body. Responses use the same protocol as the request.

Normalized Request

RouterRequest.items is the canonical ordered conversation — messages, tool calls, tool results, prior-turn reasoning, and hosted tool calls. Tools, generation settings, reasoning, response format, metadata, and the stream flag are separate normalized fields. The shared contract covers system/developer/user/assistant/tool messages, text and image input (image detail is auto, low, or high), function tools with choice controls, temperature/top-p/token limits/stop sequences, reasoning controls, text/JSON output formats, metadata, and streaming.

Pure Adapters

Normalize without HTTP dispatch:
The incoming model becomes request.requestedModel but doesn’t force the policy to choose a matching candidate.

Inspect The Selection

Headers:
requested_model echoes the model the caller sent; selected_model is the model that served. The field matches the managed platform’s wire shape, minus the platform-only conversation_id. OpenAI streams include dari_routing in the first completion chunk.

Streaming

Set stream: true in either protocol. Router Core translates executor events into the caller’s SSE format. It primes the stream before committing the HTTP response, so provider connection failures return a normal JSON error. OpenAI stream_options is accepted for ecosystem compatibility and ignored — usage is always emitted. Matching OpenAI’s shape, streamed usage arrives after the finish chunk in a final chunk with an empty choices array, before data: [DONE]. After headers are committed, stream failures are emitted as SSE error events. An empty assistant reply yields a terminal finish event with no content. The policy and executor share one abort signal — request or response-body cancellation aborts both and closes the stream iterator.

Tools

Both protocols normalize tool declarations, choice controls, assistant calls, and results. Arguments may be a JSON string or object in the normalized contract. Stream validation: tool calls must start before their argument deltas and end before finish. Anthropic output is serialized into sequential content blocks even when an executor produces overlapping tool calls.

Hosted Tools

Provider-hosted web_search calls are part of the contract on the OpenAI protocol — they serialize as function-style tool calls named web_search whose arguments carry the provider’s replayable payload. The Anthropic serialization cannot represent them: output containing a hosted tool call on /v1/messages fails with a configuration error (hosted_tool_call_unrepresentable), so route hosted-tool models over the OpenAI protocol.

Reasoning

Reasoning is part of the shared contract. OpenAI callers receive readable thinking as reasoning_content (streamed as deltas) and encrypted continuations as reasoning_details; Anthropic callers receive thinking blocks. Every Anthropic thinking block gets a signature — the provider continuation when one exists, otherwise a portable dari-ir-v1 envelope so the block replays through the request parser. Redacted thinking has no streamable text and is emitted as one complete redacted_thinking block.

Unsupported Features

Router Core implements a shared subset, not full parity. OpenAI Responses, audio, logprobs, multiple choices, prediction, MCP servers, and other provider-specific features are outside this HTTP handler.
A custom executor can use provider-specific features outside router.fetch, but those features don’t become part of the portable request contract.

Errors

Framework failures use RouterFrameworkError with a kind identifying the boundary and a code for the condition. Config is checked at createRouter() time. Input, policy output, and executor output are checked at each boundary. HTTP errors are serialized in the caller’s protocol shape. A known route with a non-POST method returns 405 with an Allow: POST header.