@askalf/dario 4.8.75 → 4.8.76
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 +18 -17
- package/dist/analytics.d.ts +28 -0
- package/dist/analytics.js +37 -0
- package/dist/overage-guard.d.ts +20 -11
- package/dist/overage-guard.js +26 -16
- package/dist/proxy.d.ts +7 -5
- package/dist/proxy.js +23 -4
- package/dist/tui/tabs/hits.js +10 -7
- package/dist/tui/tabs/status.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -200,9 +200,9 @@ The tool doesn't know. The backend doesn't know. dario is the seam.
|
|
|
200
200
|
|
|
201
201
|
## Overage guard
|
|
202
202
|
|
|
203
|
-
A subscriber should never see a single
|
|
203
|
+
A subscriber should never see a single response billed outside their subscription pool during normal operation. One means something is wrong — wire-shape drift, a classifier change, an account misconfig — and continuing to forward requests in the same shape bleeds real money (accounts with extra-usage enabled) or returns a wall of rejections (accounts without it). The first hit is the signal; the second through hundredth are damage.
|
|
204
204
|
|
|
205
|
-
So the moment any upstream response
|
|
205
|
+
So the moment any upstream response bills to something other than your subscription pool — `representative-claim: overage`, `api`, or a new credit/SDK bucket like the one the 2026-06-15 Agent-SDK split introduces — dario **halts the proxy**. The check is an allow-list, not a match on `overage`: anything that isn't a known subscription claim (`five_hour`/`seven_day` and their fallbacks) and isn't the `unknown` no-header sentinel trips it, so a credit-bucket claim dario has never seen still halts. Every subsequent request returns `503` with an Anthropic-shaped error body the client surfaces verbatim, until you run `dario resume`, press `R` on the TUI, or the cooldown clears (default 30 min). The halt is visible across the TUI's Status, Hits, and Analytics tabs, fires a best-effort native OS notification, and emits named SSE events. (In `--upstream-api-key` passthrough mode the guard is off — `api` billing is the point there, not a failure.)
|
|
206
206
|
|
|
207
207
|
```
|
|
208
208
|
┌─ dario ─────────────────────────────[ q quit · Tab next · ? help ]──┐
|
|
@@ -368,26 +368,27 @@ Follow [@ask_alf](https://x.com/ask_alf) for drift bulletins as they happen.
|
|
|
368
368
|
|
|
369
369
|
MIT — see [LICENSE](LICENSE) and [DISCLAIMER.md](DISCLAIMER.md).
|
|
370
370
|
|
|
371
|
-
##
|
|
371
|
+
## Own Your Stack
|
|
372
372
|
|
|
373
|
-
|
|
374
|
-
|---|---|
|
|
375
|
-
| [askalf platform](https://askalf.org) | Self-hosted AI workforce — agents that run real business + life work. Uses dario as its LLM substrate. *Early access at [askalf.org](https://askalf.org).* |
|
|
376
|
-
| [hands](https://github.com/askalf/hands) | Cross-platform computer-use agent — your LLM on your mouse, keyboard, and screen. Routes through dario or any Anthropic-compat. |
|
|
377
|
-
| [deepdive](https://github.com/askalf/deepdive) | Local research agent. One command, cited answer. Plan → search → headless fetch → extract → synthesize. |
|
|
378
|
-
| [agent](https://github.com/askalf/agent) | Connect any device to an askalf fleet — runs the shell or Claude Code tasks the fleet dispatches. |
|
|
379
|
-
| [browser-bridge](https://github.com/askalf/browser-bridge) | Stealth headless Chromium in a container, CDP on 9222. Playwright / Puppeteer / MCP-compatible. |
|
|
380
|
-
| [claude-sync](https://github.com/askalf/claude-sync) | Sync Claude Code sessions across machines via a portable `.ccsync` file. |
|
|
381
|
-
| [pgflex](https://github.com/askalf/pgflex) | One Postgres API, two modes — real PostgreSQL for production, PGlite (WASM) for dev. |
|
|
382
|
-
| [redisflex](https://github.com/askalf/redisflex) | One Redis API, two modes — ioredis for production, in-process for dev. Includes a BullMQ-shaped in-memory queue. |
|
|
373
|
+
dario is the routing layer of **[Own Your Stack](https://github.com/askalf)** — open tools for owning your AI infrastructure instead of renting it by the token. One subscription. Your box. Your terms.
|
|
383
374
|
|
|
375
|
+
| Tool | |
|
|
376
|
+
|---|---|
|
|
377
|
+
| **[dario](https://github.com/askalf/dario)** | own your routing — your subscription, in any tool *(you are here)* |
|
|
378
|
+
| **[deepdive](https://github.com/askalf/deepdive)** | own your research — local agent, cited answers |
|
|
379
|
+
| **[hands](https://github.com/askalf/hands)** | own your computer-use — your LLM on your own mouse & keyboard |
|
|
380
|
+
| **[agent](https://github.com/askalf/agent)** | own your fleet — connect any device, dispatch real work |
|
|
381
|
+
| **[browser-bridge](https://github.com/askalf/browser-bridge)** | own your browser — stealth headless Chromium, your CDP endpoint |
|
|
382
|
+
| **[claude-sync](https://github.com/askalf/claude-sync)** | own your sessions — sync Claude Code across machines |
|
|
383
|
+
| **[askalf platform](https://askalf.org)** | own your operation — the self-hosted AI workforce, running on this stack |
|
|
384
384
|
|
|
385
385
|
---
|
|
386
386
|
|
|
387
|
-
## Built by Sprayberry
|
|
387
|
+
## Built by Thomas Sprayberry
|
|
388
388
|
|
|
389
|
-
|
|
389
|
+
dario is part of **Own Your Stack** — the open toolkit behind **[Sprayberry Labs](https://sprayberrylabs.com)**, an independent studio (Atlanta, GA) that ships bespoke software with the autonomous AI workforce these tools are part of.
|
|
390
390
|
|
|
391
|
-
|
|
391
|
+
Built in the open, scars included. Follow the build → **[@ask_alf](https://x.com/ask_alf)** · **[sprayberrylabs.com/own-your-stack](https://sprayberrylabs.com/own-your-stack)**
|
|
392
392
|
|
|
393
|
-
|
|
393
|
+
---
|
|
394
|
+
Part of **[Own Your Stack](https://github.com/askalf)** — own your AI infrastructure instead of renting it. Built by Thomas Sprayberry.
|
package/dist/analytics.d.ts
CHANGED
|
@@ -55,6 +55,34 @@ export type BillingBucket = 'subscription' | 'subscription_fallback' | 'extra_us
|
|
|
55
55
|
* billing bucket. Pure function; no state; safe to call from any context.
|
|
56
56
|
*/
|
|
57
57
|
export declare function billingBucketFromClaim(claim: string | null | undefined): BillingBucket;
|
|
58
|
+
/**
|
|
59
|
+
* The `representative-claim` values that mean "billed against the subscription
|
|
60
|
+
* pool" — the place dario exists to keep traffic. `five_hour`/`seven_day` and
|
|
61
|
+
* their server-side `_fallback` variants are all subscription billing (see
|
|
62
|
+
* `billingBucketFromClaim` + discussion #1). Anything else is either a
|
|
63
|
+
* non-subscription billing classification or the `unknown` sentinel below.
|
|
64
|
+
*/
|
|
65
|
+
export declare const SUBSCRIPTION_CLAIMS: ReadonlySet<string>;
|
|
66
|
+
/**
|
|
67
|
+
* The sentinel `claim` dario assigns when a response carried no rate-limit
|
|
68
|
+
* header at all (non-200s, stream aborts, early rejects — see `pool.ts`
|
|
69
|
+
* `parseRateLimits` / `EMPTY_SNAPSHOT`). It is NOT a billing classification,
|
|
70
|
+
* so the overage-guard must never halt on it.
|
|
71
|
+
*/
|
|
72
|
+
export declare const NO_BILLING_CLAIM = "unknown";
|
|
73
|
+
/**
|
|
74
|
+
* True when a claim represents real *non-subscription* billing — the
|
|
75
|
+
* condition the overage-guard halts on (see `overage-guard.ts`, #288).
|
|
76
|
+
*
|
|
77
|
+
* Deliberately an allow-list, not `claim === 'overage'`: it halts on anything
|
|
78
|
+
* that is NOT a known subscription claim AND NOT the `unknown` sentinel. That
|
|
79
|
+
* catches `overage` and `api` as before, but ALSO any new credit/SDK bucket
|
|
80
|
+
* string Anthropic introduces — e.g. the 2026-06-15 Agent-SDK/headless split,
|
|
81
|
+
* whose credit-bucket claim dario has never observed (it keeps traffic in the
|
|
82
|
+
* pool) and so cannot hardcode. `unknown` is exempt: halting on it would halt
|
|
83
|
+
* the proxy on every transient non-200/stream-abort.
|
|
84
|
+
*/
|
|
85
|
+
export declare function isNonSubscriptionBilling(claim: string | null | undefined): boolean;
|
|
58
86
|
export declare class Analytics extends EventEmitter {
|
|
59
87
|
private records;
|
|
60
88
|
private maxRecords;
|
package/dist/analytics.js
CHANGED
|
@@ -37,6 +37,43 @@ export function billingBucketFromClaim(claim) {
|
|
|
37
37
|
return 'unknown';
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* The `representative-claim` values that mean "billed against the subscription
|
|
42
|
+
* pool" — the place dario exists to keep traffic. `five_hour`/`seven_day` and
|
|
43
|
+
* their server-side `_fallback` variants are all subscription billing (see
|
|
44
|
+
* `billingBucketFromClaim` + discussion #1). Anything else is either a
|
|
45
|
+
* non-subscription billing classification or the `unknown` sentinel below.
|
|
46
|
+
*/
|
|
47
|
+
export const SUBSCRIPTION_CLAIMS = new Set([
|
|
48
|
+
'five_hour',
|
|
49
|
+
'seven_day',
|
|
50
|
+
'five_hour_fallback',
|
|
51
|
+
'seven_day_fallback',
|
|
52
|
+
]);
|
|
53
|
+
/**
|
|
54
|
+
* The sentinel `claim` dario assigns when a response carried no rate-limit
|
|
55
|
+
* header at all (non-200s, stream aborts, early rejects — see `pool.ts`
|
|
56
|
+
* `parseRateLimits` / `EMPTY_SNAPSHOT`). It is NOT a billing classification,
|
|
57
|
+
* so the overage-guard must never halt on it.
|
|
58
|
+
*/
|
|
59
|
+
export const NO_BILLING_CLAIM = 'unknown';
|
|
60
|
+
/**
|
|
61
|
+
* True when a claim represents real *non-subscription* billing — the
|
|
62
|
+
* condition the overage-guard halts on (see `overage-guard.ts`, #288).
|
|
63
|
+
*
|
|
64
|
+
* Deliberately an allow-list, not `claim === 'overage'`: it halts on anything
|
|
65
|
+
* that is NOT a known subscription claim AND NOT the `unknown` sentinel. That
|
|
66
|
+
* catches `overage` and `api` as before, but ALSO any new credit/SDK bucket
|
|
67
|
+
* string Anthropic introduces — e.g. the 2026-06-15 Agent-SDK/headless split,
|
|
68
|
+
* whose credit-bucket claim dario has never observed (it keeps traffic in the
|
|
69
|
+
* pool) and so cannot hardcode. `unknown` is exempt: halting on it would halt
|
|
70
|
+
* the proxy on every transient non-200/stream-abort.
|
|
71
|
+
*/
|
|
72
|
+
export function isNonSubscriptionBilling(claim) {
|
|
73
|
+
if (!claim || claim === NO_BILLING_CLAIM)
|
|
74
|
+
return false;
|
|
75
|
+
return !SUBSCRIPTION_CLAIMS.has(claim);
|
|
76
|
+
}
|
|
40
77
|
// Anthropic pricing (per 1M tokens, USD). Not authoritative — used for
|
|
41
78
|
// rough burn-rate display in the /analytics summary.
|
|
42
79
|
const PRICING = {
|
package/dist/overage-guard.d.ts
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Overage-guard — halt the proxy
|
|
3
|
-
*
|
|
2
|
+
* Overage-guard — halt the proxy the first time a response bills to anything
|
|
3
|
+
* other than the subscription pool, to prevent silent API-rate bleed.
|
|
4
4
|
*
|
|
5
|
-
* Subscribers should never see a single
|
|
5
|
+
* Subscribers should never see a single non-subscription hit during normal
|
|
6
6
|
* operation. One means something is wrong (wire-shape drift, classifier
|
|
7
7
|
* change, account misconfig, billing-flip after a CC release) and
|
|
8
8
|
* continuing to forward requests bleeds against per-token billing.
|
|
9
9
|
*
|
|
10
10
|
* The guard subscribes to the Analytics record stream — every completed
|
|
11
11
|
* request emits a record carrying its `claim` (raw representative-claim
|
|
12
|
-
* value). When
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* value). When a non-subscription claim lands — `overage`, `api`, or any
|
|
13
|
+
* new credit/SDK-bucket string (the 2026-06-15 Agent-SDK split) — the guard
|
|
14
|
+
* transitions to a halted state and emits a `'halt'` event. The detection is
|
|
15
|
+
* an allow-list (`isNonSubscriptionBilling`): it fires on anything that is
|
|
16
|
+
* not a known subscription claim and not the `unknown` sentinel (which means
|
|
17
|
+
* "no rate-limit header" — a transient non-200/abort, not a billing flip).
|
|
18
|
+
* The HTTP request path checks `isHalted()` on every incoming request and
|
|
19
|
+
* returns 503 with an Anthropic-shaped error body when halted.
|
|
20
|
+
*
|
|
21
|
+
* The name predates the broadening (it caught only `overage` through v4.8.x,
|
|
22
|
+
* #288); the issue lineage + `dario_overage_guard` error type are kept stable.
|
|
16
23
|
*
|
|
17
24
|
* Resume paths:
|
|
18
25
|
* - explicit: `dario resume` CLI → POST /admin/resume → `clear('manual')`
|
|
@@ -26,7 +33,7 @@
|
|
|
26
33
|
* See dario#288.
|
|
27
34
|
*/
|
|
28
35
|
import { EventEmitter } from 'node:events';
|
|
29
|
-
import type
|
|
36
|
+
import { type Analytics, type RequestRecord } from './analytics.js';
|
|
30
37
|
export interface HaltState {
|
|
31
38
|
since: number;
|
|
32
39
|
cooldownUntil: number;
|
|
@@ -58,9 +65,11 @@ export declare class OverageGuard extends EventEmitter {
|
|
|
58
65
|
private analyticsListener;
|
|
59
66
|
constructor(opts: OverageGuardOptions);
|
|
60
67
|
/**
|
|
61
|
-
* Subscribe to an Analytics instance. Every record
|
|
62
|
-
*
|
|
63
|
-
*
|
|
68
|
+
* Subscribe to an Analytics instance. Every record whose claim is a
|
|
69
|
+
* non-subscription billing classification (`isNonSubscriptionBilling` —
|
|
70
|
+
* `overage`, `api`, or a new credit/SDK bucket) triggers halt (when
|
|
71
|
+
* behavior === 'halt') or a warn-only event (when behavior === 'warn').
|
|
72
|
+
* Subscription claims and the `unknown` sentinel are ignored.
|
|
64
73
|
*
|
|
65
74
|
* Idempotent — calling attach() a second time replaces the listener
|
|
66
75
|
* rather than stacking; useful for tests.
|
package/dist/overage-guard.js
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Overage-guard — halt the proxy
|
|
3
|
-
*
|
|
2
|
+
* Overage-guard — halt the proxy the first time a response bills to anything
|
|
3
|
+
* other than the subscription pool, to prevent silent API-rate bleed.
|
|
4
4
|
*
|
|
5
|
-
* Subscribers should never see a single
|
|
5
|
+
* Subscribers should never see a single non-subscription hit during normal
|
|
6
6
|
* operation. One means something is wrong (wire-shape drift, classifier
|
|
7
7
|
* change, account misconfig, billing-flip after a CC release) and
|
|
8
8
|
* continuing to forward requests bleeds against per-token billing.
|
|
9
9
|
*
|
|
10
10
|
* The guard subscribes to the Analytics record stream — every completed
|
|
11
11
|
* request emits a record carrying its `claim` (raw representative-claim
|
|
12
|
-
* value). When
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* value). When a non-subscription claim lands — `overage`, `api`, or any
|
|
13
|
+
* new credit/SDK-bucket string (the 2026-06-15 Agent-SDK split) — the guard
|
|
14
|
+
* transitions to a halted state and emits a `'halt'` event. The detection is
|
|
15
|
+
* an allow-list (`isNonSubscriptionBilling`): it fires on anything that is
|
|
16
|
+
* not a known subscription claim and not the `unknown` sentinel (which means
|
|
17
|
+
* "no rate-limit header" — a transient non-200/abort, not a billing flip).
|
|
18
|
+
* The HTTP request path checks `isHalted()` on every incoming request and
|
|
19
|
+
* returns 503 with an Anthropic-shaped error body when halted.
|
|
20
|
+
*
|
|
21
|
+
* The name predates the broadening (it caught only `overage` through v4.8.x,
|
|
22
|
+
* #288); the issue lineage + `dario_overage_guard` error type are kept stable.
|
|
16
23
|
*
|
|
17
24
|
* Resume paths:
|
|
18
25
|
* - explicit: `dario resume` CLI → POST /admin/resume → `clear('manual')`
|
|
@@ -26,6 +33,7 @@
|
|
|
26
33
|
* See dario#288.
|
|
27
34
|
*/
|
|
28
35
|
import { EventEmitter } from 'node:events';
|
|
36
|
+
import { isNonSubscriptionBilling } from './analytics.js';
|
|
29
37
|
export class OverageGuard extends EventEmitter {
|
|
30
38
|
opts;
|
|
31
39
|
halted = null;
|
|
@@ -39,9 +47,11 @@ export class OverageGuard extends EventEmitter {
|
|
|
39
47
|
this.opts = opts;
|
|
40
48
|
}
|
|
41
49
|
/**
|
|
42
|
-
* Subscribe to an Analytics instance. Every record
|
|
43
|
-
*
|
|
44
|
-
*
|
|
50
|
+
* Subscribe to an Analytics instance. Every record whose claim is a
|
|
51
|
+
* non-subscription billing classification (`isNonSubscriptionBilling` —
|
|
52
|
+
* `overage`, `api`, or a new credit/SDK bucket) triggers halt (when
|
|
53
|
+
* behavior === 'halt') or a warn-only event (when behavior === 'warn').
|
|
54
|
+
* Subscription claims and the `unknown` sentinel are ignored.
|
|
45
55
|
*
|
|
46
56
|
* Idempotent — calling attach() a second time replaces the listener
|
|
47
57
|
* rather than stacking; useful for tests.
|
|
@@ -57,7 +67,7 @@ export class OverageGuard extends EventEmitter {
|
|
|
57
67
|
return;
|
|
58
68
|
}
|
|
59
69
|
this.analyticsListener = (r) => {
|
|
60
|
-
if (r.claim
|
|
70
|
+
if (isNonSubscriptionBilling(r.claim)) {
|
|
61
71
|
this.onOverageDetected(r);
|
|
62
72
|
}
|
|
63
73
|
};
|
|
@@ -113,7 +123,7 @@ export class OverageGuard extends EventEmitter {
|
|
|
113
123
|
if (this.opts.notifyOs && this.opts.notifier) {
|
|
114
124
|
try {
|
|
115
125
|
const title = this.opts.behavior === 'halt' ? 'dario halted' : 'dario warning';
|
|
116
|
-
const msg = `Request classified as '
|
|
126
|
+
const msg = `Request classified as '${r.claim}' (non-subscription billing)${this.opts.behavior === 'halt' ? '. Proxy halted. Run `dario resume` to continue.' : ''}`;
|
|
117
127
|
this.opts.notifier(title, msg);
|
|
118
128
|
}
|
|
119
129
|
catch {
|
|
@@ -180,10 +190,10 @@ export function buildHaltErrorBody(state) {
|
|
|
180
190
|
error: {
|
|
181
191
|
type: 'dario_overage_guard',
|
|
182
192
|
message: `dario halted to prevent API-rate bleed. A request was classified ` +
|
|
183
|
-
`as '
|
|
184
|
-
`To resume: run \`dario resume\` in another
|
|
185
|
-
|
|
186
|
-
`Details: github.com/askalf/dario/issues/288`,
|
|
193
|
+
`as '${state.request.claim}' (non-subscription billing) instead of ` +
|
|
194
|
+
`your subscription pool. To resume: run \`dario resume\` in another ` +
|
|
195
|
+
`terminal, or wait until ${isoCooldown} for the cooldown to ` +
|
|
196
|
+
`auto-clear. Details: github.com/askalf/dario/issues/288`,
|
|
187
197
|
},
|
|
188
198
|
};
|
|
189
199
|
}
|
package/dist/proxy.d.ts
CHANGED
|
@@ -321,11 +321,13 @@ interface ProxyOptions {
|
|
|
321
321
|
*/
|
|
322
322
|
upstreamApiKey?: string;
|
|
323
323
|
/**
|
|
324
|
-
* Overage-guard — halt the proxy on the first response
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
324
|
+
* Overage-guard — halt the proxy on the first response that bills to
|
|
325
|
+
* anything other than the subscription pool (`overage`, `api`, or a new
|
|
326
|
+
* credit/SDK bucket — the 2026-06-15 split). Subscribers should never see
|
|
327
|
+
* a single non-subscription hit during normal operation; one means
|
|
328
|
+
* something is wrong (wire-shape drift, classifier change, account
|
|
329
|
+
* misconfig) and continuing to forward bleeds against per-token billing.
|
|
330
|
+
* Auto-disabled in upstream-API-key passthrough mode (see proxy setup).
|
|
329
331
|
*
|
|
330
332
|
* Default: enabled, halt behavior, 30-min cooldown, OS-notify on.
|
|
331
333
|
* See dario#288.
|
package/dist/proxy.js
CHANGED
|
@@ -44,7 +44,19 @@ function computeBuildTag(userMessage, version) {
|
|
|
44
44
|
const chars = [4, 7, 20].map(i => userMessage[i] || '0').join('');
|
|
45
45
|
return createHash('sha256').update(`${BILLING_SEED}${chars}${version}`).digest('hex').slice(0, 3);
|
|
46
46
|
}
|
|
47
|
-
// Per-request cch:
|
|
47
|
+
// Per-request cch. NOTE: real Claude Code does NOT randomise this — it's a
|
|
48
|
+
// deterministic xxHash64 integrity hash over the serialized request body,
|
|
49
|
+
// written in place over a `cch=00000` placeholder inside the billing-tag
|
|
50
|
+
// system block (reverse-engineered on cc_version=2.1.37; see dario#528).
|
|
51
|
+
//
|
|
52
|
+
// We emit a random 5-hex value on purpose. The published seed
|
|
53
|
+
// (0x6E52736AC806831E) + scope do NOT reproduce the cch on current CC
|
|
54
|
+
// (falsified against a live 2.1.177 capture), and the seed/scope appear to
|
|
55
|
+
// rotate between releases. A deterministic-but-wrong value is no better than
|
|
56
|
+
// random against a server that recomputes — and a cleaner tell — whereas a
|
|
57
|
+
// random value keeps the right shape (5 lowercase hex) and varies per request
|
|
58
|
+
// like the real one. Revisit only if Anthropic starts rejecting on cch, which
|
|
59
|
+
// would require re-RE'ing the current binary for the live seed + pre-image.
|
|
48
60
|
function computeCch() {
|
|
49
61
|
return randomBytes(3).toString('hex').slice(0, 5);
|
|
50
62
|
}
|
|
@@ -807,8 +819,15 @@ export async function startProxy(opts = {}) {
|
|
|
807
819
|
// defaults (enabled=true, behavior='halt', cooldown=30min, notifyOs=true)
|
|
808
820
|
// so an opts-less proxy still gets protection. The notifier is wired
|
|
809
821
|
// separately below once notify.ts is loaded.
|
|
822
|
+
//
|
|
823
|
+
// Disabled in upstream-API-key passthrough mode: there the operator
|
|
824
|
+
// explicitly opted into the per-token API pool, so responses legitimately
|
|
825
|
+
// bill as `api`. The guard now halts on ANY non-subscription claim (incl.
|
|
826
|
+
// `api`), so leaving it on would false-halt the whole passthrough path
|
|
827
|
+
// (e.g. the self-hosted compat workflow). The subscription-protection
|
|
828
|
+
// premise doesn't apply when there's no subscription to protect.
|
|
810
829
|
const overageGuard = new OverageGuard({
|
|
811
|
-
enabled: opts.overageGuardEnabled ?? true,
|
|
830
|
+
enabled: (opts.overageGuardEnabled ?? true) && !upstreamApiKey,
|
|
812
831
|
behavior: opts.overageGuardBehavior ?? 'halt',
|
|
813
832
|
cooldownMs: opts.overageGuardCooldownMs ?? 30 * 60 * 1000,
|
|
814
833
|
notifyOs: opts.overageGuardNotifyOs ?? true,
|
|
@@ -820,10 +839,10 @@ export async function startProxy(opts = {}) {
|
|
|
820
839
|
// a TUI attached. -v / --verbose is not required — this is loud by
|
|
821
840
|
// design.
|
|
822
841
|
overageGuard.on('halt', (state) => {
|
|
823
|
-
console.error(`[dario] OVERAGE-GUARD HALTED: ${state.request.model} on account=${state.request.account} returned representative-claim
|
|
842
|
+
console.error(`[dario] OVERAGE-GUARD HALTED: ${state.request.model} on account=${state.request.account} returned representative-claim=${state.request.claim} at ${new Date(state.request.timestamp).toISOString()}. Returning 503 to new requests until \`dario resume\` or cooldown expires (${new Date(state.cooldownUntil).toISOString()}). See dario#288.`);
|
|
824
843
|
});
|
|
825
844
|
overageGuard.on('warn', (state) => {
|
|
826
|
-
console.error(`[dario] OVERAGE-GUARD WARN: ${state.request.model} on account=${state.request.account} returned representative-claim
|
|
845
|
+
console.error(`[dario] OVERAGE-GUARD WARN: ${state.request.model} on account=${state.request.account} returned representative-claim=${state.request.claim} at ${new Date(state.request.timestamp).toISOString()}. Behavior=warn — proxy continuing to forward; investigate before bill bleeds. See dario#288.`);
|
|
827
846
|
});
|
|
828
847
|
overageGuard.on('resume', (info) => {
|
|
829
848
|
console.error(`[dario] overage-guard resumed (${info.reason}). Normal request handling restored.`);
|
package/dist/tui/tabs/hits.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import { fg, dim, brand, inverse, BOX, pad, truncate } from '../render.js';
|
|
27
27
|
import { renderKvRow } from '../layout.js';
|
|
28
|
-
import { billingBucketFromClaim } from '../../analytics.js';
|
|
28
|
+
import { billingBucketFromClaim, isNonSubscriptionBilling } from '../../analytics.js';
|
|
29
29
|
const MAX_BUFFER = 5000;
|
|
30
30
|
export const HitsTab = {
|
|
31
31
|
id: 'hits',
|
|
@@ -143,7 +143,7 @@ export const HitsTab = {
|
|
|
143
143
|
if (state.halt) {
|
|
144
144
|
const since = formatTimestamp(state.halt.since);
|
|
145
145
|
const cooldown = formatRemaining(state.halt.cooldownUntil - Date.now());
|
|
146
|
-
const line1 = ` ${fg('red', '⚠ HALTED')}
|
|
146
|
+
const line1 = ` ${fg('red', '⚠ HALTED')} ${state.halt.request.claim} detected at ${since} on ${state.halt.request.model} (account=${state.halt.request.account})`;
|
|
147
147
|
const line2 = ` ${dim('→ New /v1/messages requests return 503 until')} ${fg('cyan', 'R')} ${dim('here, or')} ${fg('cyan', 'dario resume')}${dim(' from any shell. Auto-resume in')} ${cooldown}${dim('.')}`;
|
|
148
148
|
lines.push(line1);
|
|
149
149
|
lines.push(line2);
|
|
@@ -158,9 +158,12 @@ export const HitsTab = {
|
|
|
158
158
|
pad('st', colStatus)));
|
|
159
159
|
for (let i = startIdx; i < endIdx; i++) {
|
|
160
160
|
const r = newestFirst[i];
|
|
161
|
-
|
|
161
|
+
// Flag any non-subscription billing red — the same condition the
|
|
162
|
+
// overage-guard halts on (overage, api, or a credit/SDK bucket), not
|
|
163
|
+
// just literal `overage`. See isNonSubscriptionBilling (#288).
|
|
164
|
+
const isNonSub = isNonSubscriptionBilling(r.claim);
|
|
162
165
|
const marker = i === state.selectedIdx ? fg('cyan', '▎')
|
|
163
|
-
:
|
|
166
|
+
: isNonSub ? fg('red', '!')
|
|
164
167
|
: ' ';
|
|
165
168
|
const row = marker + ' ' +
|
|
166
169
|
pad(formatTime(r.timestamp), colTime) +
|
|
@@ -169,12 +172,12 @@ export const HitsTab = {
|
|
|
169
172
|
pad(formatTokens(r.outputTokens), colOut) +
|
|
170
173
|
pad(formatLatency(r.latencyMs), colLat) +
|
|
171
174
|
pad(formatStatus(r.status), colStatus);
|
|
172
|
-
//
|
|
173
|
-
// wins via the inverse() wrapper so the user can drill into one.
|
|
175
|
+
// Non-subscription rows render in red even when unselected; selection
|
|
176
|
+
// still wins via the inverse() wrapper so the user can drill into one.
|
|
174
177
|
let styled;
|
|
175
178
|
if (i === state.selectedIdx)
|
|
176
179
|
styled = inverse(truncate(row, w - 2));
|
|
177
|
-
else if (
|
|
180
|
+
else if (isNonSub)
|
|
178
181
|
styled = fg('red', truncate(row, w - 2));
|
|
179
182
|
else
|
|
180
183
|
styled = truncate(row, w - 2);
|
package/dist/tui/tabs/status.js
CHANGED
|
@@ -115,7 +115,7 @@ export const StatusTab = {
|
|
|
115
115
|
const remainingMs = Math.max(0, s.cooldownUntil - Date.now());
|
|
116
116
|
const remaining = formatDuration(remainingMs);
|
|
117
117
|
// Red banner header — this is the loud surface when halted
|
|
118
|
-
lines.push(' ' + fg('red', '⚠ HALTED') + ' ' + dim(
|
|
118
|
+
lines.push(' ' + fg('red', '⚠ HALTED') + ' ' + dim(`${s.request.claim} detected ${formatAgo(s.since)} ago`));
|
|
119
119
|
lines.push(' ' + renderKvRow('Request', `${s.request.model} ${dim('account=' + s.request.account)}`, w - 4));
|
|
120
120
|
lines.push(' ' + renderKvRow('Cause', `representative-claim = ${fg('red', s.request.claim)}`, w - 4));
|
|
121
121
|
lines.push(' ' + renderKvRow('Auto-resume in', remaining === '0s' ? fg('yellow', 'now (cooldown elapsed)') : remaining, w - 4));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "4.8.
|
|
3
|
+
"version": "4.8.76",
|
|
4
4
|
"description": "Use your Claude Pro/Max subscription in any tool — Cursor, Cline, Aider, the Agent SDK, your scripts — at subscription pricing, not per-token API bills. One local Anthropic + OpenAI-compatible endpoint.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|