@askalf/dario 6.20.0 → 6.20.1

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
@@ -17,7 +17,7 @@
17
17
 
18
18
  <p><strong>One local endpoint. Every AI tool you own. Your Claude seats first, your ChatGPT plan and a metered key as the rungs you chain behind them, and a ledger of who paid for what.</strong></p>
19
19
 
20
- <sub><code>npm i -g @askalf/dario</code> or <code>brew install askalf/tap/dario</code> · <strong>0</strong> runtime deps · <a href="https://www.npmjs.com/package/@askalf/dario">SLSA-attested</a> every release · nothing phones home · ~43k lines you can read in a weekend · independent, unofficial, third-party (<a href="DISCLAIMER.md">DISCLAIMER.md</a>)</sub>
20
+ <sub><code>npm i -g @askalf/dario</code> or <code>brew install askalf/tap/dario</code> · <strong>0</strong> runtime deps · <a href="https://www.npmjs.com/package/@askalf/dario">SLSA-attested</a> every release · nothing phones home · ~44k lines you can read in a weekend · independent, unofficial, third-party (<a href="DISCLAIMER.md">DISCLAIMER.md</a>)</sub>
21
21
 
22
22
  <sub><a href="#start-in-60-seconds">Start</a> · <a href="#why-people-pick-it">Why dario</a> · <a href="#what-it-does-with-a-request">Routing</a> · <a href="#will-my-account-get-suspended">Risk</a> · <a href="#reference">Reference</a> · <a href="#trust--transparency">Trust</a> · <a href="docs/returning.md">Coming back after a while?</a></sub>
23
23
 
@@ -114,11 +114,11 @@ dario runs entirely on your machine, authenticates as you with your own Claude l
114
114
 
115
115
  | Signal | Status |
116
116
  |---|---|
117
- | Source | **~43k** lines of TypeScript across **87** files, auditable in a weekend. One credential path since v5: the pool. |
117
+ | Source | **~44k** lines of TypeScript across **88** files, auditable in a weekend. One credential path since v5: the pool. |
118
118
  | Dependencies | **0 runtime.** Verify: `npm ls --production` |
