Clients and gateways¶
Outcome: Know which wire protocol each client uses and how it reaches Router.route().
Surfaces (same daemon, :11435)¶
| Surface | Paths | Typical client |
|---|---|---|
| OpenAI Chat | POST /v1/chat/completions |
Cursor BYOK, VS Code, SDKs |
| Audio | POST /v1/audio/transcriptions, POST /v1/audio/speech |
OpenAI speech-to-text / text-to-speech clients |
| OpenAI Responses | POST /v1/responses, POST /v1/responses/input_tokens, GET /v1/responses/{id}, POST /v1/responses/{id}/cancel, DELETE /v1/responses/{id} |
Newer OpenAI SDKs |
| Anthropic | POST /v1/messages, POST /v1/messages/count_tokens |
Claude Code, Claude Desktop (gateway mode) |
| Ollama facade | /api/chat, /api/generate, /api/embed, /api/tags, … |
JetBrains AI Assistant, ChatGPT Desktop |
| MCP | POST /mcp (JSON-RPC 2.0), POST /v1/mcp/query (deprecated) |
Cursor, Claude Desktop |
Adapters convert to InternalRequest / InternalResponse (internals).
One-click setup¶
daari setup cursor --tunnel
daari setup claude-code
daari setup claude-desktop
daari setup intellij
daari setup vscode
daari setup openai-compat
Sampling parameters¶
Generation controls are read on every surface and mapped to the backend: max_tokens
(max_completion_tokens, max_output_tokens, or num_predict depending on the
surface), top_p, top_k, stop / stop_sequences, seed, frequency_penalty,
and response_format: json_object. Agent SDKs also send parallel_tool_calls,
logit_bias, and top_logprobs — those reach frontier / OpenAI-compat executors
via openai_payload() and split the L0 cache fingerprint when set. Omitted
parameters are not sent, so backend defaults stand.
What a local model cannot do is reported in daari_meta.warning rather than silently
dropped: presence_penalty, n > 1, logprobs, top_logprobs, logit_bias,
parallel_tool_calls, and tool_choice: required.
frequency_penalty is approximated by Ollama's repeat_penalty. Sampling parameters
are part of the cache key, so a 16-token answer is never served to a request asking
for 500.
Image parts (image_url, Anthropic image sources, Ollama images) ride on
Message.images. A vision request is forwarded to a vision-capable tier, or the
gateway returns 422 — it never strips the image and answers as if the question
were text-only.
OpenAI input_audio parts ride on Message.audio. They are rebuilt on the L6
OpenAI payload. When asr.base_url is set, daari also transcribes each clip and
injects the text into the local-tier prompt; see
local speech-to-text.
web_search_options on chat completions escalates to L6 (forwarded on the
frontier payload) or returns a clear 4xx/501 when frontier is disabled /
X-Daari-No-Frontier / budget-blocked — local tiers never pretend they searched.
POST /v1/embeddings is served by the same embedder L1 already uses, so a client
pointed at daari does not need a second host for vectors.
POST /v1/moderations and POST /v1/rerank forward OpenAI/Cohere-shaped bodies
to the configured frontier slot (frontier.enabled + API key / first
frontier.providers[] entry). Point that slot at a moderations- or
rerank-capable OpenAI-compatible root (including /v1). When frontier is off
or no key resolves, both routes return 501 — daari never invents scores.
POST /v1/images/generations is the same governed L6 shape for image
generation (no_frontier, model allowlists, region_pin, spend rows, and
input guardrails on prompt apply before any upstream call).
POST /v1/images/edits is the multipart sibling (image file + prompt, optional
mask) with the same frontier gate and governance.
POST /v1/images/variations is the multipart variation path (image file, no
prompt) under the same L6 governance plane.
POST /v1/ocr accepts a LiteLLM/Mistral-shaped body (document with
document_url or image_url, optional model) and returns a structured
pages[].markdown payload. Routing prefers ocr.base_url (forward to
/ocr), then ocr.vision_model on local Ollama multimodal chat for images,
else L6 frontier passthrough. When none are configured the route returns
503. Virtual-key auth, spend/usage metering, and the ocr rate-limit
family apply like other non-chat modalities.
POST /v1/audio/transcriptions accepts the OpenAI multipart form and forwards
it to asr.base_url when that is set. With no local ASR the route returns
501. asr.frontier_fallback defaults to false, so the file is never sent
to a cloud endpoint unless you turn that flag on. See
local speech-to-text.
POST /v1/audio/speech accepts the OpenAI JSON speech body and forwards it to
tts.base_url when that is set. With no local TTS the route returns 501.
See local text-to-speech.
POST /v1/messages/count_tokens is a local estimate (estimate_tokens on system +
messages + tools), not an L6 round-trip. When L6 itself is Anthropic (provider
anthropic/claude, or anthropic.com in base_url), the frontier executor POSTs
native /v1/messages with x-api-key / anthropic-version headers, not an OpenAI
body at /chat/completions. Prompt-cache hints land on the last system block as
cache_control: ephemeral. Mid-conversation system turns stay in place on that
Anthropic egress path; trailing system messages are still hoisted only when
the request hits a local Ollama tier (llama chat templates otherwise emit empty
streams for Claude Code SessionStart hooks).
POST /mcp is a JSON-RPC 2.0 MCP server (streamable HTTP): initialize,
tools/list, tools/call. Tools are route, stats, and whatever integration
providers are registered (Sourcegraph, GHE, GitLab, configured MCP egress). Auth is
the same Bearer / x-api-key middleware as the rest of the daemon.
POST /v1/mcp/query still works and sends a Deprecation header pointing at /mcp.
The Responses surface round-trips function_call / function_call_output items,
chains turns with previous_response_id, honors store: false, and returns
queued for background: true (poll GET /v1/responses/{id}).
Create-time POST /v1/responses streams also stamp contiguous
sequence_number (from 0) on every non-keepalive event so clients can
reconnect with starting_after.
GET /v1/responses/{id}?stream=true replays stored terminal output as
Responses SSE with its own 0-based sequence_numbers (and matching SSE id:
lines) synthesized from the stored output (not a byte-identical replay of the
original create stream); starting_after skips events with sequence ≤ that
value (past-the-end resume is an empty stream). When starting_after is
omitted, a non-negative integer Last-Event-ID header is treated the same
way (invalid/negative values are ignored → resume from 0); an explicit query
param wins over the header. In-flight background rows (queued / non-terminal)
return 409 — resume is completed-only. Plain GET (no stream) remains JSON.
POST /v1/responses/{id}/cancel is idempotent: an in-flight background job
stops writing tokens and the stored status becomes cancelled; an already
terminal object returns 200 with its current body. DELETE /v1/responses/{id}
removes the row so a later GET is 404. Tenancy matches GET (other virtual keys
see 404, not 403; the master key can cancel or delete any row). include is
rejected with 400 rather than ignored; metadata is echoed.
Only type: function tools are accepted by default — hosted types (web_search,
mcp, file_search, …) return 400 naming the unsupported type(s) and the
supported allowlist. Opt in to local server-side MCP with
integrations.mcp_egress.server_side_responses: true: Responses may then send
type: mcp tools whose server_label matches a configured
integrations.mcp_servers id; daari lists that server's tools, runs egress
tools/call on model tool rounds (SSRF / policy / metering unchanged), and
continues the turn. Unknown labels and other hosted types still 400.
Responses-native sampling maps onto the same SamplingParams as chat:
reasoning.effort → reasoning_effort, text.format → JSON / json_schema
(including name/strict), plus tool_choice, parallel_tool_calls, and
service_tier. prompt_cache_key, prompt_cache_retention, and
prompt_cache_options forward on OpenAI-compatible L6; local tiers name them
in daari_meta.dropped_params / x-daari-dropped-params. Unsupported
truncation is logged (responses_truncation_ignored) rather than 422'd.
POST /v1/responses/input_tokens is a local estimate (estimate_tokens on
instructions + input messages + tools), matching Anthropic
/v1/messages/count_tokens — not an L6 round-trip.
POST /v1/responses/compact summarizes a prior stored conversation (via
previous_response_id and/or input) through the router (typically a local
tier), stores a new response whose conversation is a summary system turn plus
the most recent messages, and returns that Responses object — $0 context
maintenance when routed locally.
Auth¶
Optional server.api_key, virtual keys (daari keys), or SSO for admin surfaces. Health stays open when keyed.
Next¶
→ Cursor guide · MCP · HTTP API