stream: true. The wire format is Server-Sent Events carrying
chat.completion.chunk objects, terminated by data: [DONE] — the same shape
every OpenAI SDK already parses.
Usage
Withstream_options: {"include_usage": true}, every chunk carries
"usage": null and one final chunk — after the finish_reason chunk, with an
empty choices array — carries the real counts:
include_usage, no usage chunk is sent at all.
Reading the cost
A streamed response cannot carryx-nanorouter-charged-usdc. The figure is
not known until the last token, long after the headers went out.
What you get up front is x-nanorouter-request-id. Hold it, and read the
settled figure back once the stream ends:
chargedUsd is null until the ledger closes the request, which happens within
a moment of the stream ending. A null there means “not settled yet”, not
“free”.
Reasoning
Reasoning models stream their summary asdelta.reasoning_content, alongside
the ordinary delta.content:
reasoning_effort defaults to low.
Tool activity
When the router runsweb_search or web_fetch on the model’s behalf, those
rounds happen between upstream calls and emit no message content. To keep
that from looking like a stall, the router sends a chunk with an empty delta and
a top-level nanorouter key:
"status": "done" chunk follows, with ms for the duration and
failed: true if the tool errored.
It is a top-level key rather than something inside delta, because it is not
part of the assistant’s message. Every OpenAI SDK ignores unknown top-level
keys, so this is safe to ignore — but it is what lets you render “searching the
web…” instead of a spinner.
Long, quiet requests
Two things the router does that you may notice: Streamed responses get SSE keepalives every 15 seconds, so an idle connection is not dropped by an intermediary. Buffered (stream: false) responses get whitespace keepalives. A
non-streaming request sends nothing at all until the model finishes, and
ingress proxies drop connections that have been silent too long. After 45
seconds the router commits the 200 and drips spaces until the real body is
ready. JSON tolerates leading whitespace, so JSON.parse, httpx’s .json()
and every OpenAI SDK skip them — but a hand-rolled parser that assumes the first
byte is { will not.