119
119
  | Provenance | Every release [SLSA-attested](https://www.npmjs.com/package/@askalf/dario) via GitHub Actions + Sigstore, published with OIDC trusted publishing: no long-lived npm token exists to leak |
120
120
  | Scanning | [CodeQL](https://github.com/askalf/dario/actions/workflows/codeql.yml) on every push to master, every PR and weekly · [ClusterFuzzLite](./.github/workflows/cflite.yml) fuzzes the SSE translator, the rejection parsers and the cch stamper weekly · the [OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/askalf/dario) badge above and the [Best Practices](https://www.bestpractices.dev/projects/13638) entry are live |
121
- | Tests | **287 test files** run in parallel by `npm test` on Node 18, 20 and 22; the live e2e / compat / stealth suites have their own entry points. Green on every release |
121
+ | Tests | **288 test files** run in parallel by `npm test` on Node 18, 20 and 22; the live e2e / compat / stealth suites have their own entry points. Green on every release |
122
122
  | Credentials | Your own subscription tokens, never logged, redacted from errors, `0600` on disk in `0700` dirs |
123
123
  | Network | Binds `127.0.0.1` by default; upstream only to configured backends over HTTPS; hardcoded SSRF allow-list; refuses a non-loopback bind without `DARIO_API_KEY`; refuses cross-origin and DNS-rebound browser requests |
124
124
  | Telemetry | **None.** No analytics, no tracking, nothing phones home |
@@ -50,6 +50,9 @@ export interface CodexForwardOutcome {
50
50
  outputTokens: number;
51
51
  cacheReadTokens: number;
52
52
  cacheCreateTokens: number;
53
+ /** Reasoning tokens counted inside outputTokens (Responses
54
+ * `output_tokens_details.reasoning_tokens`); 0 when the backend sent none. */
55
+ reasoningTokens: number;
53
56
  stream: boolean;
54
57
  model: string;
55
58
  alias: string;
@@ -202,7 +205,17 @@ export interface CodexTokenUsage {
202
205
  output: number;
203
206
  cacheRead: number;
204
207
  cacheCreate: number;
208
+ /** The part of `output` that was reasoning; see reasoningTokensFromResponses. */
209
+ reasoning: number;
205
210
  }
211
+ /**
212
+ * The reasoning share of a Responses usage block: `output_tokens_details.
213
+ * reasoning_tokens`, which OpenAI counts inside `output_tokens` the way
214
+ * Anthropic counts thinking inside its output. Rounded and capped at the
215
+ * output it is part of, 0 when absent or malformed, so an analytics row never
216
+ * carries more reasoning than output.
217
+ */
218
+ export declare function reasoningTokensFromResponses(u: unknown, output: number): number;
206
219
  /**
207
220
  * Terminal Responses usage -> analytics accounting. Delegates the netting to
208
221
  * anthropicUsageFromResponses so the analytics row and the Anthropic-shape
@@ -535,6 +535,24 @@ export function chatCompletionsUsage(u) {
535
535
  }
536
536
  return out;
537
537
  }
538
+ /**
539
+ * The reasoning share of a Responses usage block: `output_tokens_details.
540
+ * reasoning_tokens`, which OpenAI counts inside `output_tokens` the way
541
+ * Anthropic counts thinking inside its output. Rounded and capped at the
542
+ * output it is part of, 0 when absent or malformed, so an analytics row never
543
+ * carries more reasoning than output.
544
+ */
545
+ export function reasoningTokensFromResponses(u, output) {
546
+ if (!u || typeof u !== 'object')
547
+ return 0;
548
+ const d = u.output_tokens_details;
549
+ if (!d || typeof d !== 'object')
550
+ return 0;
551
+ const n = d.reasoning_tokens;
552
+ if (typeof n !== 'number' || !Number.isFinite(n) || n <= 0)
553
+ return 0;
554
+ return Math.min(Math.round(n), Math.max(0, Math.floor(output)));
555
+ }
538
556
  /**
539
557
  * Terminal Responses usage -> analytics accounting. Delegates the netting to
540
558
  * anthropicUsageFromResponses so the analytics row and the Anthropic-shape
@@ -550,6 +568,7 @@ export function splitResponsesUsage(u) {
550
568
  output: a.output_tokens,
551
569
  cacheRead: a.cache_read_input_tokens ?? 0,
552
570
  cacheCreate: a.cache_creation_input_tokens ?? 0,
571
+ reasoning: reasoningTokensFromResponses(u, a.output_tokens),
553
572
  };
554
573
  }
555
574
  /**
@@ -947,7 +966,8 @@ timing) {
947
966
  reported = true;
948
967
  try {
949
968
  onDone({ status, latencyMs: Date.now() - startedAt, upstreamTtfbMs: ttfbMs(), upstreamMs: upstreamMsNow(), inputTokens: usage?.input ?? 0, outputTokens: usage?.output ?? 0,
950
- cacheReadTokens: usage?.cacheRead ?? 0, cacheCreateTokens: usage?.cacheCreate ?? 0, stream: true, model, alias: creds.alias, cache });
969
+ cacheReadTokens: usage?.cacheRead ?? 0, cacheCreateTokens: usage?.cacheCreate ?? 0, reasoningTokens: usage?.reasoning ?? 0,
970
+ stream: true, model, alias: creds.alias, cache });
951
971
  }
952
972
  catch { /* never break a served request */ }
953
973
  };
@@ -1205,6 +1225,7 @@ timing) {
1205
1225
  status, latencyMs: Date.now() - startedAt, upstreamTtfbMs: ttfbMs(), upstreamMs: upstreamMsNow(),
1206
1226
  inputTokens: usage?.input ?? 0, outputTokens: usage?.output ?? 0,
1207
1227
  cacheReadTokens: usage?.cacheRead ?? 0, cacheCreateTokens: usage?.cacheCreate ?? 0,
1228
+ reasoningTokens: usage?.reasoning ?? 0,
1208
1229
  stream, model, alias: creds.alias, cache,
1209
1230
  });
