@askalf/dario 2.11.0 → 3.0.2

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
@@ -2,7 +2,7 @@
2
2
  <h1 align="center">dario</h1>
3
3
  <p align="center"><strong>Use your Claude subscription as an API. The only proxy that bills correctly.</strong></p>
4
4
  <p align="center">
5
- No API key needed. Your Claude Max/Pro subscription becomes a local API endpoint<br/>that any tool, SDK, or framework can use with native billing classification,<br/>so your Max plan limits actually work.
5
+ No API key needed. Your Claude Max/Pro subscription becomes a local API endpoint<br/>that any tool, SDK, or framework can use. Template replay makes every request<br/>indistinguishable from real Claude Code — so your Max plan limits actually work.
6
6
  </p>
7
7
  </p>
8
8
 
@@ -33,7 +33,7 @@ export ANTHROPIC_BASE_URL=http://localhost:3456 # or OPENAI_BASE_URL=http://lo
33
33
  export ANTHROPIC_API_KEY=dario # or OPENAI_API_KEY=dario
34
34
  ```
35
35
 
36
- Opus, Sonnet, Haiku — all models, streaming, tool use. **Zero dependencies.** ~1,600 lines of TypeScript. Works with Cursor, Continue, Aider, LiteLLM, Hermes, OpenClaw, or any tool that speaks the Anthropic or OpenAI API. When rate limited, `--cli` routes through Claude Code for uninterrupted Opus access.
36
+ Opus, Sonnet, Haiku — all models, streaming, tool use. **Zero dependencies.** ~1,900 lines of TypeScript. Works with Cursor, Continue, Aider, LiteLLM, Hermes, OpenClaw, or any tool that speaks the Anthropic or OpenAI API. When rate limited, `--cli` routes through Claude Code for uninterrupted Opus access.
37
37
 
38
38
  <table>
39
39
  <tr>
@@ -80,30 +80,28 @@ Opus, Sonnet, Haiku — all models, streaming, tool use. **Zero dependencies.**
80
80
 
81
81
  Most Claude subscription proxies have a critical billing problem: **Anthropic classifies their requests as third-party and routes all usage to Extra Usage billing** — even when you have Max plan limits available. You're paying for your subscription twice.
82
82
 
83
- dario is the only proxy that solves this. It injects native Claude Code device identity, per-request billing checksums (reverse-engineered from the Claude Code binary), and priority routing into every request so Anthropic's billing system treats your requests exactly like Claude Code itself. Your Max plan limits work correctly, and Opus/Sonnet stay available even at high utilization.
83
+ dario is the only proxy that solves this. Instead of transforming your requests signal by signal, dario v3.0 uses **template replay** — it replaces the entire request with Claude Code's exact template. CC's tool definitions, CC's field structure, CC's parameters. Only your conversation content is preserved. Anthropic's classifier sees a genuine Claude Code request because it IS one.
84
84
 
85
85
  | | dario | Other proxies |
86
86
  |---|---|---|
87
- | **Billing classification** | Native Claude Code session | Third-party (Extra Usage) |
87
+ | **Approach** | Template replay sends CC's actual request | Signal matching or none |
88
+ | **Tools** | CC's exact tool definitions sent upstream | Client tools (detected) |
88
89
  | **Max plan limits** | Used correctly | Bypassed — billed separately |
89
- | **Device identity** | Injected automatically | Missing |
90
- | **Priority routing** | Full billing tag fingerprint | Missing |
91
- | **Billing tag fingerprint** | Per-request SHA-256 matching binary RE | Static or missing |
92
- | **Beta flags** | Match Claude Code v2.1.100 | Outdated or missing |
93
- | **Billable beta filtering** | Strips surprise charges | Passes everything through |
90
+ | **Detection resistance** | Undetectable without flagging CC itself | Detected by tool names, field order, effort level, etc. |
91
+ | **Dependencies** | 0 | Many |
94
92
 
95
93
  <details>
96
94
  <summary><strong>vs competitors</strong></summary>
97
95
 
98
96
  | Feature | dario | Meridian (710 stars) | CLIProxyAPI (24K stars) |
99
97
  |---------|-------|---------|------------|
100
- | Native billing classification | **Yes** | No | Inherited (CLI-only) |
98
+ | Template replay (undetectable) | **Yes** | No | Inherited (CLI-only) |
101
99
  | Direct OAuth (streaming, tools) | **Yes** | Yes (SDK-based) | No |
102
100
  | CLI fallback (rate limit bypass) | **Yes** | No | Yes (only mode) |
103
101
  | OpenAI API compat | **Yes** | Yes | Yes |
104
102
  | Orchestration sanitization | **Yes** | Yes | No |
105
103
  | Token anomaly detection | **Yes** | Yes | No |
106
- | Codebase size | ~1,600 lines | ~9,000 lines | Platform |
104
+ | Codebase size | ~1,900 lines | ~9,000 lines | Platform |
107
105
  | Dependencies | 0 | Many | Many |
108
106
  | Setup | 2 commands | Config + build | Config + dashboard |
109
107
 
@@ -386,18 +384,21 @@ Add to your `openclaw.json` models config:
386
384
 
387
385
  ## How It Works
388
386
 
389
- ### Direct API Mode (default)
387
+ ### Direct API Mode (default) — Template Replay
390
388
 
391
389
  ```
