<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>llm-router</title><link>https://richardrh.github.io/llm-router/</link><description>Recent content on llm-router</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://richardrh.github.io/llm-router/index.xml" rel="self" type="application/rss+xml"/><item><title>Why llm-router</title><link>https://richardrh.github.io/llm-router/docs/why-llm-router/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://richardrh.github.io/llm-router/docs/why-llm-router/</guid><description>&lt;h1 id="why-llm-router"&gt;Why llm-router&lt;a class="anchor" href="#why-llm-router" aria-label="Link"&gt;&lt;/a&gt;&lt;/h1&gt;&#10;&lt;p&gt;One narrow job — &lt;em&gt;rewrite one alias into different upstream model ids, spread&#10;across providers, fail over&lt;/em&gt; — done by a single static Go binary instead of a&#10;control plane.&lt;/p&gt;&#10;&lt;h2 id="litellm-vs-llm-router"&gt;LiteLLM vs llm-router&lt;a class="anchor" href="#litellm-vs-llm-router" aria-label="Link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;llm-router&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;LiteLLM&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Licence&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;none published yet&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;MIT&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Performance&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;one ~17 MB static binary; measured idle at 5.9 MB RSS, 0% CPU, binds instantly&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;litellm 1.104 proxy measured idle at 94.8 MB RSS across its two processes, ~25 s from launch to first bind; a Python dependency tree spanning boto3, azure-*, polars, rq, redis and cryptography&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Security&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;constant-time key check, inbound credentials stripped, secrets in env, cli upstreams store nothing, no bodies in logs — details below&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;boots only with a &lt;code&gt;LITELLM_MASTER_KEY&lt;/code&gt;; virtual keys, budgets and teams need a Postgres database — a second credential store to run and protect&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Alias → several providers&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;per-target model ids, weights, weight-0 failover-only targets&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;model_name&lt;/code&gt; with multiple deployments, weights and fallbacks&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Wire translation&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;OpenAI Chat ↔ Anthropic Messages in both directions, streams translated incrementally without buffering&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;serves the OpenAI format and converts to provider SDKs; passthrough routes for some providers&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Usage accounting&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;a SQLite file, one row per request, queryable over &lt;code&gt;/usage&lt;/code&gt; with no database server&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;spend tracking through its database or external callbacks&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Control plane (keys, budgets, teams)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;deliberately none; optional operational console and playground&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;a core feature&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Both were measured idle on the same machine (macOS, arm64, one worker): llm-router&#10;5.9 MB RSS, LiteLLM 94.8 MB — about 16× — and LiteLLM took 25 s to start serving&#10;against llm-router&amp;rsquo;s instant bind. CPU was 0% for both while idle. Under load the&#10;difference is structural: the Go relay copies bytes; the Python proxy parses and&#10;re-serialises.&lt;/p&gt;</description></item><item><title>Running</title><link>https://richardrh.github.io/llm-router/docs/running/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://richardrh.github.io/llm-router/docs/running/</guid><description>&lt;h1 id="running"&gt;Running&lt;a class="anchor" href="#running" aria-label="Link"&gt;&lt;/a&gt;&lt;/h1&gt;&#10;&lt;h2 id="daemon-mode"&gt;Daemon mode&lt;a class="anchor" href="#daemon-mode" aria-label="Link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;llm-router runs as a &lt;strong&gt;long-lived foreground server&lt;/strong&gt;: it binds its listen&#10;address, serves requests until it receives &lt;code&gt;SIGTERM&lt;/code&gt; or &lt;code&gt;SIGINT&lt;/code&gt;, then drains&#10;in-flight streams for up to 20 seconds before exiting. There is no&#10;self-daemonizing flag on purpose — detaching, logging and restart-on-crash are&#10;owned by whatever supervises it (Docker, Kubernetes, &lt;code&gt;systemd&lt;/code&gt;, &lt;code&gt;launchd&lt;/code&gt;,&#10;&lt;code&gt;tmux&lt;/code&gt; on a laptop). Kill signals and restarts are handled cleanly.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./llm-router -config router.yaml &lt;span class="c1"&gt;# -config defaults to ./router.yaml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./llm-router -listen 0.0.0.0:8787 &lt;span class="c1"&gt;# override the bind address&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./llm-router -check &lt;span class="c1"&gt;# validate config, print resolved routes, exit&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For &lt;code&gt;systemd&lt;/code&gt;, use &lt;code&gt;Restart=on-failure&lt;/code&gt;; the process expects to be supervised.&lt;/p&gt;</description></item><item><title>Pointing clients at it</title><link>https://richardrh.github.io/llm-router/docs/pointing-clients/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://richardrh.github.io/llm-router/docs/pointing-clients/</guid><description>&lt;h1 id="pointing-clients-at-it"&gt;Pointing clients at it&lt;a class="anchor" href="#pointing-clients-at-it" aria-label="Link"&gt;&lt;/a&gt;&lt;/h1&gt;&#10;&lt;p&gt;&lt;strong&gt;Any OpenRouter client.&lt;/strong&gt; Change the base URL and nothing else:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;OPENAI_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://127.0.0.1:8787/api/v1 &lt;span class="c1"&gt;# was https://openrouter.ai/api/v1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;omp / pi.&lt;/strong&gt; In &lt;code&gt;~/.omp/agent/models.yml&lt;/code&gt;, use &lt;code&gt;discovery.type: litellm&lt;/code&gt; so&#10;context windows and pricing come from &lt;code&gt;/model_group/info&lt;/code&gt; rather than a guess:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;my-router&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;http://127.0.0.1:8787/api/v1&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ROUTER_KEY &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# only if `apiKey:` is set in router.yaml&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;authHeader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;openai-completions&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;disableStrictTools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;discovery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;litellm&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Aliases then appear as &lt;code&gt;my-router/anthropic/claude-opus-5.5&lt;/code&gt; and&#10;&lt;code&gt;my-router/anthropic/claude-sonnet-5.5&lt;/code&gt;:&lt;/p&gt;</description></item><item><title>Configuration</title><link>https://richardrh.github.io/llm-router/docs/configuration/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://richardrh.github.io/llm-router/docs/configuration/</guid><description>&lt;h1 id="configuration"&gt;Configuration&lt;a class="anchor" href="#configuration" aria-label="Link"&gt;&lt;/a&gt;&lt;/h1&gt;&#10;&lt;p&gt;&lt;code&gt;router.yaml&lt;/code&gt; is the whole config, commented and ready to run. The keys worth&#10;knowing:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Key&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Meaning&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;listen&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Bind address. Loopback by default.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;apiKey&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Shared secret clients must present. &lt;code&gt;&amp;quot;&amp;quot;&lt;/code&gt; accepts any.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;ui.enabled&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Enable the embedded console at &lt;code&gt;/ui/&lt;/code&gt;. Requires a nonempty &lt;code&gt;apiKey&lt;/code&gt;.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;ui.secureCookies&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Use HTTPS cookies and origin checks behind a TLS-terminating proxy. Defaults to false.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;defaults.*&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Retry budget, timeouts, sticky affinity, breaker thresholds.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;store.path&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;SQLite file for usage history. Empty keeps no history.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;store.queueSize&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Records waiting to be written before they are dropped.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;store.maxRows&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Records kept, oldest deleted first. &lt;code&gt;0&lt;/code&gt; keeps everything.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;upstreams.&amp;lt;name&amp;gt;.baseUrl&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Provider root; the protocol&amp;rsquo;s path is appended.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;upstreams.&amp;lt;name&amp;gt;.apiKeyEnv&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Env var holding the key. Preferred over &lt;code&gt;apiKey&lt;/code&gt;.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;upstreams.&amp;lt;name&amp;gt;.kind&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;http&lt;/code&gt; (default), &lt;code&gt;cli&lt;/code&gt; for Claude Code, or &lt;code&gt;codex&lt;/code&gt; for ChatGPT subscription access.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;upstreams.&amp;lt;name&amp;gt;.mode&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Claude Code: &lt;code&gt;one-shot&lt;/code&gt; (default) or &lt;code&gt;persistent&lt;/code&gt;. Codex is always persistent.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;upstreams.&amp;lt;name&amp;gt;.toolMode&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Claude Code: &lt;code&gt;claude&lt;/code&gt; or non-streaming &lt;code&gt;client&lt;/code&gt;. Codex uses streaming-capable native client tools.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;upstreams.&amp;lt;name&amp;gt;.command&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Claude one-shot uses &lt;code&gt;{prompt}&lt;/code&gt;; persistent may use &lt;code&gt;{model}&lt;/code&gt;. Codex defaults to &lt;code&gt;codex app-server --listen stdio://&lt;/code&gt; without placeholders.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;upstreams.&amp;lt;name&amp;gt;.cwd&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Working directory for a CLI process; Claude Code discovers project configuration here.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;upstreams.&amp;lt;name&amp;gt;.timeout&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Bounds one CLI turn. Defaults to &lt;code&gt;maxStreamDuration&lt;/code&gt;.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;upstreams.&amp;lt;name&amp;gt;.maxSessions&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Maximum live persistent CLI processes. Defaults to &lt;code&gt;maxConcurrency&lt;/code&gt;.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;upstreams.&amp;lt;name&amp;gt;.sessionIdleTimeout&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Closes inactive persistent CLI processes. Zero keeps them alive until shutdown.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;upstreams.&amp;lt;name&amp;gt;.authStyle&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;bearer&lt;/code&gt; (the default) or &lt;code&gt;anthropic&lt;/code&gt;.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;upstreams.&amp;lt;name&amp;gt;.headers&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Extra headers per upstream, overriding the client&amp;rsquo;s.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;upstreams.&amp;lt;name&amp;gt;.bodyDrop&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Top-level JSON fields removed before dispatch.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;upstreams.&amp;lt;name&amp;gt;.bodyPatch&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Top-level JSON fields forced.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;upstreams.&amp;lt;name&amp;gt;.maxConcurrency&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;In-flight turn cap. At the limit: skipped, never queued.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;models.&amp;lt;alias&amp;gt;.api&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Wire protocol clients speak for this alias.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;models.&amp;lt;alias&amp;gt;.contextWindow&lt;/code&gt;, &lt;code&gt;maxOutputTokens&lt;/code&gt;, &lt;code&gt;cost&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Advertised to clients for budgeting; not enforced upstream.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;models.&amp;lt;alias&amp;gt;.targets[]&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Where the alias may be served: upstream, model id, weight.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="choosing-a-model-version-provider-and-thinking-mode"&gt;Choosing a model version, provider and thinking mode&lt;a class="anchor" href="#choosing-a-model-version-provider-and-thinking-mode" aria-label="Link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;These are set per &lt;strong&gt;target&lt;/strong&gt; — per provider route — because the same logical&#10;model needs different fields on each:&lt;/p&gt;</description></item><item><title>Usage history</title><link>https://richardrh.github.io/llm-router/docs/usage-history/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://richardrh.github.io/llm-router/docs/usage-history/</guid><description>&lt;h1 id="usage-history"&gt;Usage history&lt;a class="anchor" href="#usage-history" aria-label="Link"&gt;&lt;/a&gt;&lt;/h1&gt;&#10;&lt;p&gt;Point &lt;code&gt;store.path&lt;/code&gt; at a file and the router keeps a row per served request: the&#10;alias, the upstream that answered, the tokens, the latency and the cost.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;~/.llm-router/usage.db&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;queueSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;maxRows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;130000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# about 10 MB, keeping the newest 130k requests&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Query it through the router:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -H &lt;span class="s2"&gt;&amp;#34;Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$ROUTER_KEY&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;http://127.0.0.1:8787/usage?group_by=alias&amp;amp;since=7d&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;or open the file directly, even while the router runs (WAL is enabled):&lt;/p&gt;</description></item><item><title>Design notes</title><link>https://richardrh.github.io/llm-router/docs/design-notes/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://richardrh.github.io/llm-router/docs/design-notes/</guid><description>&lt;h1 id="design-notes"&gt;Design notes&lt;a class="anchor" href="#design-notes" aria-label="Link"&gt;&lt;/a&gt;&lt;/h1&gt;&#10;&lt;p&gt;&lt;strong&gt;Failover happens strictly before the first response byte is committed.&lt;/strong&gt; The&#10;request body is buffered (bounded by &lt;code&gt;maxBodyBytes&lt;/code&gt;) so it can be replayed&#10;against the next target. Once a status line is written, the response belongs to&#10;the client: a truncated or failed stream is logged and returned, never&#10;re-dispatched, because the client cannot un-see it.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Retried statuses.&lt;/strong&gt; 429, 401, 402, 403, 5xx. Ordinary 4xx are the client&amp;rsquo;s&#10;bug and are returned unchanged instead of being fired at every provider. A 429&#10;parks the upstream without counting against its health — it is busy, not broken.&lt;/p&gt;</description></item><item><title>Codex subscription</title><link>https://richardrh.github.io/llm-router/docs/codex/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://richardrh.github.io/llm-router/docs/codex/</guid><description>&lt;h1 id="codex-subscription"&gt;Codex subscription&lt;a class="anchor" href="#codex-subscription" aria-label="Link"&gt;&lt;/a&gt;&lt;/h1&gt;&#10;&lt;p&gt;&lt;code&gt;kind: codex&lt;/code&gt; connects the router to the official Codex app-server over stdio.&#10;Codex owns ChatGPT sign-in, credential storage, and token refresh. The router&#10;never reads or forwards subscription tokens. This is separate from OpenAI API&#10;billing and from any Codex login stored by omp.&lt;/p&gt;&#10;&lt;h2 id="install-and-connect"&gt;Install and connect&lt;a class="anchor" href="#install-and-connect" aria-label="Link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;The adapter is verified against Codex &lt;strong&gt;0.163.0&lt;/strong&gt;. Install that release under the&#10;same OS account that runs the router, for example:&lt;/p&gt;</description></item></channel></rss>