@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.
Files changed (44) hide show
  1. package/README.md +2 -2
  2. package/app/agents/CostSection.tsx +100 -0
  3. package/app/agents/PageClient.tsx +7 -0
  4. package/app/api/assistant/route.ts +8 -1
  5. package/app/api/costs/agent/route.ts +56 -0
  6. package/app/api/costs/route.ts +19 -77
  7. package/app/api/costs/usage/route.ts +65 -0
  8. package/app/api/gateway/sync.ts +18 -4
  9. package/app/api/stats/route.ts +2 -3
  10. package/app/api/stats-since/route.ts +1 -1
  11. package/app/api/stream/route.ts +3 -5
  12. package/app/api/system-health/route.ts +33 -32
  13. package/app/components/CostBreakdown.tsx +1 -1
  14. package/app/components/Sidebar.tsx +10 -0
  15. package/app/components/SystemCockpit.tsx +1 -1
  16. package/app/components/ui/TimeSeriesChart.tsx +27 -9
  17. package/app/costs/CostsSkeleton.tsx +88 -0
  18. package/app/costs/PageClient.tsx +366 -0
  19. package/app/costs/loading.tsx +13 -0
  20. package/app/costs/page.tsx +5 -0
  21. package/app/globals.css +6 -0
  22. package/daemon.js +455 -41
  23. package/docs/ARCHITECTURE.md +39 -29
  24. package/docs/DESIGN-SYSTEM.md +2 -2
  25. package/docs/FRONTEND-ARCHITECTURE.md +4 -2
  26. package/docs/REV4A.md +3 -2
  27. package/docs/dev/API-REFERENCE.md +118 -22
  28. package/docs/dev/DATABASE.md +119 -19
  29. package/docs/dev/GATEWAY.md +53 -9
  30. package/docs/rag/DATA-FRESHNESS.md +19 -7
  31. package/docs/rag/GLOSSARY.md +7 -4
  32. package/docs/rag/REV4A-OVERVIEW.md +4 -1
  33. package/docs/rag/WHAT-I-CAN-ANSWER.md +3 -3
  34. package/instrumentation.ts +11 -0
  35. package/lib/agent-costs.ts +172 -0
  36. package/lib/cost-reconciliation.ts +78 -0
  37. package/lib/costs-db.ts +31 -0
  38. package/lib/model-pricing.ts +123 -3
  39. package/lib/price-schedule-sync.ts +133 -0
  40. package/lib/utils/format.ts +12 -0
  41. package/model-pricing.json +269 -121
  42. package/package.json +1 -1
  43. package/scripts/refresh-model-pricing.mjs +16 -4
  44. package/lib/billing.ts +0 -100
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flame0510/project-aether",
3
- "version": "1.9.1",
3
+ "version": "1.10.0",
4
4
  "description": "Rev4a — Revolution for Agents. OpenClaw agent fleet orchestrator.",
5
5
  "keywords": [
6
6
  "openclaw",
@@ -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]) => ` ${JSON.stringify(id)}: {\n "input": ${num(p.input)},\n "output": ${num(p.output)}\n }`)
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
- if (!current || current.input !== next.input || current.output !== next.output) {
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 ? `$${current.input}/$${current.output}` : '(none)',
250
- to: `$${next.input}/$${next.output}`,
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
- }