@askalf/dario 3.11.0 → 3.11.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/dist/analytics.d.ts +39 -16
- package/dist/analytics.js +41 -0
- package/dist/proxy.js +8 -2
- package/package.json +2 -2
package/dist/analytics.d.ts
CHANGED
|
@@ -23,6 +23,26 @@ export interface RequestRecord {
|
|
|
23
23
|
isStream: boolean;
|
|
24
24
|
isOpenAI: boolean;
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* The four billing buckets a request can land in, derived from the
|
|
28
|
+
* `anthropic-ratelimit-unified-representative-claim` response header.
|
|
29
|
+
*
|
|
30
|
+
* - `subscription` — request billed against the user's 5h subscription window (Max/Pro)
|
|
31
|
+
* - `subscription_fallback` — server-side fallback subscription bucket (rare, still covered)
|
|
32
|
+
* - `extra_usage` — overage / pay-as-you-go, paid on top of subscription
|
|
33
|
+
* - `api` — pure API key billing, no subscription involved
|
|
34
|
+
* - `unknown` — header absent or unparseable (non-200 responses, stream aborts)
|
|
35
|
+
*
|
|
36
|
+
* Exposed in `/analytics` summaries and in verbose per-request logs so
|
|
37
|
+
* users can see at a glance which bucket their traffic is actually hitting.
|
|
38
|
+
* See #34 for background.
|
|
39
|
+
*/
|
|
40
|
+
export type BillingBucket = 'subscription' | 'subscription_fallback' | 'extra_usage' | 'api' | 'unknown';
|
|
41
|
+
/**
|
|
42
|
+
* Map the raw `representative-claim` header value to a human-friendly
|
|
43
|
+
* billing bucket. Pure function; no state; safe to call from any context.
|
|
44
|
+
*/
|
|
45
|
+
export declare function billingBucketFromClaim(claim: string | null | undefined): BillingBucket;
|
|
26
46
|
export declare class Analytics {
|
|
27
47
|
private records;
|
|
28
48
|
private maxRecords;
|
|
@@ -60,27 +80,30 @@ interface PerModelStat {
|
|
|
60
80
|
avgThinkingTokens: number;
|
|
61
81
|
estimatedCost: number;
|
|
62
82
|
}
|
|
83
|
+
interface WindowStats {
|
|
84
|
+
totalInputTokens: number;
|
|
85
|
+
totalOutputTokens: number;
|
|
86
|
+
totalThinkingTokens: number;
|
|
87
|
+
estimatedCost: number;
|
|
88
|
+
avgLatencyMs: number;
|
|
89
|
+
errorRate: number;
|
|
90
|
+
claimBreakdown: Record<string, number>;
|
|
91
|
+
/** Count of requests in each derived billing bucket. See #34. */
|
|
92
|
+
billingBucketBreakdown: Record<BillingBucket, number>;
|
|
93
|
+
/**
|
|
94
|
+
* Percentage of *classified* requests (non-unknown) that hit a
|
|
95
|
+
* subscription bucket. The headline number for "is dario routing me
|
|
96
|
+
* through my subscription?" — should be 100% for a clean setup. See #34.
|
|
97
|
+
*/
|
|
98
|
+
subscriptionPercent: number;
|
|
99
|
+
}
|
|
63
100
|
export interface AnalyticsSummary {
|
|
64
|
-
window: {
|
|
101
|
+
window: WindowStats & {
|
|
65
102
|
minutes: number;
|
|
66
103
|
requests: number;
|
|
67
|
-
totalInputTokens: number;
|
|
68
|
-
totalOutputTokens: number;
|
|
69
|
-
totalThinkingTokens: number;
|
|
70
|
-
estimatedCost: number;
|
|
71
|
-
avgLatencyMs: number;
|
|
72
|
-
errorRate: number;
|
|
73
|
-
claimBreakdown: Record<string, number>;
|
|
74
104
|
};
|
|
75
|
-
allTime: {
|
|
105
|
+
allTime: WindowStats & {
|
|
76
106
|
requests: number;
|
|
77
|
-
totalInputTokens: number;
|
|
78
|
-
totalOutputTokens: number;
|
|
79
|
-
totalThinkingTokens: number;
|
|
80
|
-
estimatedCost: number;
|
|
81
|
-
avgLatencyMs: number;
|
|
82
|
-
errorRate: number;
|
|
83
|
-
claimBreakdown: Record<string, number>;
|
|
84
107
|
};
|
|
85
108
|
perAccount: Record<string, PerAccountStat>;
|
|
86
109
|
perModel: Record<string, PerModelStat>;
|
package/dist/analytics.js
CHANGED
|
@@ -5,6 +5,24 @@
|
|
|
5
5
|
* In-memory rolling window; exposed via the /analytics endpoint when
|
|
6
6
|
* pool mode is active.
|
|
7
7
|
*/
|
|
8
|
+
/**
|
|
9
|
+
* Map the raw `representative-claim` header value to a human-friendly
|
|
10
|
+
* billing bucket. Pure function; no state; safe to call from any context.
|
|
11
|
+
*/
|
|
12
|
+
export function billingBucketFromClaim(claim) {
|
|
13
|
+
switch (claim) {
|
|
14
|
+
case 'five_hour':
|
|
15
|
+
return 'subscription';
|
|
16
|
+
case 'five_hour_fallback':
|
|
17
|
+
return 'subscription_fallback';
|
|
18
|
+
case 'overage':
|
|
19
|
+
return 'extra_usage';
|
|
20
|
+
case 'api':
|
|
21
|
+
return 'api';
|
|
22
|
+
default:
|
|
23
|
+
return 'unknown';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
8
26
|
// Anthropic pricing (per 1M tokens, USD). Not authoritative — used for
|
|
9
27
|
// rough burn-rate display in the /analytics summary.
|
|
10
28
|
const PRICING = {
|
|
@@ -74,6 +92,14 @@ export class Analytics {
|
|
|
74
92
|
totalInputTokens: 0, totalOutputTokens: 0, totalThinkingTokens: 0,
|
|
75
93
|
estimatedCost: 0, avgLatencyMs: 0, errorRate: 0,
|
|
76
94
|
claimBreakdown: {},
|
|
95
|
+
billingBucketBreakdown: {
|
|
96
|
+
subscription: 0,
|
|
97
|
+
subscription_fallback: 0,
|
|
98
|
+
extra_usage: 0,
|
|
99
|
+
api: 0,
|
|
100
|
+
unknown: 0,
|
|
101
|
+
},
|
|
102
|
+
subscriptionPercent: 0,
|
|
77
103
|
};
|
|
78
104
|
}
|
|
79
105
|
const totalInput = records.reduce((s, r) => s + r.inputTokens, 0);
|
|
@@ -83,9 +109,22 @@ export class Analytics {
|
|
|
83
109
|
const avgLatency = records.reduce((s, r) => s + r.latencyMs, 0) / records.length;
|
|
84
110
|
const errors = records.filter(r => r.status >= 400).length;
|
|
85
111
|
const claims = {};
|
|
112
|
+
const buckets = {
|
|
113
|
+
subscription: 0,
|
|
114
|
+
subscription_fallback: 0,
|
|
115
|
+
extra_usage: 0,
|
|
116
|
+
api: 0,
|
|
117
|
+
unknown: 0,
|
|
118
|
+
};
|
|
86
119
|
for (const r of records) {
|
|
87
120
|
claims[r.claim] = (claims[r.claim] ?? 0) + 1;
|
|
121
|
+
buckets[billingBucketFromClaim(r.claim)]++;
|
|
88
122
|
}
|
|
123
|
+
const subscriptionHits = buckets.subscription + buckets.subscription_fallback;
|
|
124
|
+
const billedRequests = records.length - buckets.unknown;
|
|
125
|
+
const subscriptionPct = billedRequests > 0
|
|
126
|
+
? Math.round((subscriptionHits / billedRequests) * 10000) / 100
|
|
127
|
+
: 0;
|
|
89
128
|
return {
|
|
90
129
|
totalInputTokens: totalInput,
|
|
91
130
|
totalOutputTokens: totalOutput,
|
|
@@ -94,6 +133,8 @@ export class Analytics {
|
|
|
94
133
|
avgLatencyMs: Math.round(avgLatency),
|
|
95
134
|
errorRate: Math.round((errors / records.length) * 10000) / 10000,
|
|
96
135
|
claimBreakdown: claims,
|
|
136
|
+
billingBucketBreakdown: buckets,
|
|
137
|
+
subscriptionPercent: subscriptionPct,
|
|
97
138
|
};
|
|
98
139
|
}
|
|
99
140
|
perAccountStats(records) {
|
package/dist/proxy.js
CHANGED
|
@@ -8,7 +8,7 @@ import { arch, platform } from 'node:process';
|
|
|
8
8
|
import { getAccessToken, getStatus } from './oauth.js';
|
|
9
9
|
import { buildCCRequest, reverseMapResponse, createStreamingReverseMapper } from './cc-template.js';
|
|
10
10
|
import { AccountPool, parseRateLimits } from './pool.js';
|
|
11
|
-
import { Analytics } from './analytics.js';
|
|
11
|
+
import { Analytics, billingBucketFromClaim } from './analytics.js';
|
|
12
12
|
import { loadAllAccounts, loadAccount, refreshAccountToken } from './accounts.js';
|
|
13
13
|
import { getOpenAIBackend, isOpenAIModel, forwardToOpenAI } from './openai-backend.js';
|
|
14
14
|
const ANTHROPIC_API = 'https://api.anthropic.com';
|
|
@@ -1039,7 +1039,13 @@ export async function startProxy(opts = {}) {
|
|
|
1039
1039
|
else {
|
|
1040
1040
|
overagePct = 'n/a';
|
|
1041
1041
|
}
|
|
1042
|
-
|
|
1042
|
+
// Show the derived billing bucket as the headline, with the raw
|
|
1043
|
+
// claim value in parens so power users still see the header as-is.
|
|
1044
|
+
// See #34 — users want "am I actually on subscription?" answered
|
|
1045
|
+
// at a glance instead of having to memorize that `five_hour` means
|
|
1046
|
+
// "yes, subscription."
|
|
1047
|
+
const bucket = billingBucketFromClaim(billingClaim);
|
|
1048
|
+
console.log(`[dario] #${requestCount} billing: ${bucket} (${billingClaim}, overage: ${overagePct})`);
|
|
1043
1049
|
}
|
|
1044
1050
|
else if (verbose) {
|
|
1045
1051
|
console.log(`[dario] #${requestCount} billing: headers absent (status=${upstream.status})`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "3.11.
|
|
3
|
+
"version": "3.11.1",
|
|
4
4
|
"description": "A local LLM router. One endpoint, every provider — Claude subscriptions, OpenAI, OpenRouter, Groq, local LiteLLM, any OpenAI-compat endpoint — your tools don't need to change.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
23
23
|
"build": "tsc && cp src/cc-template-data.json dist/",
|
|
24
|
-
"test": "node test/issue-29-tool-translation.mjs && node test/hybrid-tools.mjs && node test/scrub-paths.mjs && node test/provider-prefix.mjs && node test/analytics-recording.mjs && node test/failover-429.mjs && node test/live-fingerprint.mjs",
|
|
24
|
+
"test": "node test/issue-29-tool-translation.mjs && node test/hybrid-tools.mjs && node test/scrub-paths.mjs && node test/provider-prefix.mjs && node test/analytics-recording.mjs && node test/analytics-billing-bucket.mjs && node test/failover-429.mjs && node test/live-fingerprint.mjs",
|
|
25
25
|
"audit": "npm audit --production --audit-level=high",
|
|
26
26
|
"prepublishOnly": "npm run build",
|
|
27
27
|
"start": "node dist/cli.js",
|