392
- ┌──────────┐ ┌─────────────────┐ ┌──────────────────┐
393
- │ Your App │ ──> │ dario (proxy) │ ──> │ api.anthropic.com│
394
- │ │ │ localhost:3456 │ │ │
395
- │ sends │ │ swaps API key │ │ sees valid
396
- API key │ │ for OAuth │ │ OAuth bearer
397
- "dario" │ │ bearer token │ │ token
398
- └──────────┘ └─────────────────┘ └──────────────────┘
390
+ ┌──────────┐ ┌─────────────────────┐ ┌──────────────────┐
391
+ │ Your App │ ──> │ dario (proxy) │ ──> │ api.anthropic.com│
392
+ │ │ │ localhost:3456 │ │ │
393
+ │ sends │ │ │ │ sees a genuine
394
+ its own │ │ replaces request │ │ Claude Code
395
+ tools & │ │ with CC template │ │ request
396
+ │ params │ │ keeps only content │ │ │
397
+ └──────────┘ └─────────────────────┘ └──────────────────┘
399
398
  ```
400
399
 
400
+ Your app sends whatever it wants — any tools, any parameters. dario replaces the entire request with Claude Code's template and injects only your conversation content. The upstream sees CC's exact tool definitions, field structure, and parameters.
401
+
401
402
  ### CLI Backend Mode (`--cli`)
402
403
 
403
404
  ```
@@ -454,10 +455,7 @@ Add to your `openclaw.json` models config:
454
455
 
455
456
  ### Direct API Mode
456
457
  - All Claude models (Opus 4.6, Sonnet 4.6, Haiku 4.5) + 1M extended context aliases (`opus1m`, `sonnet1m`)
457
- - **Native billing classification** — device identity, per-request billing tag with SHA-256 checksums matching real Claude Code (extracted via binary RE), ensures Max plan limits work correctly
458
- - **Stealth layer** (v2.9.0) — strips thinking blocks from conversation history (saves 50-80% input tokens), scrubs non-CC fields (`temperature`, `top_p`, `top_k`, `stop_sequences`, `service_tier`), reorders JSON fields to match Claude Code's exact field order, and normalizes system prompts to exactly 3 blocks. Every request is indistinguishable from real Claude Code traffic.
459
- - **Adaptive thinking** — matches Claude Code's `{ type: 'adaptive' }` mode for optimal reasoning (auto-skipped for Haiku 4.5)
460
- - **Effort control** — injects `output_config: { effort: 'medium' }` matching Claude Code's default, or passes through client-specified effort level
458
+ - **Template replay** (v3.0) replaces the entire request with Claude Code's exact template. CC's tool definitions, field structure, and parameters are sent upstream. Only your conversation content is preserved. Your client's tools are mapped to CC equivalents and reverse-mapped in responses. Tested with 40 third-party tools all route to `five_hour`. See [Discussion 13](https://github.com/askalf/dario/discussions/13) and [Discussion 14](https://github.com/askalf/dario/discussions/14).
461
459
  - **Enriched 429 errors** — rate limit errors include utilization %, limiting window, and reset time instead of Anthropic's default `"Error"` message
