@askalf/dario 3.16.0 → 3.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <p align="center">
2
2
  <h1 align="center">dario</h1>
3
- <p align="center"><strong>Your Claude Max subscription, in every tool you use.<br>Plus OpenAI, Groq, OpenRouter, Ollama one local URL. Your tools don't change a line.</strong></p>
3
+ <p align="center"><strong>A universal LLM router that runs on your machine.<br>One local endpoint, every provider — Anthropic, OpenAI, Groq, OpenRouter, Ollama, any OpenAI-compat URL. Your tools point here and stop caring which vendor is upstream.</strong></p>
4
4
  </p>
5
5
 
6
6
  <p align="center">
@@ -12,40 +12,33 @@
12
12
  </p>
13
13
 
14
14
  ```bash
15
- npm install -g @askalf/dario && dario login && dario proxy
15
+ npm install -g @askalf/dario && dario proxy
16
16
  ```
17
17
 
18
- Three commands. Your Claude Max subscription is now the Claude backend for **Cursor, Continue, Aider, Zed, OpenCode, Claude Code itself, your own scripts** anything that speaks the Anthropic or OpenAI API. Point them at `http://localhost:3456` and the model name decides where the request goes: `claude-opus-4-6` hits your Max plan, `gpt-4o` hits your OpenAI key, `llama-3.3-70b` hits your Groq / OpenRouter / local vLLM.
18
+ One command, one local URL, every provider behind it. Point `ANTHROPIC_BASE_URL`, `OPENAI_BASE_URL`, or anything that speaks either protocol at `http://localhost:3456` and the **model name** decides where the request goes:
19
19
 
