> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dari.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Protocols And Streaming

> The shared OpenAI and Anthropic request, response, and stream contract.

`router.fetch()` accepts two HTTP protocols and normalizes both into a `RouterRequest` before the policy or executor sees them.

| Protocol                | Paths                                       |
| ----------------------- | ------------------------------------------- |
| OpenAI Chat Completions | `/v1/chat/completions`, `/chat/completions` |
| Anthropic Messages      | `/v1/messages`, `/messages`                 |

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:

```ts theme={null}
import { anthropicRequest, openAIChatRequest } from "@dari/router-core";

const openAI = openAIChatRequest({ model: "my-router", messages: [{ role: "user", content: "Hello" }] });
const anthropic = anthropicRequest({ model: "my-router", max_tokens: 256, messages: [{ role: "user", content: "Hello" }] });
```

The incoming `model` becomes `request.requestedModel` but doesn't force the policy to choose a matching candidate.

## Inspect The Selection

<Tabs>
  <Tab title="OpenAI">
    ```json theme={null}
    {
      "dari_routing": {
        "requested_model": "my-router",
        "selected_model": "openai/gpt-5.4-mini",
        "reasoning_effort": "off",
        "reason": "Use the smaller model for plain text."
      }
    }
    ```

    Headers:

    ```
    X-Router-Selected-Model: openai/gpt-5.4-mini
    X-Router-Reasoning-Effort: off
    ```
  </Tab>

  <Tab title="Anthropic">
    The same `dari_routing` object appears on the response body, and in streams on the `message` object inside `message_start`.
  </Tab>
</Tabs>

`requested_model` echoes the `model` the caller sent; `selected_model` is the model that served. The field matches the [managed platform's](/router/send-chat-completions) 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.

<Accordion title="Recognized but rejected fields fail with validation errors">
  A custom executor can use provider-specific features outside `router.fetch`, but those features don't become part of the portable request contract.
</Accordion>

## Errors

Framework failures use `RouterFrameworkError` with a `kind` identifying the boundary and a `code` for the condition.

| Kind              | Meaning                                               |
| ----------------- | ----------------------------------------------------- |
| `invalid_request` | Malformed input or no eligible model                  |
| `not_found`       | Unknown path                                          |
| `configuration`   | Invalid models, executors, selector, or runtime setup |
| `policy`          | Policy failure or invalid selection                   |
| `executor`        | Provider setup, output, or stream failure             |
| `cancelled`       | Work aborted before the response was committed        |

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.
