@flame0510/project-aether 1.9.1 → 1.10.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 +2 -2
- package/app/agents/CostSection.tsx +100 -0
- package/app/agents/PageClient.tsx +7 -0
- package/app/api/assistant/route.ts +8 -1
- package/app/api/costs/agent/route.ts +56 -0
- package/app/api/costs/route.ts +19 -77
- package/app/api/costs/usage/route.ts +65 -0
- package/app/api/gateway/sync.ts +18 -4
- package/app/api/stats/route.ts +2 -3
- package/app/api/stats-since/route.ts +1 -1
- package/app/api/stream/route.ts +3 -5
- package/app/api/system-health/route.ts +33 -32
- package/app/components/CostBreakdown.tsx +1 -1
- package/app/components/Sidebar.tsx +10 -0
- package/app/components/SystemCockpit.tsx +1 -1
- package/app/components/ui/TimeSeriesChart.tsx +27 -9
- package/app/costs/CostsSkeleton.tsx +88 -0
- package/app/costs/PageClient.tsx +366 -0
- package/app/costs/loading.tsx +13 -0
- package/app/costs/page.tsx +5 -0
- package/app/globals.css +6 -0
- package/daemon.js +455 -41
- package/docs/ARCHITECTURE.md +39 -29
- package/docs/DESIGN-SYSTEM.md +2 -2
- package/docs/FRONTEND-ARCHITECTURE.md +4 -2
- package/docs/REV4A.md +3 -2
- package/docs/dev/API-REFERENCE.md +118 -22
- package/docs/dev/DATABASE.md +119 -19
- package/docs/dev/GATEWAY.md +53 -9
- package/docs/rag/DATA-FRESHNESS.md +19 -7
- package/docs/rag/GLOSSARY.md +7 -4
- package/docs/rag/REV4A-OVERVIEW.md +4 -1
- package/docs/rag/WHAT-I-CAN-ANSWER.md +3 -3
- package/instrumentation.ts +11 -0
- package/lib/agent-costs.ts +172 -0
- package/lib/cost-reconciliation.ts +78 -0
- package/lib/costs-db.ts +31 -0
- package/lib/model-pricing.ts +123 -3
- package/lib/price-schedule-sync.ts +133 -0
- package/lib/utils/format.ts +12 -0
- package/model-pricing.json +269 -121
- package/package.json +1 -1
- package/scripts/refresh-model-pricing.mjs +16 -4
- package/lib/billing.ts +0 -100
package/package.json
CHANGED
|
@@ -86,7 +86,12 @@ function round(n) {
|
|
|
86
86
|
function serialisePricing(pricing) {
|
|
87
87
|
const num = (n) => String(n);
|
|
88
88
|
const body = Object.entries(pricing)
|
|
89
|
-
.map(([id, p]) =>
|
|
89
|
+
.map(([id, p]) => {
|
|
90
|
+
const rates = [['input', p.input], ['output', p.output], ['cacheRead', p.cacheRead], ['cacheWrite', p.cacheWrite]]
|
|
91
|
+
.filter(([, v]) => v !== undefined)
|
|
92
|
+
.map(([k, v]) => ` "${k}": ${num(v)}`);
|
|
93
|
+
return ` ${JSON.stringify(id)}: {\n${rates.join(',\n')}\n }`;
|
|
94
|
+
})
|
|
90
95
|
.join(',\n');
|
|
91
96
|
return `{\n${body}\n}\n`;
|
|
92
97
|
}
|
|
@@ -243,11 +248,18 @@ async function main() {
|
|
|
243
248
|
} else {
|
|
244
249
|
const current = pricing[m.id];
|
|
245
250
|
const next = { input: round(input), output: round(output) };
|
|
246
|
-
|
|
251
|
+
// Cache rates when OpenRouter publishes them: OpenClaw prices cached tokens with
|
|
252
|
+
// these, and without one they fall back to the input rate (lib/model-pricing.ts).
|
|
253
|
+
const cacheRead = perMillion(up.pricing?.input_cache_read);
|
|
254
|
+
const cacheWrite = perMillion(up.pricing?.input_cache_write);
|
|
255
|
+
if (cacheRead !== null) next.cacheRead = round(cacheRead);
|
|
256
|
+
if (cacheWrite !== null) next.cacheWrite = round(cacheWrite);
|
|
257
|
+
const show = (x) => `${x.input}/${x.output}` + (x.cacheRead !== undefined ? ` cache ${x.cacheRead}` : '');
|
|
258
|
+
if (!current || ['input', 'output', 'cacheRead', 'cacheWrite'].some((k) => current[k] !== next[k])) {
|
|
247
259
|
priceChanges.push({
|
|
248
260
|
id: m.id,
|
|
249
|
-
from: current ?
|
|
250
|
-
to:
|
|
261
|
+
from: current ? show(current) : '(none)',
|
|
262
|
+
to: show(next),
|
|
251
263
|
});
|
|
252
264
|
pricing[m.id] = next;
|
|
253
265
|
}
|
package/lib/billing.ts
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import type { ModelCost } from '@/lib/types';
|
|
2
|
-
|
|
3
|
-
export type BillingMode = 'usage' | 'fixed' | 'credits' | 'unknown';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Economic dollar costs should only include providers we actually bill per usage.
|
|
7
|
-
* Today that is primarily OpenRouter, plus a small set of explicit pay-per-use aliases
|
|
8
|
-
* stored without provider prefixes in older rows.
|
|
9
|
-
*/
|
|
10
|
-
const BILLABLE_MODEL_PREFIXES = ['openrouter/'] as const;
|
|
11
|
-
const BILLABLE_ALIAS_PREFIXES = ['deepseek', 'deepseek4', 'or-', 'openrouter', 'qwen', 'kimi', 'glm'] as const;
|
|
12
|
-
const NON_BILLABLE_ALIAS_PATTERNS = [
|
|
13
|
-
/^gpt-5(\.|-|$)/,
|
|
14
|
-
/^codex(\.|-|$)/,
|
|
15
|
-
/^claude(\.|-|$)/,
|
|
16
|
-
/^claude-cli(\/|$)/,
|
|
17
|
-
/^anthropic(\/|$)/,
|
|
18
|
-
/^openai-codex(\/|$)/,
|
|
19
|
-
/^github-copilot(\/|$)/,
|
|
20
|
-
] as const;
|
|
21
|
-
|
|
22
|
-
export interface BillingBucket {
|
|
23
|
-
mode: BillingMode;
|
|
24
|
-
cost_usd: number;
|
|
25
|
-
sessions: number;
|
|
26
|
-
tokens_in: number;
|
|
27
|
-
tokens_out: number;
|
|
28
|
-
models: ModelCost[];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface BillingSummary {
|
|
32
|
-
usageBasedCost: number;
|
|
33
|
-
dbEstimatedCost: number;
|
|
34
|
-
buckets: Record<BillingMode, BillingBucket>;
|
|
35
|
-
usageModels: ModelCost[];
|
|
36
|
-
fixedModels: ModelCost[];
|
|
37
|
-
creditModels: ModelCost[];
|
|
38
|
-
unknownModels: ModelCost[];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function isBillableDollarModel(model?: string | null): boolean {
|
|
42
|
-
const m = (model ?? '').trim().toLowerCase();
|
|
43
|
-
if (!m || m === 'unknown') return false;
|
|
44
|
-
if (BILLABLE_MODEL_PREFIXES.some((prefix) => m.startsWith(prefix))) return true;
|
|
45
|
-
if (NON_BILLABLE_ALIAS_PATTERNS.some((pattern) => pattern.test(m))) return false;
|
|
46
|
-
if (m.includes('/')) return false;
|
|
47
|
-
return BILLABLE_ALIAS_PREFIXES.some((prefix) => m.startsWith(prefix));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function classifyBillingModel(model?: string | null): BillingMode {
|
|
51
|
-
const m = (model ?? '').toLowerCase();
|
|
52
|
-
if (!m || m === 'unknown') return 'unknown';
|
|
53
|
-
if (isBillableDollarModel(m)) return 'usage';
|
|
54
|
-
if (m.startsWith('openai-codex/')) return 'fixed';
|
|
55
|
-
if (m.startsWith('anthropic/') || m.includes('claude-cli')) return 'fixed';
|
|
56
|
-
if (m.startsWith('github-copilot/')) return 'credits';
|
|
57
|
-
|
|
58
|
-
// Rev4a DB often stores model aliases without provider prefix.
|
|
59
|
-
if (/^gpt-5\.(2|3|4|5)(-codex|-mini|-pro)?$/.test(m)) return 'fixed';
|
|
60
|
-
if (/^claude-(haiku|sonnet|opus)-/.test(m)) return 'fixed';
|
|
61
|
-
if (/^gemini-.*(flash|pro)/.test(m)) return 'credits';
|
|
62
|
-
|
|
63
|
-
// Known OpenRouter/free/usage-style aliases seen in this workspace.
|
|
64
|
-
if (/^(deepseek|deepseek4|or-|openrouter|qwen|kimi|glm)/.test(m)) return 'usage';
|
|
65
|
-
return 'unknown';
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function emptyBucket(mode: BillingMode): BillingBucket {
|
|
69
|
-
return { mode, cost_usd: 0, sessions: 0, tokens_in: 0, tokens_out: 0, models: [] };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function summarizeBilling(models: ModelCost[]): BillingSummary {
|
|
73
|
-
const buckets: Record<BillingMode, BillingBucket> = {
|
|
74
|
-
usage: emptyBucket('usage'),
|
|
75
|
-
fixed: emptyBucket('fixed'),
|
|
76
|
-
credits: emptyBucket('credits'),
|
|
77
|
-
unknown: emptyBucket('unknown'),
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
for (const model of models) {
|
|
81
|
-
const mode = classifyBillingModel(model.model);
|
|
82
|
-
const bucket = buckets[mode];
|
|
83
|
-
const cost = Number(model.cost_usd ?? 0);
|
|
84
|
-
bucket.cost_usd += cost;
|
|
85
|
-
bucket.sessions += Number(model.sessions ?? 0);
|
|
86
|
-
bucket.tokens_in += Number(model.tokens_in ?? 0);
|
|
87
|
-
bucket.tokens_out += Number(model.tokens_out ?? 0);
|
|
88
|
-
bucket.models.push(model);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
usageBasedCost: buckets.usage.cost_usd,
|
|
93
|
-
dbEstimatedCost: models.reduce((sum, model) => sum + Number(model.cost_usd ?? 0), 0),
|
|
94
|
-
buckets,
|
|
95
|
-
usageModels: buckets.usage.models,
|
|
96
|
-
fixedModels: buckets.fixed.models,
|
|
97
|
-
creditModels: buckets.credits.models,
|
|
98
|
-
unknownModels: buckets.unknown.models,
|
|
99
|
-
};
|
|
100
|
-
}
|