20
- **Zero runtime dependencies. ~6,500 lines of TypeScript. 376 tests across 12 suites. [SLSA-attested](https://www.npmjs.com/package/@askalf/dario) on every release. Nothing phones home, ever.**
20
+ - `claude-opus-4-7`, `claude-sonnet-4-6`, `opus`, `sonnet`, `haiku` **Anthropic** (via your Claude Max/Pro subscription, or a direct API key, your choice)
21
+ - `gpt-4o`, `o3-mini`, `chatgpt-4o-latest` → **OpenAI**
22
+ - `llama-3.3-70b`, `deepseek-v3`, anything else → **Groq**, **OpenRouter**, **local LiteLLM**, **vLLM**, **Ollama**, whichever OpenAI-compat backend you wired up
23
+ - Force a backend explicitly with a prefix: `openai:gpt-4o`, `groq:llama-3.3-70b`, `local:qwen-coder`, `claude:opus`
21
24
 
22
- ---
23
-
24
- ## Before and after
25
+ Switching providers is a **model-name change** in your tool. Not a reconfigure. Not new base URLs. Not new API keys. Not a new SDK import. **Zero runtime dependencies. ~7,600 lines of TypeScript across ~15 files. ~640 assertions across 20 test suites. [SLSA-attested](https://www.npmjs.com/package/@askalf/dario) on every release. Nothing phones home, ever.**
25
26
 
26
- Before dario, your $200/mo Claude Max is a Claude-Code-only thing. Every other tool you open bills per-token against the Anthropic API:
27
+ ---
27
28
 
28
- | Tool | Claude backend |
29
- |---|---|
30
- | Claude Code | subscription ✓ |
31
- | Cursor | pay per token |
32
- | Aider | pay per token |
33
- | Continue | pay per token |
34
- | Zed | pay per token |
35
- | Your own scripts | pay per token |
29
+ ## What it actually does
36
30
 
37
- After `dario proxy`, same list:
31
+ You point every tool at one URL. Dario reads each request, decides which backend owns it, and forwards the request in that backend's native protocol.
38
32
 
39
- | Tool | Claude backend | OpenAI backend | Local / OpenRouter / Groq |
33
+ | Client speaks | Model in request | dario routes to | What happens |
40
34
  |---|---|---|---|
41
- | Claude Code | subscription | gpt-4o passthrough | passthrough |
42
- | Cursor | **subscription ✓** | gpt-4o passthrough | passthrough |
43
- | Aider | **subscription ✓** | gpt-4o passthrough | passthrough |
44
- | Continue | **subscription ✓** | gpt-4o passthrough | passthrough |
45
- | Zed | **subscription ✓** | gpt-4o passthrough | passthrough |
46
- | Your own scripts | **subscription ✓** | gpt-4o passthrough | passthrough |
35
+ | Anthropic Messages API | `claude-*` / `opus` / `sonnet` / `haiku` | Claude backend | OAuth swap + (optional) CC template replay → `api.anthropic.com` |
36
+ | Anthropic Messages API | `gpt-*`, `llama-*`, etc. | OpenAI-compat backend | Anthropic → OpenAI translation, forwarded to configured backend |
37
+ | OpenAI Chat Completions | `gpt-*` / `o1-*` / `o3-*` | OpenAI-compat backend | Passthrough: auth swap, body forwarded byte-for-byte |
38
+ | OpenAI Chat Completions | `claude-*` | Claude backend | OpenAI → Anthropic translation, then the Claude backend path |
39
+ | Either protocol | `<provider>:<model>` | Forced by prefix | Explicit override for ambiguous names |
47
40
 
48
- The trick: every outbound request on the Claude path is rebuilt to look exactly like a request Claude Code itself would make — system prompt, tool definitions, fingerprint headers, billing tag, beta flags — using a **live-extracted template from your actually-installed CC binary** that self-heals on every Anthropic release. Anthropic's classifier sees a CC session because, from the wire up, it is one. That's what keeps your usage on subscription billing instead of API overage.
41
+ The tool doesn't know. The backend doesn't know. Dario is the seam.
49
42
 
50
43
  ---
51
44
 
@@ -55,15 +48,17 @@ The trick: every outbound request on the Claude path is rebuilt to look exactly
55
48
  # Install
56
49
  npm install -g @askalf/dario
57
50
 
58
- # Claude subscription path — uses your Claude Code OAuth if CC is installed,
59
- # runs its own OAuth flow otherwise
51
+ # Any combination of backends:
52
+
53
+ # 1. Claude via your Claude Max / Pro subscription (uses your Claude Code
54
+ # OAuth if CC is installed; runs its own OAuth flow otherwise)
60
55
  dario login
61
56
 
62
- # OpenAI or any OpenAI-compat backend (optional, additive)
57
+ # 2. OpenAI or any OpenAI-compat endpoint
63
58
  dario backend add openai --key=sk-proj-...
64
- dario backend add groq --key=gsk_... --base-url=https://api.groq.com/openai/v1
65
- dario backend add openrouter --key=sk-or-... --base-url=https://openrouter.ai/api/v1
66
- dario backend add local --key=anything --base-url=http://127.0.0.1:11434/v1
59
+ dario backend add groq --key=gsk_... --base-url=https://api.groq.com/openai/v1
60
+ dario backend add openrouter --key=sk-or-... --base-url=https://openrouter.ai/api/v1
61
+ dario backend add local --key=anything --base-url=http://127.0.0.1:11434/v1
67
62
 
68
63
  # Start the proxy
69
64
  dario proxy
@@ -75,23 +70,27 @@ export OPENAI_BASE_URL=http://localhost:3456/v1
75
70
  export OPENAI_API_KEY=dario
76
71
  ```
77
72
 
78
- That's it. Every tool that honors these standard env vars now reaches every backend you configured. Switching providers is a model-name change in your tool not a reconfigure of every SDK you've got.
73
+ That's it. Every tool that honors these standard env vars now reaches every backend you configured. No per-tool reconfiguration. No SDK changes. One URL, one fake key, every real provider behind it.
74
+
75
+ Something broken? `dario doctor` prints a single aggregated health report — dario version, Node, platform, CC binary compat, template source + age + drift, OAuth status, pool state, configured backends. Paste that instead of screenshots when you file an issue.
79
76
 
80
77
  ---
81
78
 
82
79
  ## Why you'll install this
83
80
 
84
- **You pay for Claude Max but only use it in Claude Code.** Cursor, Aider, Zed, Continue they all want API keys and bill per-token while your $200/mo subscription sits idle. Dario routes Claude requests from all of them against your plan instead. The live fingerprint extractor reads your installed CC binary on every startup, so when Anthropic rotates the template, dario picks up the new one automatically no release needed, no user action.
81
+ **You want one URL for every provider.** Cursor, Aider, Continue, Zed, OpenHands, Claude Code, your own scripts every tool you own has its own per-provider config. Dario collapses that into a single `localhost:3456` that speaks both Anthropic and OpenAI protocols and routes by model name. Switching providers is a model-name change in your tool, not a reconfigure of every SDK on your laptop.
85
82
 
86
- **You hit 5-hour rate limits on long agent runs.** Add a second / third subscription with `dario accounts add work` and pool mode routes each request to whichever account has the most headroom. **v3.13.0 session stickiness** pins a conversation to one account for its lifetime so the Anthropic prompt cache isn't shredded across accounts mid-conversation a 5–10× token cost saving on cache-reused turns of a long agent session. If a 429 lands mid-request, dario fails the *in-flight request* over to a different account without your client ever seeing the error.
83
+ **You pay for Claude Max but only use it in Claude Code.** Cursor, Aider, Zed, Continue they all want API keys and bill per-token while your $200/mo subscription sits idle. Dario's Claude backend routes requests from all of them through your plan by replaying the exact Claude Code wire shape (template, tools, headers, billing tag) that Anthropic's classifier expects for subscription billing. Section [Claude subscription backend](#2-claude-subscription-backend) has the full mechanics.
87
84
 
88
- **You want to share capacity with a trusted group without surveilling each other.** The **v3.13.0 sealed-sender overflow protocol** uses RSA blind signatures (Chaum 1983, implemented from scratch over Node's `crypto`) so members of a trust group can lend unused Claude capacity to each other with cryptographic unlinkability. A lender verifies "this is a valid group member" without learning *which* member. It's the privacy primitive that makes friends-pool possible and as far as I know, no other Claude router ships this.
85
+ **You hit rate limits on long agent runs.** Add a second / third Claude subscription with `dario accounts add work` and pool mode routes each request to whichever account has the most headroom. **Session stickiness** (v3.13.0) pins a multi-turn conversation to one account so the Anthropic prompt cache survives the run. **In-flight 429 failover** retries the same request against a different account before your client sees an error. See [Multi-account pool mode](#multi-account-pool-mode).
89
86
 
90
- **You want the proxy layer off the wire entirely.** **Shim mode** (v3.12.0, hardened in v3.13.0) is an in-process `globalThis.fetch` patch injected via `NODE_OPTIONS=--require`. No HTTP hop, no port to bind, no `BASE_URL` to set. Anthropic literally cannot detect this from outside the CC process without shipping signed-binary integrity checks against `globalThis` from inside their own binary. `dario shim -- claude --print "hi"` and CC thinks it's talking directly to `api.anthropic.com`.
87
+ **You run a coding agent that isn't Claude Code.** Cline, Roo Code, Cursor, Windsurf, Continue.dev, GitHub Copilot, OpenHands, OpenClaw, Hermes they each ship their own tool schemas and their own validators. Dario's universal `TOOL_MAP` (**71 entries as of v3.15**) pre-maps every major coding agent's tool names to Claude Code's native set on the outbound path and rebuilds to your agent's exact expected shape on the inbound path. No `--preserve-tools`, no fingerprint loss, no validator errors. See [Agent compatibility](#agent-compatibility).
91
88
 
92
- **You want provider independence.** Switching from Claude to GPT-4o to Llama-3.3-70b to a local Qwen-Coder is a **model-name change** in your tool. Not a reconfigure. Not new base URLs. Not new API keys. Not a new SDK import. One URL, one fake key, every real provider behind it.
89
+ **You want the proxy layer off the wire entirely.** **Shim mode** (v3.12, hardened in v3.13) is an in-process `globalThis.fetch` patch injected via `NODE_OPTIONS=--require`. No HTTP hop, no port to bind, no `BASE_URL` to set. `dario shim -- claude --print "hi"` and CC thinks it's talking directly to `api.anthropic.com`. See [Shim mode](#shim-mode).
93
90
 
94
- **You want to actually audit the thing.** ~6,500 lines of TypeScript across ~15 files. Zero runtime dependencies (`npm ls --production` confirms). Credentials stored at `~/.dario/` with `0600` permissions. `127.0.0.1`-only by default. Every release [SLSA-attested](https://www.npmjs.com/package/@askalf/dario) via GitHub Actions. Nothing phones home. It's small enough to read in a weekend.
91
+ **You want to share capacity with a trusted group without surveilling each other.** The **sealed-sender overflow protocol** (v3.13) uses RSA blind signatures (Chaum 1983, implemented from scratch over Node's `crypto`) so members of a trust group can lend unused Claude capacity to each other with cryptographic unlinkability. A lender verifies "this is a valid group member" without learning *which* member. See [Sealed-sender overflow](#sealed-sender-overflow-protocol).
92
+
93
+ **You want to actually audit the thing.** ~7,600 lines of TypeScript across ~15 files. Zero runtime dependencies (`npm ls --production` confirms). Credentials at `~/.dario/` with `0600` permissions. `127.0.0.1`-only by default. Every release [SLSA-attested](https://www.npmjs.com/package/@askalf/dario) via GitHub Actions. Nothing phones home. Small enough to read in a weekend.
95
94
 
96
95
  ---
97
96
 
@@ -100,10 +99,10 @@ That's it. Every tool that honors these standard env vars now reaches every back
100
99
  **Best fit:**
101
100
 
102
101
  - **Developers using multiple LLMs across multiple tools** tired of juggling base URLs, keys, and per-tool provider configs.
102
+ - **Teams running local or hosted OpenAI-compat servers** (LiteLLM, vLLM, Ollama, Groq, OpenRouter, self-hosted) who want one stable local endpoint every tool can reuse.
103
+ - **Anyone building AI coding tools** who wants provider independence without writing an OpenAI ↔ Anthropic translator themselves.
103
104
  - **Claude Max / Pro subscribers** who want their subscription usable from every tool on their machine, not just Claude Code.
104
- - **Teams running local or hosted OpenAI-compat servers** (LiteLLM, vLLM, Ollama, Groq, OpenRouter) who want one stable local endpoint that every tool reuses.
105
105
  - **Power users on multi-agent workloads** who want multi-account pooling, session stickiness, and in-flight 429 failover on their own machine, against their own subscriptions.
106
- - **Anyone building AI coding tools** who wants provider independence without writing an OpenAI ↔ Anthropic translator themselves.
107
106
 
108
107
  **Not a fit if:**
109
108
 
@@ -115,29 +114,11 @@ That's it. Every tool that honors these standard env vars now reaches every back
115
114
 
116
115
  ## Backends
117
116
 
118
- Dario's routing is organized around **backends**, each with its own auth and its own target. Backends are swappable adapters — add one, your tools reach it at `localhost:3456` with whatever API shape they already speak.
119
-
120
- ### 1. Claude subscription backend (built in)
121
-
122
- OAuth-backed Claude Max / Pro, billed against your plan instead of the API. Activated by `dario login`.
123
-
124
- **What it does:**
117
+ Dario's routing is organized around **backends**. Each is a swappable adapter — add one, your tools reach it through `localhost:3456` in whichever API shape they already speak. You can run zero, one, or all of them concurrently.
125
118
 
126
- - Every request is replaced with a Claude Code template before it goes upstream — 25 tool definitions, ~25KB system prompt, exact CC field order, exact beta headers, exact metadata structure. Only the conversation content is preserved. Anthropic's classifier sees what looks like a Claude Code session because, from the wire up, it *is* one — and that's what keeps your usage on subscription billing instead of Extra Usage.
127
- - **Live fingerprint extraction** (v3.11.0). Dario spawns your installed `claude` binary against a loopback MITM endpoint on startup, captures its outbound request, and extracts the live template (system prompt, tools, user-agent, beta flags, and as of v3.13.0 the exact header insertion order — replayed on the wire by the shim since v3.13.0 and by the proxy since v3.16.0). Eliminates the "Anthropic ships a new CC, dario is stale for 48 hours" window. Cached at `~/.dario/cc-template.live.json` with a 24h TTL. Falls back to the bundled snapshot if CC isn't installed.
128
- - **Billing tag** reconstructed using CC's own algorithm: `x-anthropic-billing-header: cc_version=<version>.<build_tag>; cc_entrypoint=cli; cch=<5-char-hex>;` where `build_tag = SHA-256(seed + chars[4,7,20] of user message + version).slice(0,3)`.
129
- - **OAuth config auto-detection** from the installed CC binary. When Anthropic rotates `client_id`, authorize URL, or scopes, dario picks up the new values on the next run without needing a release.
130
- - **Multi-account pool mode** — see below. Automatic when 2+ accounts are configured.
131
- - **Framework scrubbing** — known fingerprint tokens (`OpenClaw`, `sessions_*` prefixes, orchestration tags) stripped from system prompt and message content before the request leaves your machine.
132
- - **Bun auto-relaunch** — when Bun is installed, dario relaunches under it so the TLS fingerprint matches CC's runtime. Without Bun, dario runs on Node.js.
119
+ ### 1. OpenAI-compat backend
133
120
 
134
- **Passthrough mode** (`dario proxy --passthrough`) does an OAuth swap and nothing else — no template, no identity, no scrubbing. Use it when the upstream tool already builds a Claude-Code-shaped request on its own and you just need the token auth.
135
-
136
- **Detection scope.** The Claude backend is a per-request layer. Template replay and scrubbing are designed to be indistinguishable from Claude Code at the request level. What they *cannot* defend against is Anthropic's session-level behavioral classifier, which operates on cumulative per-OAuth aggregates (token throughput, conversation depth, streaming duration, inter-arrival timing). The practical answer to that is **pool mode** — distributing load across multiple subscriptions so no one account accumulates enough signal to trip anything. See the [FAQ entry](#faq) for the full mechanism.
137
-
138
- ### 2. OpenAI-compat backend
139
-
140
- Any provider that speaks the OpenAI Chat Completions API. Activated by:
121
+ Any provider that speaks the OpenAI Chat Completions API.
141
122
 
142
123
  ```bash
143
124
  # OpenAI itself (default base URL)
@@ -155,7 +136,7 @@ dario backend add local --key=anything --base-url=http://127.0.0.1:4000/v1
155
136
 
156
137
  Credentials live at `~/.dario/backends/<name>.json` with mode `0600`.
157
138
 
158
- **How it routes.** When the OpenAI-compat backend is configured, each request at `/v1/chat/completions` is checked:
139
+ **How it routes.** On `/v1/chat/completions` the request is inspected and forwarded:
159
140
 
160
141
  | Request model | Route |
161
142
  |---|---|
@@ -163,15 +144,38 @@ Credentials live at `~/.dario/backends/<name>.json` with mode `0600`.
163
144
  | `claude-*` (or `opus` / `sonnet` / `haiku`) | Claude subscription backend |
164
145
  | Anything else | Claude backend with OpenAI-compat translation |
165
146
 
166
- Passthrough for the OpenAI-compat backend is literal: client request body goes upstream as-is, only the `Authorization` header is swapped and the URL is pointed at `baseUrl + /chat/completions`. Response body streams back unchanged.
147
+ The request body goes upstream as-is; only the `Authorization` header is swapped and the URL is pointed at `baseUrl + /chat/completions`. Streaming is forwarded byte-for-byte.
148
+
149
+ Force a backend with a **provider prefix** on the model field (`openai:gpt-4o`, `groq:llama-3.3-70b`, `claude:opus`, `local:qwen-coder`) regardless of what the model name looks like — see [Provider prefix](#provider-prefix).
150
+
151
+ ### 2. Claude subscription backend
167
152
 
168
- You can also force the backend with a **provider prefix** on the model field (`openai:gpt-4o`, `groq:llama-3.3-70b`, `claude:opus`, `local:qwen-coder`) regardless of what the model name looks like. See [Provider prefix](#provider-prefix).
153
+ OAuth-backed Claude Max / Pro, billed against your plan instead of the API. Activated by `dario login`.
154
+
155
+ **What it does.** Every outbound Claude request is rebuilt to look exactly like a request Claude Code itself would make — system prompt, tool definitions, fingerprint headers, billing tag, beta flags, **even the exact header insertion order** — using a live-extracted template from your actually-installed CC binary that self-heals on every Anthropic release. Anthropic's classifier sees a CC session because, from the wire up, it *is* one. That's what keeps your usage on subscription billing instead of API overage.
156
+
157
+ **Key mechanisms:**
158
+
159
+ - **Live fingerprint extraction** (v3.11). Dario spawns your installed `claude` binary against a loopback MITM endpoint on startup, captures its outbound request, and extracts the live template (system prompt, tools, user-agent, beta flags, **header insertion order** as of v3.13, replayed on the wire by the shim since v3.13 and the proxy since v3.16). Eliminates the "Anthropic ships a new CC, dario is stale for 48 hours" window. Cached at `~/.dario/cc-template.live.json` with a 24h TTL. Falls back to the bundled snapshot if CC isn't installed.
160
+ - **Drift detection** (v3.17). On startup dario probes the installed `claude` binary and compares against the captured template. Mismatch triggers a forced refresh and prints a one-line warning. Users never silently sit on a stale template again.
161
+ - **Compat matrix** (v3.17). `SUPPORTED_CC_RANGE = { min: "1.0.0", maxTested: "2.1.104" }` is encoded in code. Installed CC outside that band prints a warn (untested above) or fail (below min) — zero-dep dotted-numeric comparator, no `semver` import per the dep policy.
162
+ - **Billing tag** reconstructed using CC's own algorithm: `x-anthropic-billing-header: cc_version=<version>.<build_tag>; cc_entrypoint=cli; cch=<5-char-hex>;` where `build_tag = SHA-256(seed + chars[4,7,20] of user message + version).slice(0,3)`.
163
+ - **OAuth config auto-detection** from the installed CC binary. When Anthropic rotates `client_id`, authorize URL, or scopes, dario picks up the new values on the next run without needing a release.
164
+ - **Multi-account pool mode** — see [Multi-account pool mode](#multi-account-pool-mode). Automatic when 2+ accounts are configured.
165
+ - **Framework scrubbing** — known fingerprint tokens (`OpenClaw`, `sessions_*` prefixes, orchestration tags) stripped from system prompt and message content before the request leaves your machine.
166
+ - **Atomic cache writes + cache corruption recovery** (v3.17). Template cache writes go through pid-qualified `.tmp` + `rename`, so an OS crash mid-write doesn't leave a half-written file. Unparseable cache files get quarantined to `cc-template.live.json.bad-<timestamp>` and dario self-heals on the next capture.
167
+ - **OAuth single-flight** (v3.17). Two concurrent refreshes for the same account alias now share one outbound `POST /oauth/token`, so the pool's background refresh timer and a user-triggered request at the same millisecond can't race and invalidate each other's refresh token.
168
+ - **Bun auto-relaunch** — when Bun is installed, dario relaunches under it so the TLS fingerprint matches CC's runtime. Without Bun, dario runs on Node.js.
169
+
170
+ **Passthrough mode** (`dario proxy --passthrough`) does an OAuth swap and nothing else — no template, no identity, no scrubbing. Use it when the upstream tool already builds a Claude-Code-shaped request on its own.
171
+
172
+ **Detection scope.** The Claude backend is a per-request layer. Template replay and scrubbing are designed to be indistinguishable from CC at the request level. What they *cannot* defend against is Anthropic's session-level behavioral classifier, which operates on cumulative per-OAuth aggregates (token throughput, conversation depth, streaming duration, inter-arrival timing). The practical answer is **pool mode** — distribute load across multiple subscriptions so no single account accumulates enough signal to trip anything.
169
173
 
170
174
  ---
171
175
 
172
176
  ## Multi-account pool mode
173
177
 
174
- Dario can manage multiple Claude subscriptions and route each request to the account with the most headroom. Single-account dario is unchanged — pool mode activates **only** when `~/.dario/accounts/` contains 2+ accounts.
178
+ Pool mode activates automatically when `~/.dario/accounts/` contains 2+ accounts. Single-account dario is unchanged.
175
179
 
176
180
  ```bash
177
181
  dario accounts add work
@@ -187,19 +191,19 @@ Each request picks the account with the highest headroom:
187
191
  headroom = 1 - max(util_5h, util_7d)
188
192
  ```
189
193
 
190
- The response's `anthropic-ratelimit-unified-*` headers are parsed back into the pool so the next selection sees fresh utilization. An account that returns a 429 is marked `rejected` and routed around until its window resets. When every account is exhausted, requests queue for up to 60 seconds waiting for headroom to reappear. Accounts can mix plans — Max and Pro accounts can sit in the same pool; dario doesn't care about tier, only headroom.
194
+ The response's `anthropic-ratelimit-unified-*` headers are parsed back into the pool so the next selection sees fresh utilization. An account that returns a 429 is marked `rejected` and routed around until its window resets. When every account is exhausted, requests queue for up to 60 seconds waiting for headroom to reappear. Plans can mix freely — Max and Pro accounts sit in the same pool; dario doesn't care about tier, only headroom.
191
195
 
192
- ### Session stickiness (v3.13.0)
196
+ ### Session stickiness (v3.13)
193
197
 
194
- Multi-turn agent sessions now pin to one account for the life of the conversation, so the Anthropic prompt cache isn't destroyed by account rotation between turns.
198
+ Multi-turn agent sessions pin to one account for the life of the conversation, so the Anthropic prompt cache isn't destroyed by account rotation between turns.
195
199
 
196
- **The problem.** Claude Max prompt cache is scoped to `{account × cache_control key}`. When the pool rotates a long agent conversation across accounts on headroom alone, turn 1 builds a cache entry on account A, turn 2 lands on account B and reads nothing from A's cache — paying full cache-create cost again. For a long agent session that's a 5–10× token cost multiplier on the cache-reused portion of every turn after the first.
200
+ **The problem.** Claude prompt cache is scoped to `{account × cache_control key}`. When the pool rotates a long agent conversation across accounts on headroom alone, turn 1 builds a cache entry on account A, turn 2 lands on account B and reads nothing from A's cache — paying full cache-create cost again. For a long agent session that's a **5–10× token-cost multiplier** on every turn after the first.
197
201
 
198
- **The fix.** Dario hashes a conversation's first user message into a 16-hex-char `stickyKey` (SHA-256 truncated, deterministic) and binds the key to whichever account `select()` would have picked on turn 1. Subsequent turns re-use that account as long as it's still healthy (not rejected, token not near expiry, headroom > 2%). On 429 failover, dario rebinds the key to the new account so the next turn doesn't re-select the exhausted one. 6h TTL, 2,000-entry cap, lazy cleanup. No client cooperation required — it works through the normal proxy path with nothing to configure.
202
+ **The fix.** Dario hashes a conversation's first user message into a 16-hex-char `stickyKey` (SHA-256 truncated, deterministic) and binds the key to whichever account `select()` would have picked on turn 1. Subsequent turns re-use that account as long as it's still healthy (not rejected, token not near expiry, headroom > 2%). On 429 failover, dario rebinds the key to the new account so the next turn doesn't re-select the exhausted one. 6h TTL, 2,000-entry cap, lazy cleanup. No client cooperation required.
199
203
 
200
- ### In-flight 429 failover (v3.8.0+)
204
+ ### In-flight 429 failover (v3.8+)
201
205
 
202
- When a Claude request hits a 429 mid-flight, dario retries the *same request* against a different account before the client ever sees an error. The client sees one successful response; the pool sees the rejected account go cold until its window resets. Combined with session stickiness, this means long agent runs survive pool-level exhaustion without dropping user-facing turns.
206
+ When a Claude request hits a 429 mid-flight, dario retries the *same request* against a different account before the client sees an error. The client sees one successful response; the pool sees the rejected account go cold until its window resets. Combined with session stickiness, long agent runs survive pool-level exhaustion without dropping user-facing turns.
203
207
 
204
208
  ### Inspection
205
209
 
@@ -208,29 +212,31 @@ curl http://localhost:3456/accounts # per-account utilization, claim, sticky
208
212
  curl http://localhost:3456/analytics # per-account / per-model stats, burn rate, exhaustion predictions
209
213
  ```
210
214
 
215
+ Every request carries a `billingBucket` field (`subscription` / `subscription_fallback` / `extra_usage` / `api` / `unknown`) so you can see which bucket each request billed against and a `subscriptionPercent` headline number tells you at a glance whether dario is actually routing through your subscription or silently falling to API overage.
216
+
211
217
  ---
212
218
 
213
- ## Sealed-sender overflow protocol (v3.13.0)
219
+ ## Sealed-sender overflow protocol (v3.13)
214
220
 
215
221
  Trust-group members can lend each other Claude capacity with **cryptographic unlinkability**: a lender can verify the borrower is a valid group member without learning *which* member, so no one in the pool can surveil another through borrow telemetry.
216
222
 
217
- **The primitive.** RSA blind signatures (Chaum 1983), implemented from scratch on top of Node's `crypto` module using `RSA_NO_PADDING` for raw `m^e mod n` / `c^d mod n` primitives. Full-Domain Hash via MGF1-SHA256 (with counter retry) prevents multiplicative forgery. The flow: the group admin signs *blinded* tokens in a batch without seeing their real values; the member unblinds locally to obtain valid RSA-FDH signatures on random tokens the admin has never seen and can never correlate back to the member. When a member spends a token with a lender, the lender verifies the signature with the group public key — it proves "some member got this signed" without identifying who.
223
+ **The primitive.** RSA blind signatures (Chaum 1983), implemented from scratch on top of Node's `crypto` module using `RSA_NO_PADDING` for raw `m^e mod n` / `c^d mod n` primitives. Full-Domain Hash via MGF1-SHA256 (with counter retry) prevents multiplicative forgery. The flow: the group admin signs *blinded* tokens in a batch without seeing their real values; the member unblinds locally to obtain valid RSA-FDH signatures on random tokens the admin has never seen. When a member spends a token with a lender, the lender verifies the signature with the group public key — it proves "some member got this signed" without identifying who.
218
224
 
219
- **What this is, and what it isn't.** This is **privacy between group members**, not anonymity from Anthropic. When a lender accepts a borrow, the actual upstream request still lands under the lender's attributable Claude account identity — Anthropic sees the lender as the originator, exactly as they would for any other request on that account. The cryptographic unlinkability protects group members from each other: no member can correlate borrow telemetry back to another member's identity.
225
+ **What this is, and what it isn't.** This is **privacy between group members**, not anonymity from Anthropic. When a lender accepts a borrow, the actual upstream request still lands under the lender's attributable Claude account identity — Anthropic sees the lender as the originator, exactly as they would for any other request on that account. The cryptographic unlinkability protects group members from each other.
220
226
 
221
- **What's in v3.13.0:**
227
+ **What's in the release:**
222
228
 
223
229
  - `src/sealed-pool.ts` — ~550 lines. `GroupAdmin` / `GroupMember` / `GroupLender` classes with quota/expiry enforcement, SHA-256-hashed double-spend set, JSON wire envelope (`{v:1, groupId, token, sig, request}`), and key export/import for distributing group credentials.
224
230
  - `POST /v1/pool/borrow` endpoint on the proxy, gated on `~/.dario/group.json`. Positioned before `checkAuth` — the group signature *is* the authentication, so doubling it with a local API key would add nothing. Verified borrows delegate to `pool.select()` and forward upstream under the lender's account.
225
- - 57 test assertions covering raw RSA roundtrip, unlinkability, wrong-key / tampered-sig / wrong-group / double-spend rejection, key export/import, admin membership / quota / expiry enforcement, concurrent-borrow double-spend prevention, and end-to-end two-member unlinkability.
231
+ - 85 test assertions in `test/sealed-pool.mjs` covering raw RSA roundtrip, unlinkability, wrong-key / tampered-sig / wrong-group / double-spend rejection, key export/import, admin membership / quota / expiry enforcement, concurrent-borrow double-spend prevention, and end-to-end two-member unlinkability.
226
232
 
227
- Full feature-parity with `/v1/messages` (streaming, inside-request 429 failover, reverse tool mapping) for borrowed requests is intentionally a follow-up — v3.13.0 ships the cryptographic primitive and a working minimal endpoint; full integration layers on top.
233
+ Full feature-parity with `/v1/messages` (streaming, inside-request 429 failover, reverse tool mapping) for borrowed requests is intentionally a follow-up — the current release ships the cryptographic primitive and a working minimal endpoint; full integration layers on top.
228
234
 
229
235
  ---
230
236
 
231
237
  ## Shim mode
232
238
 
233
- *Experimental, opt-in. The default path is still the HTTP proxy — shim mode is a second transport, not a replacement.*
239
+ *Experimental, opt-in. The proxy is still the default — shim mode is a second transport, not a replacement.*
234
240
 
235
241
  Shim mode runs a child process with an **in-process `globalThis.fetch` patch** that rewrites the child's outbound requests to `api.anthropic.com/v1/messages` exactly the way the proxy would, then sends them directly from the child to Anthropic. No localhost HTTP hop. No port to bind. No `ANTHROPIC_BASE_URL` to set.
236
242
 
@@ -239,30 +245,44 @@ dario shim -- claude --print "hello"
239
245
  dario shim -v -- claude --print "hello" # verbose
240
246
  ```
241
247
 
242
- Under the hood: `dario shim` spawns the child with `NODE_OPTIONS=--require <dario-runtime.cjs>` and a unix socket / named pipe for telemetry. The runtime patches `globalThis.fetch` only for Anthropic messages requests, applies the same template replay the proxy does, and relays per-request events back to the parent so analytics still work. Every other fetch call in the child is untouched and failsafe-passes through on any internal error.
248
+ Under the hood: `dario shim` spawns the child with `NODE_OPTIONS=--require <dario-runtime.cjs>` and a unix socket / named pipe for telemetry. The runtime patches `globalThis.fetch` only for Anthropic messages requests, applies the same template replay the proxy does, and relays per-request events back to the parent so analytics still work. Every other fetch call is untouched and fails safe on any internal error.
243
249
 
244
- **Why it matters.** Anthropic can fingerprint a proxy via TLS, headers, IP, or `BASE_URL` env. They literally cannot easily detect a `globalThis.fetch` monkey-patch from inside their own process without shipping signed-binary integrity checks against `globalThis` — and even then, the shim runs *before* CC's code loads, so it could patch the integrity check too. This is the transport that has the longest half-life against classifier evolution.
250
+ **Why it matters.** Anthropic can fingerprint a proxy via TLS, headers, IP, or `BASE_URL` env. They literally cannot easily detect a `globalThis.fetch` monkey-patch from inside their own process without shipping signed-binary integrity checks against `globalThis` from inside the CC binary — and even then, the shim runs *before* CC's code loads, so it could patch the integrity check too. The longest-half-life transport against classifier evolution.
245
251
 
246
- **v3.13.0 hardening.** The shim got a significant robustness pass:
247
-
248
- - **Runtime detection** — `detectRuntime()` checks `globalThis.Bun` / `globalThis.Deno` / `process.versions.node` and logs a warning for non-Node runtimes. Canary for the day Anthropic ships a Bun-compiled CC.
249
- - **Template mtime-based auto-reload** — long-running child processes pick up mid-session fingerprint refreshes from dario's live capture without restart.
250
- - **Strict defensive `rewriteBody`** — the previous logic accepted `length >= 1` on the system array and invented `[1]`/`[2]` blocks out of thin air. Now requires exactly `length === 3` with all-text blocks; any mismatch passes through unchanged. Passthrough on an unknown shape is safer than blind replacement.
251
- - **`rewriteHeaders` honors captured header order** — the live fingerprint capture now records the exact order CC emits headers on the wire, and the shim replays that order on every outbound request. Header sequence alone is a fingerprint vector; v3.13.0 removes it from the shim, and v3.16.0 closes the same gap on the proxy via the shared `orderHeadersForOutbound` helper so both transports produce an identical wire shape.
252
- - **`checkVersionDrift`** — logs when the child's UA `cc_version` differs from the template's, so stale-cache windows during CC upgrades are visible in debug output.
252
+ **v3.13 hardening** added runtime detection (canary for the day Anthropic ships a Bun-compiled CC), template mtime-based auto-reload (long-running children pick up mid-session fingerprint refreshes without restart), strict defensive `rewriteBody` (requires exactly 3 text blocks, passes through on any mismatch instead of inventing structure), and header-order replay (honors captured CC header sequence so the shim matches CC wire-exact).
253
253
 
254
254
  **When to use shim mode:**
255
- - Running a single CC instance on a locked-down machine where binding a local port is inconvenient or forbidden.
255
+ - Running a single CC instance on a locked-down machine where binding a local port is inconvenient.
256
256
  - Wrapping one-off scripts (`dario shim -- node my-agent.js`) without setting up environment variables.
257
- - Debugging a specific child process in isolation — verbose logs are scoped to that process.
257
+ - Debugging a specific child process in isolation — verbose logs are scoped to that child.
258
258
  - You suspect Anthropic is fingerprinting your proxy traffic and you want to take the proxy off the wire.
259
259
 
260
- **When to stay on the proxy** (which is still the default):
261
- - Multi-client routing. The proxy serves every tool on the machine through one endpoint; the shim wraps one child at a time.
260
+ **When to stay on the proxy** (default):
261
+ - Multi-client routing. The proxy serves every tool on the machine through one endpoint; shim wraps one child at a time.
262
262
  - Multi-account pool mode. Pooling across subscriptions needs a shared OAuth pool the proxy owns — a shim patch inside one child can't see pool state across other processes.
263
- - Anything that isn't a Node / Bun child. The shim relies on `NODE_OPTIONS`, so non-JS runtimes (Python SDK, a Go CLI) still need the proxy.
263
+ - Anything that isn't a Node / Bun child. The shim relies on `NODE_OPTIONS`, so Python SDKs or Go CLIs still need the proxy.
264
+
265
+ ---
266
+
267
+ ## Agent compatibility
268
+
269
+ As of **v3.18**, dario's built-in `TOOL_MAP` carries **~65 schema-verified entries** covering the tool schemas of every major coding agent. On the Claude backend, tool calls translate to CC's native `Bash / Read / Write / Edit / Glob / Grep / WebSearch / WebFetch` on the outbound path (keeping the subscription fingerprint intact) and rebuild to your agent's exact expected shape on the inbound path (so your validator is happy). No flag required.
270
+
271
+ | Agent | Covered tool names (subset) |
272
+ |---|---|
273
+ | Claude Code | default — CC's own tools |
274
+ | Cline / Roo Code | `execute_command`, `write_to_file`, `replace_in_file`, `apply_diff`, `list_files`, `search_files`, `read_file` |
275
+ | Cursor | `run_terminal_cmd`, `edit_file`, `search_replace`, `codebase_search`, `grep_search`, `file_search`, `list_dir`, `read_file` (`target_file`) |
276
+ | Windsurf | `run_command`, `view_file`, `write_to_file`, `replace_file_content`, `find_by_name`, `grep_search`, `list_dir`, `search_web`, `read_url_content` |
277
+ | Continue.dev | `builtin_run_terminal_command`, `builtin_read_file`, `builtin_create_new_file`, `builtin_edit_existing_file`, `builtin_file_glob_search`, `builtin_grep_search`, `builtin_ls` |
278
+ | GitHub Copilot | `run_in_terminal`, `insert_edit_into_file`, `semantic_search`, `codebase_search`, `list_dir`, `fetch_webpage` |
279
+ | OpenHands | `execute_bash`, `str_replace_editor` |
280
+ | OpenClaw | `exec`, `process`, `web_search`, `web_fetch`, `browser`, `message` |
281
+ | Hermes | `terminal`, `patch`, `web_extract`, `clarify` |
264
282
 
265
- See the [v3.12.0 release notes](https://github.com/askalf/dario/releases/tag/v3.12.0) for the original design writeup and [v3.13.0](https://github.com/askalf/dario/releases/tag/v3.13.0) for the hardening notes.
283
+ If your agent's tool names aren't pre-mapped, there are two escape hatches: **`--preserve-tools`** (forward your schema verbatim, lose the CC fingerprint) or **`--hybrid-tools`** (keep the fingerprint, fill request-context fields from headers). See [Custom tool schemas](#custom-tool-schemas).
284
+
285
+ The OpenAI-compat backend forwards tool definitions byte-for-byte and doesn't need any of this.
266
286
 
267
287
  ---
268
288
 
@@ -272,6 +292,7 @@ See the [v3.12.0 release notes](https://github.com/askalf/dario/releases/tag/v3.
272
292
  |---|---|
273
293
  | `dario login` | Log in to the Claude backend (detects CC credentials or runs its own OAuth flow) |
274
294
  | `dario proxy` | Start the local API proxy on port 3456 |
295
+ | `dario doctor` | Aggregated health report — dario / Node / CC binary + compat / template + drift / OAuth / pool / backends |
275
296
  | `dario status` | Show Claude backend OAuth token health and expiry |
276
297
  | `dario refresh` | Force an immediate Claude token refresh |
277
298
  | `dario logout` | Delete stored Claude credentials |
@@ -289,9 +310,9 @@ See the [v3.12.0 release notes](https://github.com/askalf/dario/releases/tag/v3.
289
310
  | Flag / env | Description | Default |
290
311
  |---|---|---|
291
312
  | `--passthrough` / `--thin` | Thin proxy for the Claude backend — OAuth swap only, no template injection | off |
292
- | `--preserve-tools` / `--keep-tools` | Keep client tool schemas instead of remapping to CC's `Bash/Read/Grep/Glob/WebSearch/WebFetch`. Required for clients whose tools have fields CC doesn't — see [Custom tool schemas](#custom-tool-schemas). | off |
313
+ | `--preserve-tools` / `--keep-tools` | Keep client tool schemas instead of remapping to CC's. Required for clients whose tools have fields CC doesn't — see [Custom tool schemas](#custom-tool-schemas). | off |
293
314
  | `--hybrid-tools` / `--context-inject` | Remap to CC tools **and** inject request-context values (`sessionId`, `requestId`, `channelId`, `userId`, `timestamp`) into client-declared fields CC's schema doesn't carry. See [Hybrid tool mode](#hybrid-tool-mode). | off |
294
- | `--model=<name>` | Force a model. Shortcuts (`opus`, `sonnet`, `haiku`), full IDs (`claude-opus-4-6`), or a **provider prefix** (`openai:gpt-4o`, `groq:llama-3.3-70b`, `claude:opus`, `local:qwen-coder`) to force the backend server-wide. See [Provider prefix](#provider-prefix). | passthrough |
315
+ | `--model=<name>` | Force a model. Shortcuts (`opus`, `sonnet`, `haiku`), full IDs (`claude-opus-4-7`), or a **provider prefix** (`openai:gpt-4o`, `groq:llama-3.3-70b`, `claude:opus`, `local:qwen-coder`) to force the backend server-wide. See [Provider prefix](#provider-prefix). | passthrough |
295
316
  | `--port=<n>` | Port to listen on | `3456` |
296
317
  | `--host=<addr>` / `DARIO_HOST` | Bind address. Use `0.0.0.0` for LAN, or a specific IP (e.g. a Tailscale interface). When non-loopback, also set `DARIO_API_KEY`. | `127.0.0.1` |
297
318
  | `--verbose` / `-v` | Log every request | off |
@@ -316,7 +337,7 @@ client = anthropic.Anthropic(
316
337
  )
317
338
 
318
339
  msg = client.messages.create(
319
- model="claude-opus-4-6",
340
+ model="claude-opus-4-7",
320
341
  max_tokens=1024,
321
342
  messages=[{"role": "user", "content": "Hello!"}],
322
343
  )
@@ -339,9 +360,9 @@ msg = client.chat.completions.create(
339
360
  messages=[{"role": "user", "content": "Hello!"}],
340
361
  )
341
362
 
342
- # claude-opus-4-6 routes to the Claude subscription backend — same SDK, same URL
363
+ # claude-opus-4-7 routes to the Claude subscription backend — same SDK, same URL
343
364
  claude_msg = client.chat.completions.create(
344
- model="claude-opus-4-6",
365
+ model="claude-opus-4-7",
345
366
  messages=[{"role": "user", "content": "Hello!"}],
346
367
  )
347
368
  ```
@@ -357,7 +378,7 @@ const client = new Anthropic({
357
378
  });
358
379
 
359
380
  const msg = await client.messages.create({
360
- model: "claude-opus-4-6",
381
+ model: "claude-opus-4-7",
361
382
  max_tokens: 1024,
362
383
  messages: [{ role: "user", content: "Hello!" }],
363
384
  });
@@ -370,7 +391,7 @@ export OPENAI_BASE_URL=http://localhost:3456/v1
370
391
  export OPENAI_API_KEY=dario
371
392
  ```
372
393
 
373
- Any tool that accepts an OpenAI base URL works. Use Claude model names (`claude-opus-4-6`, `opus`, `sonnet`, `haiku`) for the Claude backend, or GPT-family names for the configured OpenAI-compat backend.
394
+ Any tool that accepts an OpenAI base URL works. Use Claude model names (`claude-opus-4-7`, `opus`, `sonnet`, `haiku`) for the Claude backend, or GPT-family names for the configured OpenAI-compat backend.
374
395
 
375
396
  ### curl
376
397
 
@@ -379,7 +400,7 @@ Any tool that accepts an OpenAI base URL works. Use Claude model names (`claude-
379
400
  curl http://localhost:3456/v1/messages \
380
401
  -H "Content-Type: application/json" \
381
402
  -H "anthropic-version: 2023-06-01" \
382
- -d '{"model":"claude-opus-4-6","max_tokens":1024,"messages":[{"role":"user","content":"Hello!"}]}'
403
+ -d '{"model":"claude-opus-4-7","max_tokens":1024,"messages":[{"role":"user","content":"Hello!"}]}'
383
404
 
384
405
  # OpenAI backend via OpenAI format
385
406
  curl http://localhost:3456/v1/chat/completions \
@@ -394,7 +415,7 @@ All supported. Claude backend: full Anthropic SSE format plus OpenAI-SSE transla
394
415
 
395
416
  ### Provider prefix
396
417
 
397
- Any request's `model` field can be written as `<provider>:<name>` to force which backend handles it, regardless of what the model name looks like. Useful when regex-based routing (`gpt-*` → OpenAI, `claude-*` → Claude) doesn't match — for example when routing a `llama-3.3-70b` request through an OpenAI-compat backend, or when you want the same model name to go to different providers on different requests.
418
+ Any request's `model` field can be written as `<provider>:<name>` to force which backend handles it, regardless of what the model name looks like. Useful when regex-based routing (`gpt-*` → OpenAI, `claude-*` → Claude) doesn't match — for example routing a `llama-3.3-70b` request through OpenRouter, or making the same model name go to different providers on different requests.
398
419
 
399
420
  Recognized prefixes:
400
421
 
@@ -408,33 +429,15 @@ Recognized prefixes:
408
429
  | `claude:` | Claude subscription backend |
409
430
  | `anthropic:` | Claude subscription backend |
410
431
 
411
- The prefix gets stripped before the request goes upstream — the backend only sees the bare model name. Unrecognized prefixes are ignored, so ollama-style `llama3:8b` passes through untouched. `dario proxy --model=openai:gpt-4o` applies the prefix to every request server-wide.
412
-
413
- ### Agent compatibility
414
-
415
- As of **v3.15.0**, dario's built-in `TOOL_MAP` has **71 entries** covering the tool schemas of every major coding agent. If you're running one of these, no flag is required on the Claude backend — tool calls translate to CC's native `Bash/Read/Write/Edit/Glob/Grep/WebSearch/WebFetch` on the outbound path (so the subscription fingerprint stays intact) and rebuild to your agent's exact expected shape on the inbound path (so your validator is happy).
416
-
417
- | Agent | Covered tool names (subset) |
418
- |---|---|
419
- | Claude Code | default — CC's own tools |
420
- | Cline / Roo Code | `execute_command`, `write_to_file`, `replace_in_file`, `apply_diff`, `list_files`, `search_files`, `read_file` |
421
- | Cursor | `run_terminal_cmd`, `edit_file`, `search_replace`, `codebase_search`, `grep_search`, `file_search`, `list_dir`, `read_file` (`target_file`) |
422
- | Windsurf | `run_command`, `view_file`, `write_to_file`, `replace_file_content`, `find_by_name`, `grep_search`, `list_dir`, `search_web`, `read_url_content` |
423
- | Continue.dev | `builtin_run_terminal_command`, `builtin_read_file`, `builtin_create_new_file`, `builtin_edit_existing_file`, `builtin_file_glob_search`, `builtin_grep_search`, `builtin_ls` |
424
- | GitHub Copilot | `run_in_terminal`, `insert_edit_into_file`, `semantic_search`, `codebase_search`, `list_dir`, `fetch_webpage` |
425
- | OpenHands | `execute_bash`, `str_replace_editor` |
426
- | OpenClaw | `exec`, `process`, `web_search`, `web_fetch`, `browser`, `message` |
427
- | Hermes | `terminal`, `patch`, `web_extract`, `clarify` |
428
-
429
- If your agent's tool names aren't in this list, you've got two escape hatches below: **`--preserve-tools`** (forward your schema verbatim, lose the CC fingerprint) or **`--hybrid-tools`** (keep the fingerprint, fill request-context fields from headers). Open an issue with your agent's tool schema and we'll add a pre-mapping entry.
432
+ The prefix gets stripped before the request goes upstream — the backend only sees the bare model name. Unrecognized prefixes are ignored, so Ollama-style `llama3:8b` passes through untouched. `dario proxy --model=openai:gpt-4o` applies the prefix to every request server-wide.
430
433
 
431
434
  ### Custom tool schemas
432
435
 
433
- By default, on the Claude backend, dario replaces your client's tool definitions with the real Claude Code tools (`Bash`, `Read`, `Write`, `Edit`, `Grep`, `Glob`, `WebSearch`, `WebFetch`) and translates parameters back and forth. That's how dario looks like CC on the wire, which is what lets your request bill against your Claude subscription instead of API pricing. For the agents listed in [Agent compatibility](#agent-compatibility) above, the translation is pre-mapped and runs automatically — nothing to configure.
436
+ By default, on the Claude backend, dario replaces your client's tool definitions with the real Claude Code tools (`Bash`, `Read`, `Write`, `Edit`, `Grep`, `Glob`, `WebSearch`, `WebFetch`) and translates parameters back and forth. That's how dario looks like CC on the wire, which is what lets your request bill against your Claude subscription instead of API pricing. For the agents listed in [Agent compatibility](#agent-compatibility), the translation is pre-mapped and runs automatically — nothing to configure.
434
437
 
435
- The trade-off shows up when you're running something that *isn't* in the pre-mapped list and whose tools carry fields CC's schema doesn't have — a `sessionId`, a custom request id, a channel-bound context token, a `confidence` score the model is supposed to emit. Those fields don't survive the round trip. The model only ever sees `Bash({command})`, responds with `Bash({command})`, and dario's reverse map rebuilds your tool call without the fields the model never saw. Your validator then rejects the call for a missing required field.
438
+ The trade-off shows up when you're running something that *isn't* in the pre-mapped list and whose tools carry fields CC's schema doesn't have — a `sessionId`, a custom request id, a channel-bound context token, a `confidence` score the model is supposed to emit. Those fields don't survive the round trip.
436
439
 
437
- Symptom: your tool calls come back looking stripped-down, or your runtime complains about a required field being absent *only when routed through dario's Claude backend*, while the same tools work fine against a direct API key or the OpenAI-compat backend.
440
+ Symptom: your tool calls come back looking stripped-down, or your runtime complains about a required field being absent *only when routed through dario's Claude backend*.
438
441
 
439
442
  Fix: run dario with `--preserve-tools`. That skips the CC tool remap entirely, passes your client's tool definitions through to the model unchanged, and lets the model populate every field your schema expects.
440
443
 
@@ -444,7 +447,7 @@ dario proxy --preserve-tools
444
447
 
445
448
  The cost: requests no longer look like CC on the wire, so the CC subscription fingerprint is gone. On a Max/Pro plan, that means the request may be counted against your API usage rather than your subscription quota. If you're on API-key billing already, `--preserve-tools` is free; if you're using dario specifically to route against a subscription, [hybrid tool mode](#hybrid-tool-mode) below is the compromise that keeps both.
446
449
 
447
- The openai-compat backend (OpenRouter, OpenAI, Groq, local LiteLLM) is unaffected — it forwards tool definitions byte-for-byte and doesn't need this flag.
450
+ The OpenAI-compat backend is unaffected — it forwards tool definitions byte-for-byte and doesn't need this flag.
448
451
 
449
452
  ### Hybrid tool mode
450
453
 
@@ -454,7 +457,7 @@ For the very common case where the "missing" fields on your client's tool are **
454
457
  dario proxy --hybrid-tools
455
458
  ```
456
459
 
457
- **How it works.** On each request, dario builds a `RequestContext` from headers (`x-session-id`, `x-request-id`, `x-channel-id`, `x-user-id`) plus its own generated ids and the current timestamp. After `translateBack` produces the client-shaped tool call on the response path, any field declared on the client's tool schema whose name matches a known context field (`sessionId`/`session_id`, `requestId`/`request_id`, `channelId`/`channel_id`, `userId`/`user_id`, `timestamp`/`created_at`/`createdAt`) and isn't already populated gets filled from the context. Fields the model genuinely populated via `translateBack` are never overwritten.
460
+ **How it works.** On each request, dario builds a `RequestContext` from headers (`x-session-id`, `x-request-id`, `x-channel-id`, `x-user-id`) plus its own generated ids and the current timestamp. After `translateBack` produces the client-shaped tool call on the response path, any field declared on the client's tool schema whose name matches a known context field (`sessionId`/`session_id`, `requestId`/`request_id`, `channelId`/`channel_id`, `userId`/`user_id`, `timestamp`/`created_at`/`createdAt`) and isn't already populated gets filled from the context. Fields the model genuinely populated are never overwritten.
458
461
 
459
462
  **When to use which flag:**
460
463
 
@@ -465,8 +468,6 @@ dario proxy --hybrid-tools
465
468
  | Your custom fields need the model's reasoning (e.g. `confidence`, `reasoning_trace`, `tool_selection_rationale`) | `--preserve-tools` | The model has to see the real schema to populate these. Accept the fingerprint loss. |
466
469
  | Your client's tools are already a subset of CC's `Bash/Read/Write/Edit/Grep/Glob/WebSearch/WebFetch` | *(neither)* | Default mode works as-is. |
467
470
 
468
- Hybrid mode was built to resolve [#29](https://github.com/askalf/dario/issues/29) cleanly for OpenClaw-style agents whose `process` tool declares `sessionId`, after the full provider-comparison diagnostic from [@boeingchoco](https://github.com/boeingchoco) made clear that the problem wasn't fixable in the translation layer alone.
469
-
470
471
  ### Library mode
471
472
 
472
473
  ```typescript
@@ -492,12 +493,12 @@ curl http://localhost:3456/health
492
493
  |---|---|
493
494
  | `POST /v1/messages` | Anthropic Messages API (Claude backend) |
494
495
  | `POST /v1/chat/completions` | OpenAI-compatible Chat API (routes by model name) |
495
- | `POST /v1/pool/borrow` | Sealed-sender borrow endpoint (v3.13.0). Accepts group-signed tokens and forwards the request through the lender's pool. |
496
+ | `POST /v1/pool/borrow` | Sealed-sender borrow endpoint. Accepts group-signed tokens and forwards the request through the lender's pool. |
496
497
  | `GET /v1/models` | Model list (Claude models — OpenAI models come from the OpenAI backend directly) |
497
498
  | `GET /health` | Proxy health + OAuth status + request count |
498
499
  | `GET /status` | Detailed Claude OAuth token status |
499
500
  | `GET /accounts` | Pool snapshot including sticky binding count (pool mode only) |
500
- | `GET /analytics` | Per-account / per-model stats, burn rate, exhaustion predictions. Every request carries a `billingBucket` field (`subscription` / `subscription_fallback` / `extra_usage` / `api` / `unknown`) so you can see which bucket each request billed against. |
501
+ | `GET /analytics` | Per-account / per-model stats, burn rate, exhaustion predictions, `billingBucket` + `subscriptionPercent` per request |
501
502
 
502
503
  ---
503
504
 
@@ -507,16 +508,17 @@ Dario handles your OAuth tokens and API keys locally. Here's why you can trust i
507
508
 
508
509
  | Signal | Status |
509
510
  |---|---|
510
- | **Source code** | ~6,500 lines of TypeScript across ~15 files — small enough to audit in a weekend |
511
+ | **Source code** | ~7,600 lines of TypeScript across ~15 files — small enough to audit in a weekend |
511
512
  | **Dependencies** | 0 runtime dependencies. Verify: `npm ls --production` |
512
513
  | **npm provenance** | Every release is [SLSA-attested](https://www.npmjs.com/package/@askalf/dario) via GitHub Actions with sigstore provenance attached to the transparency log |
513
514
  | **Security scanning** | [CodeQL](https://github.com/askalf/dario/actions/workflows/codeql.yml) runs on every push and weekly |
514
- | **Test footprint** | 376 assertions across 12 files. Full `npm test` green on every release |
515
+ | **Test footprint** | ~640 assertions across 20 files. Full `npm test` green on every release |
515
516
  | **Credential handling** | Tokens and API keys never logged, redacted from errors, stored with `0600` permissions |
516
517
  | **OAuth flow** | PKCE (Proof Key for Code Exchange), no client secret |
517
518
  | **Network scope** | Binds to `127.0.0.1` by default. `--host` allows LAN/mesh with `DARIO_API_KEY` gating. Upstream traffic goes only to the configured backend target URLs over HTTPS |
518
519
  | **SSRF protection** | `/v1/messages` hits `api.anthropic.com` only; `/v1/chat/completions` hits the configured backend `baseUrl` only — hardcoded allowlist |
519
520
  | **Telemetry** | None. Zero analytics, tracking, or data collection |
521
+ | **Atomic cache writes + corruption recovery** | v3.17 — template cache writes are pid-qualified `.tmp` + `rename`, corrupt cache files are quarantined and regenerated instead of crashing startup |
520
522
  | **Audit trail** | [CHANGELOG.md](CHANGELOG.md) documents every release with file-level rationale |
521
523
 
522
524
  Verify the npm tarball matches this repo:
@@ -541,11 +543,26 @@ Claude Max and Claude Pro. Any plan that lets you use Claude Code.
541
543
  Should work if your plan includes Claude Code access. Not widely tested yet — open an issue with results.
542
544
 
543
545
  **Do I need Claude Code installed?**
544
- Recommended for the Claude backend, not strictly required. With CC installed, `dario login` picks up your credentials automatically, and the live fingerprint extractor reads your CC binary on every startup so the template stays current. Without CC, dario runs its own OAuth flow and falls back to the bundled template snapshot.
546
+ Recommended for the Claude backend, not strictly required. With CC installed, `dario login` picks up your credentials automatically, and the live fingerprint extractor reads your CC binary on every startup so the template stays current. Without CC, dario runs its own OAuth flow and falls back to the bundled template snapshot. Drift detection (v3.17) warns you if your installed CC doesn't match the captured template, so upgrade windows don't silently ship stale templates.
545
547
 
546
548
  **Do I need Bun?**
547
549
  Optional, recommended for Claude-backend requests. Dario auto-relaunches under Bun when available so the TLS fingerprint matches CC's runtime. Without Bun, dario runs on Node.js and works fine; the TLS fingerprint is the only difference.
548
550
 
551
+ **Can I use dario without a Claude subscription?**
552
+ Yes. Skip `dario login`, just run `dario backend add openai --key=...` (or any OpenAI-compat URL) and `dario proxy`. Claude-backend requests will return an authentication error; OpenAI-compat requests will work normally. Dario becomes a local OpenAI-compat router with no Claude involvement.
553
+
554
+ **Can I route non-OpenAI providers through dario?**
555
+ Yes — anything that speaks the OpenAI Chat Completions API. Groq, OpenRouter, LiteLLM, vLLM, Ollama's openai-compat mode, your own vLLM server, any hosted inference endpoint that exposes `/v1/chat/completions`. Just `dario backend add <name> --key=... --base-url=...`.
556
+
557
+ **Something's wrong. Where do I start?**
558
+ `dario doctor`. One command, one aggregated report — dario version, Node, platform, CC binary compat, template source + age + drift, OAuth status, pool state, backends, home dir. Exit code 1 if any check fails. Paste the output when you file an issue.
559
+
560
+ **What happens when Anthropic rotates the OAuth config?**
561
+ Dario auto-detects OAuth config from the installed Claude Code binary. When CC ships a new version with rotated values, dario picks them up on the next run. Cache at `~/.dario/cc-oauth-cache-v3.json`, keyed by the CC binary fingerprint.
562
+
563
+ **What happens when Anthropic changes the CC request template?**
564
+ Dario extracts the live request template from your installed Claude Code binary on startup — the system prompt, tool schemas, user-agent, beta flags, and header insertion order — and uses those to replay requests instead of a version pinned into dario itself. When CC ships a new version with a tweaked template, the next `dario proxy` run picks it up automatically. Drift detection (v3.17) forces a refresh when the installed CC version changes under dario.
565
+
549
566
  **First time setup on a fresh Claude account.**
550
567
  If dario is the first thing you run against a brand-new Claude account, prime the account with a few real Claude Code commands first:
551
568
  ```bash
@@ -554,46 +571,25 @@ claude --print "hello"
554
571
  ```
555
572
  This establishes a session baseline. Without priming, brand-new accounts occasionally see billing classification issues on first use.
556
573
 
557
- **What happens when Anthropic rotates the OAuth config?**
558
- Dario auto-detects OAuth config from the installed Claude Code binary. When CC ships a new version with rotated values, dario picks them up on the next run. Cache at `~/.dario/cc-oauth-cache-v3.json`, keyed by the CC binary fingerprint.
559
-
560
- **What happens when Anthropic changes the CC request template?**
561
- Dario extracts the live request template from your installed Claude Code binary on startup — the system prompt, tool schemas, user-agent, beta flags, and as of v3.13.0 the exact header insertion order — and uses those to replay requests instead of a version pinned into dario itself. When CC ships a new version with a tweaked template, the next `dario proxy` run picks it up automatically. Fallback: the hand-curated `src/cc-template-data.json` bundled with the release.
562
-
563
574
  **I'm hitting rate limits on the Claude backend. What do I do?**
564
- Claude subscriptions have rolling 5-hour and 7-day usage windows. Check utilization with Claude Code's `/usage` command or the [statusline](https://code.claude.com/docs/en/statusline). For multi-agent workloads, add more accounts and let pool mode distribute the load: `dario accounts add <alias>`. As of v3.13.0, pool mode also keeps long conversations pinned to one account so the Anthropic prompt cache isn't destroyed by rotation.
575
+ Claude subscriptions have rolling 5-hour and 7-day usage windows. Check utilization with Claude Code's `/usage` command or the [statusline](https://code.claude.com/docs/en/statusline). For multi-agent workloads, add more accounts and let pool mode distribute the load: `dario accounts add <alias>`. Session stickiness keeps long conversations pinned to one account so the prompt cache isn't destroyed by rotation.
565
576
 
566
577
  **I'm seeing `representative-claim: seven_day` in my rate-limit headers instead of `five_hour`. Am I being downgraded to API billing?**
567
578
 
568
- **No.** You're still on subscription billing. Both `five_hour` and `seven_day` are the same subscription billing mode — they're just two different accounting buckets inside it.
569
-
570
- Here's the full picture. Every Claude Max and Pro subscription has **two rolling usage windows**:
571
-
572
- - **5-hour window** — your short-term usage bucket. Refreshes on a rolling 5-hour schedule.
573
- - **7-day window** — your longer-term usage bucket. Refreshes on a rolling 7-day schedule. Intentionally larger than the 5-hour one so you can keep working past brief bursts of heavy usage.
574
-
575
- When Anthropic bills a request, it decides which bucket to charge it against based on your current utilization. That decision comes back in the `anthropic-ratelimit-unified-representative-claim` response header:
579
+ **No.** You're still on subscription billing. Both `five_hour` and `seven_day` are the same subscription billing mode — two different accounting buckets inside it.
576
580
 
577
581
  | Claim | What it means |
578
582
  |---|---|
579
583
  | `five_hour` | You're well inside your 5-hour window; billing against the short-term bucket. |
580
- | `seven_day` | You've exhausted (or come close to exhausting) the 5-hour window for this rolling cycle, so Anthropic is now charging this request against the 7-day bucket. **Still subscription billing. Still your plan.** Not API pricing, not overage. |
581
- | `overage` | Both subscription windows are effectively exhausted. *This* is where per-token Extra Usage charges kick in — if you've enabled Extra Usage on the account. If you haven't, you get 429'd instead. |
582
-
583
- **Seeing `seven_day` is a healthy state.** Your Max/Pro plan is doing exactly what it's supposed to do: letting you keep working past short bursts of heavy use by absorbing them into the larger 7-day bucket. Your subscription is not being "downgraded." When your 5-hour window rolls forward enough, the claim on new requests will go back to `five_hour` on its own.
584
+ | `seven_day` | You've exhausted (or come close to exhausting) the 5-hour window for this rolling cycle, so Anthropic is charging this request against the 7-day bucket. **Still subscription billing. Still your plan.** Not API pricing, not overage. |
585
+ | `overage` | Both subscription windows are effectively exhausted. *This* is where per-token Extra Usage charges kick in — if you've enabled Extra Usage on the account. If not, you get 429'd instead. |
584
586
 
585
- **Practical answer if `seven_day` is painful for your workload.** Add more Claude subscriptions to the pool. Each account has its own independent 5-hour and 7-day windows, and pool mode routes each request to the account with the most headroom. With 2-3 accounts, you almost never see the `seven_day` bucket get touched. `dario accounts add <alias>`.
587
+ Seeing `seven_day` is a healthy state. Your Max/Pro plan is doing exactly what it's supposed to do: letting you keep working past short bursts of heavy use by absorbing them into the larger 7-day bucket. When your 5-hour window rolls forward enough, the claim on new requests will go back to `five_hour` on its own. If the 7-day bucket is painful, add more Claude subscriptions to the pool each account has its own independent 5h/7d windows, and pool mode routes each request to the account with the most headroom.
586
588
 
587
589
  Standalone writeup: [Discussion #32 — why you see `representative-claim: seven_day` and why it's not a downgrade](https://github.com/askalf/dario/discussions/32).
588
590
 
589
591
  **My multi-agent workload is getting reclassified to overage even though dario template-replays per request. Why?**
590
- Reclassification at high agent volume is not a per-request problem. Anthropic's classifier operates on cumulative per-OAuth-session aggregates — token throughput, conversation depth, streaming duration, inter-arrival timing, thinking-block volume. Dario's Claude backend can make each individual request indistinguishable from Claude Code and still hit this wall on a long-running agent session, because the wall isn't at the request level. Thorough diagnostic work on this was contributed by [@belangertrading](https://github.com/belangertrading) in [#23](https://github.com/askalf/dario/issues/23). The practical answer at the dario layer is **pool mode** — distribute load across multiple subscriptions so no single account accumulates enough signal to trip anything. See [Multi-account pool mode](#multi-account-pool-mode).
591
-
592
- **Can I route non-OpenAI providers through dario?**
593
- Yes — anything that speaks the OpenAI Chat Completions API. Groq, OpenRouter, LiteLLM, vLLM, Ollama's openai-compat mode. Just `dario backend add <name> --key=... --base-url=...`.
594
-
595
- **Does dario work with only the OpenAI backend, no Claude subscription?**
596
- Yes. Skip `dario login`, just run `dario backend add openai --key=...` and `dario proxy`. Claude-backend requests will return an authentication error; OpenAI-compat requests will work normally. Dario becomes a local OpenAI-compat shim with no Claude involvement.
592
+ Reclassification at high agent volume is not a per-request problem. Anthropic's classifier operates on cumulative per-OAuth-session aggregates — token throughput, conversation depth, streaming duration, inter-arrival timing, thinking-block volume. Dario's Claude backend can make each individual request indistinguishable from Claude Code and still hit this wall on a long-running agent session. Thorough diagnostic work was contributed by [@belangertrading](https://github.com/belangertrading) in [#23](https://github.com/askalf/dario/issues/23). The practical answer at the dario layer is **pool mode** — distribute load across multiple subscriptions so no single account accumulates enough signal to trip anything. See [Multi-account pool mode](#multi-account-pool-mode).
597
593
 
598
594
  **Why "dario"?**
599
595
  It's a name, not an acronym. Don't overthink it.
@@ -614,19 +610,20 @@ Longer-form writing on how dario works and why it works that way:
614
610
 
615
611
  ## Contributing
616
612
 
617
- PRs welcome. The codebase is small TypeScript — around ~6,500 lines across ~15 files:
613
+ PRs welcome. The codebase is small TypeScript — ~7,600 lines across ~15 files:
618
614
 
619
615
  | File | Purpose |
620
616
  |---|---|
621
- | `src/proxy.ts` | HTTP proxy server, request handler, rate governor, Claude backend dispatch |
622
- | `src/cc-template.ts` | CC request template engine, tool mapping, orchestration and framework scrubbing |
617
+ | `src/proxy.ts` | HTTP proxy server, request handler, rate governor, Claude backend dispatch, OpenAI-compat routing, pool failover |
618
+ | `src/cc-template.ts` | CC request template engine, universal `TOOL_MAP` (~65 schema-verified entries), orchestration and framework scrubbing, header-order replay |
623
619
  | `src/cc-template-data.json` | Bundled fallback CC request template (used when live-fingerprint extraction isn't possible) |
624
620
  | `src/cc-oauth-detect.ts` | OAuth config auto-detection from the installed CC binary |
625
- | `src/live-fingerprint.ts` | Live extraction of the CC request template (system prompt, tools, user-agent, beta flags, header order) from the installed Claude Code binary |
621
+ | `src/live-fingerprint.ts` | Live extraction of the CC request template (system prompt, tools, user-agent, beta flags, header order) from the installed Claude Code binary, drift detection, compat matrix, atomic cache writes, corruption recovery |
622
+ | `src/doctor.ts` | `dario doctor` health report aggregator — dario/Node/CC/template/drift/OAuth/pool/backends |
626
623
  | `src/oauth.ts` | Single-account token storage, PKCE flow, auto-refresh |
627
- | `src/accounts.ts` | Multi-account credential storage and independent OAuth lifecycle |
624
+ | `src/accounts.ts` | Multi-account credential storage, independent OAuth lifecycle, refresh single-flight |
628
625
  | `src/pool.ts` | Account pool, headroom-aware routing, session stickiness, failover target selection |
629
- | `src/sealed-pool.ts` | **v3.13.0.** Sealed-sender overflow protocol — RSA blind signatures for unlinkable group pooling |
626
+ | `src/sealed-pool.ts` | Sealed-sender overflow protocol — RSA blind signatures for unlinkable group pooling |
630
627
  | `src/analytics.ts` | Rolling request history, per-account / per-model stats, burn-rate, billing bucket classification |
631
628
  | `src/openai-backend.ts` | OpenAI-compat backend credential storage and request forwarder |
632
629
  | `src/shim/runtime.cjs` | Hand-written CJS payload loaded into child processes via `NODE_OPTIONS=--require`; patches `globalThis.fetch` for Anthropic messages requests only |
@@ -639,7 +636,8 @@ git clone https://github.com/askalf/dario
639
636
  cd dario
640
637
  npm install
641
638
  npm run dev # runs with tsx, no build step
642
- npm test # 376 assertions across 12 suites
639
+ npm test # ~640 assertions across 20 suites
640
+ npm run e2e # live proxy + OAuth (requires a working Claude backend)
643
641
  ```
644
642
 
645
643
  ---
@@ -652,7 +650,9 @@ npm test # 376 assertions across 12 suites
652
650
  | [@belangertrading](https://github.com/belangertrading) | Billing classification investigation ([#4](https://github.com/askalf/dario/issues/4)), cache_control fingerprinting ([#6](https://github.com/askalf/dario/issues/6)), billing reclassification root cause ([#7](https://github.com/askalf/dario/issues/7)), OAuth client_id discovery ([#12](https://github.com/askalf/dario/issues/12)), multi-agent session-level billing analysis ([#23](https://github.com/askalf/dario/issues/23)) |
653
651
  | [@nathan-widjaja](https://github.com/nathan-widjaja) | README positioning rewrite structure ([#21](https://github.com/askalf/dario/issues/21)) |
654
652
  | [@iNicholasBE](https://github.com/iNicholasBE) | macOS keychain credential detection ([#30](https://github.com/askalf/dario/pull/30)) |
655
- | [@boeingchoco](https://github.com/boeingchoco) | Reverse-direction tool parameter translation ([#29](https://github.com/askalf/dario/issues/29)), SSE event-group framing regression catch (v3.7.1), provider-comparison diagnostic that surfaced the `--preserve-tools` discoverability gap (v3.8.1), motivating case for hybrid tool mode ([#33](https://github.com/askalf/dario/issues/33), v3.9.0) |
653
+ | [@boeingchoco](https://github.com/boeingchoco) | Reverse-direction tool parameter translation ([#29](https://github.com/askalf/dario/issues/29)), SSE event-group framing regression catch (v3.7.1), provider-comparison diagnostic that surfaced the `--preserve-tools` discoverability gap (v3.8.1), motivating case for hybrid tool mode ([#33](https://github.com/askalf/dario/issues/33), v3.9.0), OpenClaw tool-mapping root cause that drove the universal `TOOL_MAP` work ([#36](https://github.com/askalf/dario/issues/36)) |
654
+ | [@tetsuco](https://github.com/tetsuco) | Framework-name path corruption in scrubber ([#35](https://github.com/askalf/dario/issues/35)), OpenClaw Bash/Glob reverse-mapping collisions ([#37](https://github.com/askalf/dario/issues/37)) |
655
+ | [@mikelovatt](https://github.com/mikelovatt) | Silent subscription-percent drain surfaced via friendly billing buckets ([#34](https://github.com/askalf/dario/issues/34)) |
656
656
 
657
657
  ---
658
658