462
460
  - **Auto CLI fallback** — if the API returns 429 and Claude Code is installed, transparently retries through `claude --print` with SSE conversion
463
461
  - **OpenAI-compatible** (`/v1/chat/completions`) — works with any OpenAI SDK or tool
@@ -582,7 +580,7 @@ Dario handles your OAuth tokens. Here's why you can trust it:
582
580
 
583
581
  | Signal | Status |
584
582
  |--------|--------|
585
- | **Source code** | ~1,600 lines of TypeScript — small enough to audit in one sitting |
583
+ | **Source code** | ~1,900 lines of TypeScript — small enough to audit in one sitting |
586
584
  | **Dependencies** | 0 runtime dependencies. Verify: `npm ls --production` |
587
585
  | **npm provenance** | Every release is [SLSA attested](https://www.npmjs.com/package/@askalf/dario) via GitHub Actions |
588
586
  | **Security scanning** | [CodeQL](https://github.com/askalf/dario/actions/workflows/codeql.yml) runs on every push and weekly |
@@ -606,18 +604,21 @@ cd $(npm root -g)/@askalf/dario && npm ls --production
606
604
 
607
605
  | Topic | Link |
608
606
  |-------|------|
609
- | Billing tag algorithm, fingerprint analysis, Hermes/OpenClaw compatibility | [Discussion #8](https://github.com/askalf/dario/discussions/8) |
610
- | Why Opus 4.6 feels worse and how to fix it (thinking block accumulation, effort defaults) | [Discussion #9](https://github.com/askalf/dario/discussions/9) |
611
- | Rate limit header analysis and subscription throttling mechanics | [Discussion #1](https://github.com/askalf/dario/discussions/1) |
607
+ | v3.0 Template Replay why we stopped matching signals | [Discussion 14](https://github.com/askalf/dario/discussions/14) |
608
+ | Claude Code defaults are detection signals, not optimizations | [Discussion 13](https://github.com/askalf/dario/discussions/13) |
609
+ | Why Opus 4.6 feels worse and how to fix it | [Discussion 9](https://github.com/askalf/dario/discussions/9) |
610
+ | Billing tag algorithm and fingerprint analysis | [Discussion 8](https://github.com/askalf/dario/discussions/8) |
611
+ | Rate limit header analysis | [Discussion 1](https://github.com/askalf/dario/discussions/1) |
612
612
 
613
613
  ## Contributing
614
614
 
615
- PRs welcome. The codebase is ~1,600 lines of TypeScript across 4 files:
615
+ PRs welcome. The codebase is ~1,900 lines of TypeScript across 5 files:
616
616
 
617
617
  | File | Purpose |
618
618
  |------|---------|
619
- | `src/oauth.ts` | Token storage, refresh logic, Claude Code credential detection, auto OAuth flow |
620
619
  | `src/proxy.ts` | HTTP proxy server + CLI backend |
620
+ | `src/cc-template.ts` | Claude Code request template + tool mapping |
621
+ | `src/oauth.ts` | Token storage, refresh, credential detection |
621
622
  | `src/cli.ts` | CLI entry point |
622
623
  | `src/index.ts` | Library exports |
623
624
 
@@ -0,0 +1,434 @@
1
+ /**
2
+ * Claude Code request template — the exact tool definitions, system structure,
3
+ * and request shape that real Claude Code sends.
4
+ *
5
+ * Instead of transforming third-party requests signal-by-signal, we replace
6
+ * the entire request with a CC template and inject only the conversation content.
7
+ * The upstream sees a genuine CC request. Anthropic can't detect it without
8
+ * flagging their own binary.
9
+ *
10
+ * Source: MITM capture + binary RE of Claude Code v2.1.100
11
+ */
12
+ /** Claude Code's exact tool definitions (from binary RE + MITM capture). */
13
+ export declare const CC_TOOL_DEFINITIONS: ({
14
+ name: string;
15
+ description: string;
16
+ input_schema: {
17
+ type: "object";
18
+ properties: {
19
+ command: {
20
+ type: "string";
21
+ description: string;
22
+ };
23
+ timeout: {
24
+ type: "number";
25
+ description: string;
26
+ };
27
+ file_path?: undefined;
28
+ offset?: undefined;
29
+ limit?: undefined;
30
+ content?: undefined;
31
+ old_string?: undefined;
32
+ new_string?: undefined;
33
+ replace_all?: undefined;
34
+ pattern?: undefined;
35
+ path?: undefined;
36
+ output_mode?: undefined;
37
+ url?: undefined;
38
+ query?: undefined;
39
+ notebook_path?: undefined;
40
+ cell_number?: undefined;
41
+ new_source?: undefined;
42
+ prompt?: undefined;
43
+ description?: undefined;
44
+ question?: undefined;
45
+ };
46
+ required: string[];
47
+ };
48
+ } | {
49
+ name: string;
50
+ description: string;
51
+ input_schema: {
52
+ type: "object";
53
+ properties: {
54
+ file_path: {
55
+ type: "string";
56
+ description: string;
57
+ };
58
+ offset: {
59
+ type: "integer";
60
+ description: string;
61
+ };
62
+ limit: {
63
+ type: "integer";
64
+ description: string;
65
+ };
66
+ command?: undefined;
67
+ timeout?: undefined;
68
+ content?: undefined;
69
+ old_string?: undefined;
70
+ new_string?: undefined;
71
+ replace_all?: undefined;
72
+ pattern?: undefined;
73
+ path?: undefined;
74
+ output_mode?: undefined;
75
+ url?: undefined;
76
+ query?: undefined;
77
+ notebook_path?: undefined;
78
+ cell_number?: undefined;
79
+ new_source?: undefined;
80
+ prompt?: undefined;
81
+ description?: undefined;
82
+ question?: undefined;
83
+ };
84
+ required: string[];
85
+ };
86
+ } | {
87
+ name: string;
88
+ description: string;
89
+ input_schema: {
90
+ type: "object";
91
+ properties: {
92
+ file_path: {
93
+ type: "string";
94
+ description: string;
95
+ };
96
+ content: {
97
+ type: "string";
98
+ description: string;
99
+ };
100
+ command?: undefined;
101
+ timeout?: undefined;
102
+ offset?: undefined;
103
+ limit?: undefined;
104
+ old_string?: undefined;
105
+ new_string?: undefined;
106
+ replace_all?: undefined;
107
+ pattern?: undefined;
108
+ path?: undefined;
109
+ output_mode?: undefined;
110
+ url?: undefined;
111
+ query?: undefined;
112
+ notebook_path?: undefined;
113
+ cell_number?: undefined;
114
+ new_source?: undefined;
115
+ prompt?: undefined;
116
+ description?: undefined;
117
+ question?: undefined;
118
+ };
119
+ required: string[];
120
+ };
121
+ } | {
122
+ name: string;
123
+ description: string;
124
+ input_schema: {
125
+ type: "object";
126
+ properties: {
127
+ file_path: {
128
+ type: "string";
129
+ description: string;
130
+ };
131
+ old_string: {
132
+ type: "string";
133
+ description: string;
134
+ };
135
+ new_string: {
136
+ type: "string";
137
+ description: string;
138
+ };
139
+ replace_all: {
140
+ type: "boolean";
141
+ description: string;
142
+ default: boolean;
143
+ };
144
+ command?: undefined;
145
+ timeout?: undefined;
146
+ offset?: undefined;
147
+ limit?: undefined;
148
+ content?: undefined;
149
+ pattern?: undefined;
150
+ path?: undefined;
151
+ output_mode?: undefined;
152
+ url?: undefined;
153
+ query?: undefined;
154
+ notebook_path?: undefined;
155
+ cell_number?: undefined;
156
+ new_source?: undefined;
157
+ prompt?: undefined;
158
+ description?: undefined;
159
+ question?: undefined;
160
+ };
161
+ required: string[];
162
+ };
163
+ } | {
164
+ name: string;
165
+ description: string;
166
+ input_schema: {
167
+ type: "object";
168
+ properties: {
169
+ pattern: {
170
+ type: "string";
171
+ description: string;
172
+ };
173
+ path: {
174
+ type: "string";
175
+ description: string;
176
+ };
177
+ command?: undefined;
178
+ timeout?: undefined;
179
+ file_path?: undefined;
180
+ offset?: undefined;
181
+ limit?: undefined;
182
+ content?: undefined;
183
+ old_string?: undefined;
184
+ new_string?: undefined;
185
+ replace_all?: undefined;
186
+ output_mode?: undefined;
187
+ url?: undefined;
188
+ query?: undefined;
189
+ notebook_path?: undefined;
190
+ cell_number?: undefined;
191
+ new_source?: undefined;
192
+ prompt?: undefined;
193
+ description?: undefined;
194
+ question?: undefined;
195
+ };
196
+ required: string[];
197
+ };
198
+ } | {
199
+ name: string;
200
+ description: string;
201
+ input_schema: {
202
+ type: "object";
203
+ properties: {
204
+ pattern: {
205
+ type: "string";
206
+ description: string;
207
+ };
208
+ path: {
209
+ type: "string";
210
+ description: string;
211
+ };
212
+ output_mode: {
213
+ type: "string";
214
+ enum: string[];
215
+ description: string;
216
+ };
217
+ command?: undefined;
218
+ timeout?: undefined;
219
+ file_path?: undefined;
220
+ offset?: undefined;
221
+ limit?: undefined;
222
+ content?: undefined;
223
+ old_string?: undefined;
224
+ new_string?: undefined;
225
+ replace_all?: undefined;
226
+ url?: undefined;
227
+ query?: undefined;
228
+ notebook_path?: undefined;
229
+ cell_number?: undefined;
230
+ new_source?: undefined;
231
+ prompt?: undefined;
232
+ description?: undefined;
233
+ question?: undefined;
234
+ };
235
+ required: string[];
236
+ };
237
+ } | {
238
+ name: string;
239
+ description: string;
240
+ input_schema: {
241
+ type: "object";
242
+ properties: {
243
+ url: {
244
+ type: "string";
245
+ description: string;
246
+ };
247
+ command?: undefined;
248
+ timeout?: undefined;
249
+ file_path?: undefined;
250
+ offset?: undefined;
251
+ limit?: undefined;
252
+ content?: undefined;
253
+ old_string?: undefined;
254
+ new_string?: undefined;
255
+ replace_all?: undefined;
256
+ pattern?: undefined;
257
+ path?: undefined;
258
+ output_mode?: undefined;
259
+ query?: undefined;
260
+ notebook_path?: undefined;
261
+ cell_number?: undefined;
262
+ new_source?: undefined;
263
+ prompt?: undefined;
264
+ description?: undefined;
265
+ question?: undefined;
266
+ };
267
+ required: string[];
268
+ };
269
+ } | {
270
+ name: string;
271
+ description: string;
272
+ input_schema: {
273
+ type: "object";
274
+ properties: {
275
+ query: {
276
+ type: "string";
277
+ description: string;
278
+ };
279
+ command?: undefined;
280
+ timeout?: undefined;
281
+ file_path?: undefined;
282
+ offset?: undefined;
283
+ limit?: undefined;
284
+ content?: undefined;
285
+ old_string?: undefined;
286
+ new_string?: undefined;
287
+ replace_all?: undefined;
288
+ pattern?: undefined;
289
+ path?: undefined;
290
+ output_mode?: undefined;
291
+ url?: undefined;
292
+ notebook_path?: undefined;
293
+ cell_number?: undefined;
294
+ new_source?: undefined;
295
+ prompt?: undefined;
296
+ description?: undefined;
297
+ question?: undefined;
298
+ };
299
+ required: string[];
300
+ };
301
+ } | {
302
+ name: string;
303
+ description: string;
304
+ input_schema: {
305
+ type: "object";
306
+ properties: {
307
+ notebook_path: {
308
+ type: "string";
309
+ description: string;
310
+ };
311
+ cell_number: {
312
+ type: "integer";
313
+ description: string;
314
+ };
315
+ new_source: {
316
+ type: "string";
317
+ description: string;
318
+ };
319
+ command?: undefined;
320
+ timeout?: undefined;
321
+ file_path?: undefined;
322
+ offset?: undefined;
323
+ limit?: undefined;
324
+ content?: undefined;
325
+ old_string?: undefined;
326
+ new_string?: undefined;
327
+ replace_all?: undefined;
328
+ pattern?: undefined;
329
+ path?: undefined;
330
+ output_mode?: undefined;
331
+ url?: undefined;
332
+ query?: undefined;
333
+ prompt?: undefined;
334
+ description?: undefined;
335
+ question?: undefined;
336
+ };
337
+ required: string[];
338
+ };
339
+ } | {
340
+ name: string;
341
+ description: string;
342
+ input_schema: {
343
+ type: "object";
344
+ properties: {
345
+ prompt: {
346
+ type: "string";
347
+ description: string;
348
+ };
349
+ description: {
350
+ type: "string";
351
+ description: string;
352
+ };
353
+ command?: undefined;
354
+ timeout?: undefined;
355
+ file_path?: undefined;
356
+ offset?: undefined;
357
+ limit?: undefined;
358
+ content?: undefined;
359
+ old_string?: undefined;
360
+ new_string?: undefined;
361
+ replace_all?: undefined;
362
+ pattern?: undefined;
363
+ path?: undefined;
364
+ output_mode?: undefined;
365
+ url?: undefined;
366
+ query?: undefined;
367
+ notebook_path?: undefined;
368
+ cell_number?: undefined;
369
+ new_source?: undefined;
370
+ question?: undefined;
371
+ };
372
+ required: string[];
373
+ };
374
+ } | {
375
+ name: string;
376
+ description: string;
377
+ input_schema: {
378
+ type: "object";
379
+ properties: {
380
+ question: {
381
+ type: "string";
382
+ description: string;
383
+ };
384
+ command?: undefined;
385
+ timeout?: undefined;
386
+ file_path?: undefined;
387
+ offset?: undefined;
388
+ limit?: undefined;
389
+ content?: undefined;
390
+ old_string?: undefined;
391
+ new_string?: undefined;
392
+ replace_all?: undefined;
393
+ pattern?: undefined;
394
+ path?: undefined;
395
+ output_mode?: undefined;
396
+ url?: undefined;
397
+ query?: undefined;
398
+ notebook_path?: undefined;
399
+ cell_number?: undefined;
400
+ new_source?: undefined;
401
+ prompt?: undefined;
402
+ description?: undefined;
403
+ };
404
+ required: string[];
405
+ };
406
+ })[];
407
+ /** Client tool name → CC tool mapping with parameter translation. */
408
+ interface ToolMapping {
409
+ ccTool: string;
410
+ translateArgs?: (args: Record<string, unknown>) => Record<string, unknown>;
411
+ translateBack?: (args: Record<string, unknown>) => Record<string, unknown>;
412
+ }
413
+ /**
414
+ * Build a CC-template request from a client request.
415
+ * Replaces the entire request structure — tools, fields, ordering — with
416
+ * what real CC sends. Only the conversation content is preserved.
417
+ */
418
+ export declare function buildCCRequest(clientBody: Record<string, unknown>, billingTag: string, agentIdentity: string, cache1h: {
419
+ type: 'ephemeral';
420
+ ttl: '1h';
421
+ }, identity: {
422
+ deviceId: string;
423
+ accountUuid: string;
424
+ sessionId: string;
425
+ }): {
426
+ body: Record<string, unknown>;
427
+ toolMap: Map<string, ToolMapping>;
428
+ unmappedTools: string[];
429
+ };
430
+ /**
431
+ * Reverse-map CC tool calls in a response back to client tool names.
432
+ */
433
+ export declare function reverseMapResponse(responseBody: string, toolMap: Map<string, ToolMapping>): string;
434
+ export {};