1210
1231
  }
package/dist/proxy.js CHANGED
@@ -2610,7 +2610,10 @@ export async function startProxy(opts = {}) {
2610
2610
  inputTokens: o.inputTokens, outputTokens: o.outputTokens,
2611
2611
  // Anthropic convention, like every other row: inputTokens is
2612
2612
  // net of the cached prefix, which sits in cacheReadTokens.
2613
- cacheReadTokens: o.cacheReadTokens, cacheCreateTokens: o.cacheCreateTokens, thinkingTokens: 0,
2613
+ cacheReadTokens: o.cacheReadTokens, cacheCreateTokens: o.cacheCreateTokens,
2614
+ // The backend's reasoning tokens, counted inside outputTokens exactly as
2615
+ // Claude's thinking is, so the ledger's thinking share covers both.
2616
+ thinkingTokens: o.reasoningTokens,
2614
2617
  // No Anthropic rate-limit headers on this path; the claim
2615
2618
  // names the engine and is subscription billing, so the
2616
2619
  // overage guard (#288) leaves it alone.
@@ -72,7 +72,7 @@ The rolling window forgets on every restart; the **ledger** does not. Since 6.6
72
72
  Thinking: 2.1M of 3.4M output tokens (62%) · $156 of the number above
73
73
  ```
74
74
 
75
- The `Thinking:` line is the share of output tokens that were the model's reasoning, read from the usage block upstream returns on Claude traffic, and what that reasoning would have cost at each model's output rate. It is the first number to look at when a bill surprises you: a reasoning model at its default effort can spend most of its output thinking on work that did not need it, and `output_config.effort` (or a `:low` model-name suffix, see [configuration](configuration.md#per-request-effort-by-model-name)) is a cheaper fix than a smaller model. `dario usage --by-key` shows the share per key, so the lane that is thinking the most is one line away.
75
+ The `Thinking:` line is the share of output tokens that were the model's reasoning, read from the usage block upstream returns (Claude's `output_tokens_details.thinking_tokens`, and on the ChatGPT-subscription engine `output_tokens_details.reasoning_tokens`, both counted inside output), and what that reasoning would have cost at each model's output rate. It is the first number to look at when a bill surprises you: a reasoning model at its default effort can spend most of its output thinking on work that did not need it, and `output_config.effort` (or a `:low` model-name suffix, see [configuration](configuration.md#per-request-effort-by-model-name)) is a cheaper fix than a smaller model. `dario usage --by-key` shows the share per key, so the lane that is thinking the most is one line away.
76
76
 
77
77
  Only served requests count. Traffic that was metered anyway — an API key upstream, or Anthropic's paid `extra_usage` overage — is kept in its own column and reported as spent, not saved. `dario usage --card` writes the headline as a 640×320 SVG you can drop in a README or a post, and `--donut` writes the same number as three rings — by model, by key, subscription vs metered; `--no-ledger` / `DARIO_LEDGER=0` turns the file off, `DARIO_LEDGER_PATH` moves it, and `GET /analytics/ledger` is the per-day table behind the number. Details: [api-equivalent-spend.md](api-equivalent-spend.md).
78
78
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askalf/dario",
3
- "version": "6.20.0",
3
+ "version": "6.20.1",
4
4
  "description": "Use your Claude and ChatGPT subscriptions in Cursor, Cline, Aider, Claude Code and the Agent SDK at subscription pricing, not per-token API bills. One local Anthropic + OpenAI-compatible endpoint: either plan answers either wire shape, opt-in failover when one hits its limit, and a ledger of what each plan absorbed.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -91,7 +91,7 @@
91
91
  "node": ">=18.0.0"
92
92
  },
93
93
  "devDependencies": {
94
- "@types/node": "^26.6.5",
94
+ "@types/node": "^26.6.6",
95
95
  "tsx": "^4.23.15",
96
96
  "typescript": "^5.7.0"
97
97
  }