agent-nuvira 3.5.2 → 3.5.6
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 +66 -0
- package/dist/cli/chat.d.ts.map +1 -1
- package/dist/cli/chat.js +72 -19
- package/dist/cli/chat.js.map +1 -1
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +17 -3
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/execute.d.ts.map +1 -1
- package/dist/cli/execute.js +63 -22
- package/dist/cli/execute.js.map +1 -1
- package/dist/cli/failover-runner.js +4 -4
- package/dist/cli/failover-runner.js.map +1 -1
- package/dist/cli/gateway.js +13 -13
- package/dist/cli/gateway.js.map +1 -1
- package/dist/cli/loop-executor.d.ts +22 -0
- package/dist/cli/loop-executor.d.ts.map +1 -1
- package/dist/cli/loop-executor.js +56 -12
- package/dist/cli/loop-executor.js.map +1 -1
- package/dist/cli/stats.d.ts +62 -4
- package/dist/cli/stats.d.ts.map +1 -1
- package/dist/cli/stats.js +282 -5
- package/dist/cli/stats.js.map +1 -1
- package/dist/cli/ui/index.d.ts +2 -2
- package/dist/cli/ui/index.d.ts.map +1 -1
- package/dist/cli/ui/index.js +1 -1
- package/dist/cli/ui/index.js.map +1 -1
- package/dist/cli/ui/presenter.d.ts +43 -0
- package/dist/cli/ui/presenter.d.ts.map +1 -1
- package/dist/cli/ui/presenter.js +88 -1
- package/dist/cli/ui/presenter.js.map +1 -1
- package/dist/config/manager.d.ts +29 -1
- package/dist/config/manager.d.ts.map +1 -1
- package/dist/config/manager.js +178 -143
- package/dist/config/manager.js.map +1 -1
- package/dist/config/paths.d.ts +48 -1
- package/dist/config/paths.d.ts.map +1 -1
- package/dist/config/paths.js +59 -9
- package/dist/config/paths.js.map +1 -1
- package/dist/config/service-catalog.js +1 -1
- package/dist/config/service-catalog.js.map +1 -1
- package/dist/config/service-probe.js +1 -1
- package/dist/config/service-probe.js.map +1 -1
- package/dist/config/types.d.ts +22 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/gateway/adapters.js +8 -8
- package/dist/gateway/adapters.js.map +1 -1
- package/dist/gateway/registry.js +1 -1
- package/dist/gateway/registry.js.map +1 -1
- package/dist/learning/auto-router.d.ts +30 -4
- package/dist/learning/auto-router.d.ts.map +1 -1
- package/dist/learning/auto-router.js +54 -38
- package/dist/learning/auto-router.js.map +1 -1
- package/dist/learning/cost-classification.d.ts +188 -0
- package/dist/learning/cost-classification.d.ts.map +1 -0
- package/dist/learning/cost-classification.js +294 -0
- package/dist/learning/cost-classification.js.map +1 -0
- package/dist/learning/cost-ledger-maintenance.d.ts +122 -0
- package/dist/learning/cost-ledger-maintenance.d.ts.map +1 -0
- package/dist/learning/cost-ledger-maintenance.js +289 -0
- package/dist/learning/cost-ledger-maintenance.js.map +1 -0
- package/dist/learning/cost-reconcile.d.ts +160 -0
- package/dist/learning/cost-reconcile.d.ts.map +1 -0
- package/dist/learning/cost-reconcile.js +238 -0
- package/dist/learning/cost-reconcile.js.map +1 -0
- package/dist/learning/cost-tracker.d.ts +134 -13
- package/dist/learning/cost-tracker.d.ts.map +1 -1
- package/dist/learning/cost-tracker.js +190 -68
- package/dist/learning/cost-tracker.js.map +1 -1
- package/dist/learning/model-pricing.d.ts +240 -0
- package/dist/learning/model-pricing.d.ts.map +1 -0
- package/dist/learning/model-pricing.js +309 -0
- package/dist/learning/model-pricing.js.map +1 -0
- package/dist/learning/model-selection.d.ts +15 -0
- package/dist/learning/model-selection.d.ts.map +1 -1
- package/dist/learning/model-selection.js +22 -2
- package/dist/learning/model-selection.js.map +1 -1
- package/dist/learning/model-warmup.d.ts.map +1 -1
- package/dist/learning/model-warmup.js +4 -1
- package/dist/learning/model-warmup.js.map +1 -1
- package/dist/learning/session-store.d.ts +1 -5
- package/dist/learning/session-store.d.ts.map +1 -1
- package/dist/learning/session-store.js +10 -1
- package/dist/learning/session-store.js.map +1 -1
- package/dist/learning/step-checkpoint.d.ts +32 -3
- package/dist/learning/step-checkpoint.d.ts.map +1 -1
- package/dist/learning/step-checkpoint.js +33 -4
- package/dist/learning/step-checkpoint.js.map +1 -1
- package/dist/mcp/settings.d.ts +15 -3
- package/dist/mcp/settings.d.ts.map +1 -1
- package/dist/mcp/settings.js +30 -15
- package/dist/mcp/settings.js.map +1 -1
- package/dist/parity/drivers.d.ts.map +1 -1
- package/dist/parity/drivers.js +31 -5
- package/dist/parity/drivers.js.map +1 -1
- package/dist/tools/child-agent-runtime.d.ts.map +1 -1
- package/dist/tools/child-agent-runtime.js +13 -7
- package/dist/tools/child-agent-runtime.js.map +1 -1
- package/dist/tools/followup-utils.d.ts +36 -0
- package/dist/tools/followup-utils.d.ts.map +1 -1
- package/dist/tools/followup-utils.js +66 -0
- package/dist/tools/followup-utils.js.map +1 -1
- package/dist/tools/modality/browser.js +2 -2
- package/dist/tools/modality/browser.js.map +1 -1
- package/dist/tools/modality/image-gen.d.ts +1 -1
- package/dist/tools/modality/image-gen.js +1 -1
- package/dist/tools/modality/image-providers.js +1 -1
- package/dist/tools/modality/image-providers.js.map +1 -1
- package/dist/tools/modality/vision.d.ts +1 -1
- package/dist/tools/modality/vision.js +1 -1
- package/dist/tools/modality/voice.js +2 -2
- package/dist/tools/modality/voice.js.map +1 -1
- package/dist/tools/neutts-synth.d.ts +3 -3
- package/dist/tools/neutts-synth.js +3 -3
- package/dist/tools/neutts-synth.js.map +1 -1
- package/dist/tools/process-liveness.d.ts +50 -0
- package/dist/tools/process-liveness.d.ts.map +1 -1
- package/dist/tools/process-liveness.js +82 -9
- package/dist/tools/process-liveness.js.map +1 -1
- package/dist/tools/registry.d.ts +1 -1
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +27 -4
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/run-registry.d.ts.map +1 -1
- package/dist/tools/run-registry.js +14 -1
- package/dist/tools/run-registry.js.map +1 -1
- package/dist/tools/run-terminal.d.ts.map +1 -1
- package/dist/tools/run-terminal.js +18 -1
- package/dist/tools/run-terminal.js.map +1 -1
- package/dist/tools/tool-loop.d.ts.map +1 -1
- package/dist/tools/tool-loop.js +5 -3
- package/dist/tools/tool-loop.js.map +1 -1
- package/dist/tools/web-research.d.ts +1 -1
- package/dist/tools/web-research.js +3 -3
- package/dist/tools/web-research.js.map +1 -1
- package/dist/web-dashboard/chat-console.d.ts +56 -0
- package/dist/web-dashboard/chat-console.d.ts.map +1 -1
- package/dist/web-dashboard/chat-console.js +57 -1
- package/dist/web-dashboard/chat-console.js.map +1 -1
- package/dist/web-dashboard/server.d.ts.map +1 -1
- package/dist/web-dashboard/server.js +122 -2
- package/dist/web-dashboard/server.js.map +1 -1
- package/dist/web-dashboard/src/types.d.ts +63 -0
- package/dist/web-dashboard/src/types.d.ts.map +1 -1
- package/dist/web-dashboard/task-runner.d.ts +17 -1
- package/dist/web-dashboard/task-runner.d.ts.map +1 -1
- package/dist/web-dashboard/task-runner.js +84 -9
- package/dist/web-dashboard/task-runner.js.map +1 -1
- package/package.json +1 -1
- package/src/web-dashboard/public/assets/index-DeSsNU94.css +1 -0
- package/src/web-dashboard/public/assets/index-Do_O67Kv.js +236 -0
- package/src/web-dashboard/public/assets/index-Do_O67Kv.js.map +1 -0
- package/src/web-dashboard/public/index.html +2 -2
- package/src/web-dashboard/public/assets/index-DycGBWwy.js +0 -236
- package/src/web-dashboard/public/assets/index-DycGBWwy.js.map +0 -1
- package/src/web-dashboard/public/assets/index-aO3aG7ex.css +0 -1
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cost-ledger maintenance — the EXPLICIT, dry-run-default way to repair the rows
|
|
3
|
+
* already on disk. Nothing here runs on a read: the ledger is repaired only when
|
|
4
|
+
* an operator asks for it, and every figure it changes is reported before and
|
|
5
|
+
* after.
|
|
6
|
+
*
|
|
7
|
+
* WHY IT EXISTS. Two classes of row in a real profile are not what they look like,
|
|
8
|
+
* and neither can be fixed by a read:
|
|
9
|
+
*
|
|
10
|
+
* 1. LEGACY PRICES. Rows written before `costSource` existed carry a `costUsd`
|
|
11
|
+
* computed from the retired hardcoded table (`cost-tracker.ts`'s five-entry
|
|
12
|
+
* `DEFAULT_PRICING` plus a `$0.00010/1K` generic fallback). Read-side
|
|
13
|
+
* classification already re-derives them for DISPLAY (`cost-classification.ts`
|
|
14
|
+
* counts them in `recomputedCalls`), and deliberately never rewrites the file
|
|
15
|
+
* — but `recordedTotalUsd` ("the ledger total as recorded") keeps showing the
|
|
16
|
+
* old number, and a reprice is what turns the file itself honest.
|
|
17
|
+
* 2. TEST-ORIGIN ROWS. A row a test run wrote into a real profile. Rows written
|
|
18
|
+
* since `origin` was added say so (`origin: 'test'`); rows from before have no
|
|
19
|
+
* field at all, and the only sound evidence left in them is the model id — a
|
|
20
|
+
* fixture id (`wire-stub-model`: MEASURED 747 rows in a real
|
|
21
|
+
* `~/.nuvira/memory/cost-tracker.json`) could not have come from a real call.
|
|
22
|
+
*
|
|
23
|
+
* ─── WHAT IT REFUSES TO DO (read before changing either rule) ───────────────
|
|
24
|
+
*
|
|
25
|
+
* - It does NOT re-derive a row that already carries `costSource`. That row was
|
|
26
|
+
* written by today's rules under the config in force at the time; re-pricing it
|
|
27
|
+
* with a config that has changed since would rewrite history to match the
|
|
28
|
+
* present — the exact dishonesty this whole change removes.
|
|
29
|
+
* - It does NOT pass a legacy row's stored `costUsd` back in as though the
|
|
30
|
+
* provider had reported it. The stored number is the old estimate; feeding it
|
|
31
|
+
* into `resolveCallCost` as `reportedCostUsd` would LAUNDER an estimate into an
|
|
32
|
+
* exact-looking figure. Only a row already flagged `costReported: true` keeps
|
|
33
|
+
* its number verbatim, because that flag was written by the old code and means
|
|
34
|
+
* the same thing.
|
|
35
|
+
* - It does NOT guess `origin`. A legacy row gets no origin field: we do not know
|
|
36
|
+
* which process wrote it, and stamping `live` would make it unprunable forever.
|
|
37
|
+
* - It NEVER writes without `apply`, and a write always leaves a `.bak` beside the
|
|
38
|
+
* file (the ledger is the user's billing history; a reprice is reversible).
|
|
39
|
+
*/
|
|
40
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
41
|
+
import { dirname } from 'node:path';
|
|
42
|
+
import { costLedgerPath } from './cost-tracker.js';
|
|
43
|
+
import { classifyEntries, getPricingContext, } from './cost-classification.js';
|
|
44
|
+
import { resolveCallCost } from './model-pricing.js';
|
|
45
|
+
import { isFixtureModelId } from './model-selection.js';
|
|
46
|
+
// ─── Row-level rules ────────────────────────────────────────────────────────
|
|
47
|
+
/** True when the row is test-origin by evidence we can actually read. */
|
|
48
|
+
export function testRowVerdict(entry) {
|
|
49
|
+
if (entry.origin === 'test')
|
|
50
|
+
return 'test-origin';
|
|
51
|
+
// An EXPLICIT `live` tag outranks the id heuristic — the same doctrine
|
|
52
|
+
// `telemetryOrigin` applies (a stated fact beats an inferred one). A row the
|
|
53
|
+
// recorder itself marked as real usage is not deleted for looking odd.
|
|
54
|
+
if (entry.origin === 'live')
|
|
55
|
+
return null;
|
|
56
|
+
// A fixture id is evidence from the row itself, and it is the ONLY evidence a
|
|
57
|
+
// legacy row can carry (no `origin` field existed when it was written).
|
|
58
|
+
if (isFixtureModelId(entry.model ?? ''))
|
|
59
|
+
return 'fixture-id';
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Re-derive ONE legacy row. Returns the repaired row, or the SAME object when the
|
|
64
|
+
* row already carries `costSource` (never re-derived — see the header).
|
|
65
|
+
*/
|
|
66
|
+
function repriceRow(entry, ctx) {
|
|
67
|
+
if (entry.costSource !== undefined)
|
|
68
|
+
return entry;
|
|
69
|
+
const reported = entry.costReported === true ? entry.costUsd : undefined;
|
|
70
|
+
const resolved = resolveCallCost({
|
|
71
|
+
provider: entry.provider,
|
|
72
|
+
model: entry.model,
|
|
73
|
+
inputTokens: entry.inputTokens ?? 0,
|
|
74
|
+
outputTokens: entry.outputTokens ?? 0,
|
|
75
|
+
// Only a row ALREADY flagged as provider-reported keeps its figure verbatim.
|
|
76
|
+
...(reported !== undefined ? { reportedCostUsd: reported } : {}),
|
|
77
|
+
billingTier: ctx.billingTierFor(entry.provider),
|
|
78
|
+
configPricing: ctx.configPricingFor(entry.provider),
|
|
79
|
+
catalogRow: ctx.catalogRowFor?.(entry.provider, entry.model),
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
...entry,
|
|
83
|
+
costUsd: resolved.billedUsd,
|
|
84
|
+
costSource: resolved.costSource,
|
|
85
|
+
...(resolved.listUsd !== undefined ? { listPriceUsd: resolved.listUsd } : {}),
|
|
86
|
+
billingTier: resolved.billingTier,
|
|
87
|
+
pricingBasis: resolved.basis,
|
|
88
|
+
// `origin` is deliberately NOT set: unknown is not `live`.
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// ─── The pass ───────────────────────────────────────────────────────────────
|
|
92
|
+
/**
|
|
93
|
+
* Re-price a set of ledger rows and (optionally) drop the test rows among them.
|
|
94
|
+
* Pure: takes rows, returns rows — the file is written by `maintainCostLedger`.
|
|
95
|
+
*/
|
|
96
|
+
export function repriceLedgerEntries(entries, opts = {}) {
|
|
97
|
+
const ctx = opts.ctx ?? getPricingContext();
|
|
98
|
+
const prune = opts.pruneTestRows !== false;
|
|
99
|
+
const kept = [];
|
|
100
|
+
const removedCounts = new Map();
|
|
101
|
+
let repriced = 0;
|
|
102
|
+
let alreadyClassified = 0;
|
|
103
|
+
let testOriginRemoved = 0;
|
|
104
|
+
let fixtureRemoved = 0;
|
|
105
|
+
for (const entry of entries) {
|
|
106
|
+
const verdict = prune ? testRowVerdict(entry) : null;
|
|
107
|
+
if (verdict) {
|
|
108
|
+
if (verdict === 'test-origin')
|
|
109
|
+
testOriginRemoved += 1;
|
|
110
|
+
else
|
|
111
|
+
fixtureRemoved += 1;
|
|
112
|
+
const key = `${entry.provider}\u0000${entry.model}`;
|
|
113
|
+
const existing = removedCounts.get(key);
|
|
114
|
+
if (existing)
|
|
115
|
+
existing.count += 1;
|
|
116
|
+
else
|
|
117
|
+
removedCounts.set(key, {
|
|
118
|
+
provider: entry.provider,
|
|
119
|
+
model: entry.model,
|
|
120
|
+
count: 1,
|
|
121
|
+
fixtureId: verdict === 'fixture-id',
|
|
122
|
+
});
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const repaired = repriceRow(entry, ctx);
|
|
126
|
+
if (repaired !== entry)
|
|
127
|
+
repriced += 1;
|
|
128
|
+
else
|
|
129
|
+
alreadyClassified += 1;
|
|
130
|
+
kept.push(repaired);
|
|
131
|
+
}
|
|
132
|
+
const beforeTotals = classifyEntries(entries, { ctx, excludeTestOrigin: false });
|
|
133
|
+
const afterTotals = classifyEntries(kept, { ctx, excludeTestOrigin: false });
|
|
134
|
+
const sumRecorded = (rows) => Math.round(rows.reduce((s, r) => s + (r.costUsd || 0), 0) * 100000) / 100000;
|
|
135
|
+
const sumTokens = (rows) => rows.reduce((s, r) => s + (r.totalTokens || 0), 0);
|
|
136
|
+
const unpricedCalls = (rows) => classifyEntries(rows, { ctx, excludeTestOrigin: false }).unpricedCalls;
|
|
137
|
+
const removed = [...removedCounts.values()].sort((a, b) => b.count - a.count);
|
|
138
|
+
const notes = [];
|
|
139
|
+
if (repriced > 0) {
|
|
140
|
+
notes.push(`${repriced} legacy row(s) re-priced from the ONE price source (provider catalog + declared model prices + your overrides) — no hardcoded table, no generic fallback.`);
|
|
141
|
+
}
|
|
142
|
+
if (alreadyClassified > 0) {
|
|
143
|
+
notes.push(`${alreadyClassified} row(s) already carried a cost classification and were left exactly as written.`);
|
|
144
|
+
}
|
|
145
|
+
if (testOriginRemoved + fixtureRemoved > 0) {
|
|
146
|
+
notes.push(`${testOriginRemoved} row(s) tagged \`origin: test\` and ${fixtureRemoved} row(s) with a test-fixture model id were removed — a test run is not your usage.`);
|
|
147
|
+
}
|
|
148
|
+
if (afterTotals.unverifiedListedUsd > 0) {
|
|
149
|
+
notes.push(`$${afterTotals.unverifiedListedUsd.toFixed(6)} of list-priced calls are still NOT counted as spend because the billing tier is undeclared: nuvira config set providers.<id>.billingTier paid|free|subscription`);
|
|
150
|
+
}
|
|
151
|
+
if (afterTotals.unpricedCalls > 0) {
|
|
152
|
+
notes.push(`${afterTotals.unpricedCalls} row(s) have no price evidence (an unknown provider, a pass-through gateway, or a model with no price source) — they contribute no dollars, which is honest but incomplete: set pricing.<provider>.inputPer1K/outputPer1K to price them.`);
|
|
153
|
+
}
|
|
154
|
+
if (prune && testOriginRemoved + fixtureRemoved === 0) {
|
|
155
|
+
notes.push('No test-origin rows found — new writes are tagged at record time, so this should stay at zero.');
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
entries: kept,
|
|
159
|
+
report: {
|
|
160
|
+
rows: {
|
|
161
|
+
total: entries.length,
|
|
162
|
+
repriced,
|
|
163
|
+
alreadyClassified,
|
|
164
|
+
testOriginRemoved,
|
|
165
|
+
fixtureRemoved,
|
|
166
|
+
kept: kept.length,
|
|
167
|
+
},
|
|
168
|
+
before: {
|
|
169
|
+
recordedUsd: sumRecorded(entries),
|
|
170
|
+
tokens: sumTokens(entries),
|
|
171
|
+
unpricedCalls: unpricedCalls(entries),
|
|
172
|
+
},
|
|
173
|
+
after: {
|
|
174
|
+
recordedUsd: sumRecorded(kept),
|
|
175
|
+
tokens: sumTokens(kept),
|
|
176
|
+
billedUsd: afterTotals.billedUsd,
|
|
177
|
+
billedReportedUsd: afterTotals.billedReportedUsd,
|
|
178
|
+
billedListedUsd: afterTotals.billedListedUsd,
|
|
179
|
+
unverifiedListedUsd: afterTotals.unverifiedListedUsd,
|
|
180
|
+
notBilledValueUsd: afterTotals.notBilledValueUsd,
|
|
181
|
+
unpricedCalls: afterTotals.unpricedCalls,
|
|
182
|
+
},
|
|
183
|
+
removed,
|
|
184
|
+
notes,
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
// ─── File-level pass ────────────────────────────────────────────────────────
|
|
189
|
+
/** Read the ledger file, tolerating absence and corruption the way the tracker does. */
|
|
190
|
+
function readLedger(file) {
|
|
191
|
+
try {
|
|
192
|
+
if (!existsSync(file))
|
|
193
|
+
return { entries: [], version: 1 };
|
|
194
|
+
const parsed = JSON.parse(readFileSync(file, 'utf-8'));
|
|
195
|
+
return { entries: Array.isArray(parsed?.entries) ? parsed.entries : [], version: parsed?.version ?? 1 };
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return { entries: [], version: 1 };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* The whole pass against a FILE. `apply: false` (the default) reads and reports
|
|
203
|
+
* only — the dry run is the default because the ledger is billing history.
|
|
204
|
+
*
|
|
205
|
+
* On `apply` the previous file is copied to `<file>.bak` first (a fixed name, so
|
|
206
|
+
* the backup is the state immediately before THIS run and cannot itself grow a
|
|
207
|
+
* chain), and the rewrite goes through a temp-free single write.
|
|
208
|
+
*/
|
|
209
|
+
export function maintainCostLedger(opts = {}) {
|
|
210
|
+
const file = opts.file ?? costLedgerPath();
|
|
211
|
+
const ledger = readLedger(file);
|
|
212
|
+
const { entries, report } = repriceLedgerEntries(ledger.entries, {
|
|
213
|
+
ctx: opts.ctx,
|
|
214
|
+
pruneTestRows: opts.pruneTestRows,
|
|
215
|
+
});
|
|
216
|
+
let backupPath;
|
|
217
|
+
if (opts.apply) {
|
|
218
|
+
try {
|
|
219
|
+
const dir = dirname(file);
|
|
220
|
+
if (!existsSync(dir))
|
|
221
|
+
mkdirSync(dir, { recursive: true });
|
|
222
|
+
if (existsSync(file)) {
|
|
223
|
+
backupPath = `${file}.bak`;
|
|
224
|
+
copyFileSync(file, backupPath);
|
|
225
|
+
}
|
|
226
|
+
writeFileSync(file, JSON.stringify({ version: ledger.version, entries }, null, 2), 'utf-8');
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
// A failed write is reported, never silently swallowed: the operator asked
|
|
230
|
+
// for a repair and must know it did not happen.
|
|
231
|
+
return {
|
|
232
|
+
file,
|
|
233
|
+
applied: false,
|
|
234
|
+
...report,
|
|
235
|
+
notes: [...report.notes, `✗ write failed: ${err.message} — the ledger is unchanged.`],
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return { file, applied: opts.apply === true, ...(backupPath ? { backupPath } : {}), ...report };
|
|
240
|
+
}
|
|
241
|
+
/** The report as text — shared by the CLI and the scripts wrapper. */
|
|
242
|
+
export function formatMaintenanceReport(rep) {
|
|
243
|
+
const usd = (n) => `$${n.toFixed(6)}`;
|
|
244
|
+
const lines = [
|
|
245
|
+
rep.applied ? '🛠️ Cost Ledger Repriced (written)' : '🛠️ Cost Ledger Reprice — DRY RUN (nothing written)',
|
|
246
|
+
'',
|
|
247
|
+
` File: ${rep.file}${rep.backupPath ? `\n Backup: ${rep.backupPath}` : ''}`,
|
|
248
|
+
'',
|
|
249
|
+
'── Rows ──',
|
|
250
|
+
` total: ${rep.rows.total}`,
|
|
251
|
+
` re-priced: ${rep.rows.repriced} (legacy rows re-derived by today's rules)`,
|
|
252
|
+
` already classified: ${rep.rows.alreadyClassified} (left exactly as written)`,
|
|
253
|
+
` removed (origin): ${rep.rows.testOriginRemoved} (tagged origin: test)`,
|
|
254
|
+
` removed (fixture): ${rep.rows.fixtureRemoved} (model id could only be a fixture)`,
|
|
255
|
+
` kept: ${rep.rows.kept}`,
|
|
256
|
+
'',
|
|
257
|
+
'── Recorded total ──',
|
|
258
|
+
` before: ${usd(rep.before.recordedUsd)} (${rep.before.tokens} tokens, ${rep.before.unpricedCalls} unpriced calls)`,
|
|
259
|
+
` after: ${usd(rep.after.recordedUsd)} (${rep.after.tokens} tokens, ${rep.after.unpricedCalls} unpriced calls)`,
|
|
260
|
+
'',
|
|
261
|
+
'── After re-pricing, by evidence ──',
|
|
262
|
+
` 🧾 provider-reported (exact): ${usd(rep.after.billedReportedUsd)}`,
|
|
263
|
+
` 📋 list-priced, account paid: ${usd(rep.after.billedListedUsd)}`,
|
|
264
|
+
` = billed: ${usd(rep.after.billedUsd)}`,
|
|
265
|
+
` ❓ list-priced, tier undeclared: ${usd(rep.after.unverifiedListedUsd)} (not counted as spend)`,
|
|
266
|
+
` 🎁 free/subscription value at list: ${usd(rep.after.notBilledValueUsd)}`,
|
|
267
|
+
'',
|
|
268
|
+
];
|
|
269
|
+
if (rep.removed.length > 0) {
|
|
270
|
+
lines.push('── Rows that would be removed ──');
|
|
271
|
+
for (const r of rep.removed.slice(0, 20)) {
|
|
272
|
+
lines.push(` ${String(r.count).padStart(6)} × ${(r.provider + '/' + r.model).slice(0, 60)}${r.fixtureId ? ' (fixture id)' : ' (origin: test)'}`);
|
|
273
|
+
}
|
|
274
|
+
if (rep.removed.length > 20)
|
|
275
|
+
lines.push(` … and ${rep.removed.length - 20} more pair(s)`);
|
|
276
|
+
lines.push('');
|
|
277
|
+
}
|
|
278
|
+
if (rep.notes.length > 0) {
|
|
279
|
+
lines.push('── Notes ──');
|
|
280
|
+
for (const n of rep.notes)
|
|
281
|
+
lines.push(` • ${n}`);
|
|
282
|
+
lines.push('');
|
|
283
|
+
}
|
|
284
|
+
if (!rep.applied) {
|
|
285
|
+
lines.push(' Nothing was written. Re-run with --apply to write (a .bak is kept).', '');
|
|
286
|
+
}
|
|
287
|
+
return lines.join('\n');
|
|
288
|
+
}
|
|
289
|
+
//# sourceMappingURL=cost-ledger-maintenance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cost-ledger-maintenance.js","sourceRoot":"","sources":["../../src/learning/cost-ledger-maintenance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cost reconciliation — the ONLY way the word "actual" can be earned.
|
|
3
|
+
*
|
|
4
|
+
* WHAT THIS REPLACES. Every figure the ledger reports is, at best, an ESTIMATE of
|
|
5
|
+
* money owed: a provider-reported per-call cost is exact about the call, and a
|
|
6
|
+
* list price on a declared-`paid` account is a guess at the bill. Neither is the
|
|
7
|
+
* bill. The ledger cannot see one, so the honest headline is "billed by evidence
|
|
8
|
+
* — $X" and never "you paid $X". This module closes that gap the only way it can
|
|
9
|
+
* be closed: the operator reads the provider's OWN number off the invoice and
|
|
10
|
+
* enters it, and everything else here is arithmetic on two figures that came from
|
|
11
|
+
* outside this machine.
|
|
12
|
+
*
|
|
13
|
+
* WHAT IT IS NOT. It never guesses an invoice, never scales one, and never
|
|
14
|
+
* "adjusts" the ledger to match. It reports the difference and what the
|
|
15
|
+
* difference most likely consists of — unpriced calls, calls whose list price was
|
|
16
|
+
* never claimed as spend (an undeclared billing tier), or rows the ledger is
|
|
17
|
+
* missing.
|
|
18
|
+
*
|
|
19
|
+
* ─── THE BILLING FACT, EARNED (read before changing tierEvidence) ───────────
|
|
20
|
+
*
|
|
21
|
+
* `providers.<id>.billingTier` decides whether a list-priced call is money owed
|
|
22
|
+
* (`paid`) or value absorbed (`free`/`subscription`), and it is the single fact
|
|
23
|
+
* that moves the headline the most. Being user-declared, it is an assumption —
|
|
24
|
+
* which is exactly what this change is here to remove. An invoice is evidence,
|
|
25
|
+
* so this module states what the invoice PROVES, and the CLI refuses to record a
|
|
26
|
+
* tier the invoice contradicts:
|
|
27
|
+
*
|
|
28
|
+
* - $0 charged across N calls in the window, and nothing billed by evidence
|
|
29
|
+
* → the key is not being charged per call: `free` is EVIDENCED.
|
|
30
|
+
* - A charge that matches the per-call costs the provider itself reported
|
|
31
|
+
* → the key is metered and billed: `paid` is EVIDENCED.
|
|
32
|
+
* - Anything else (a flat fee, a discounted charge, a mismatch) proves only
|
|
33
|
+
* what it says: the key IS charged. `subscription` and `paid` cannot be told
|
|
34
|
+
* apart from one invoice line, so no tier is claimed — the operator picks,
|
|
35
|
+
* and the CLI says why it is asking rather than picking for them.
|
|
36
|
+
*
|
|
37
|
+
* A period that has not closed yet must not be reconciled against (`--days` on a
|
|
38
|
+
* running month reads a partial invoice). The window is always printed with the
|
|
39
|
+
* result so the comparison can be judged, and `--until` exists to pin it.
|
|
40
|
+
*/
|
|
41
|
+
import { type ClassifiableEntry, type PricingContext } from './cost-classification.js';
|
|
42
|
+
import type { BillingTier } from './model-pricing.js';
|
|
43
|
+
/**
|
|
44
|
+
* What the operator read off the provider's bill. Every field is REQUIRED except
|
|
45
|
+
* `note`: a window is not optional, because comparing an all-time ledger to a
|
|
46
|
+
* monthly invoice is a meaningless subtraction dressed up as a number.
|
|
47
|
+
*/
|
|
48
|
+
export interface InvoiceClaim {
|
|
49
|
+
/** The provider id the invoice line belongs to (e.g. `deepseek`). */
|
|
50
|
+
provider: string;
|
|
51
|
+
/** The provider's own figure for the window, USD. */
|
|
52
|
+
invoiceUsd: number;
|
|
53
|
+
/** Inclusive window start (epoch ms) — normally the billing period's start. */
|
|
54
|
+
since: number;
|
|
55
|
+
/**
|
|
56
|
+
* EXCLUSIVE window end (epoch ms) — normally the billing period's end. Half-open
|
|
57
|
+
* so consecutive periods cannot count a boundary row twice.
|
|
58
|
+
*
|
|
59
|
+
* A caller whose end is a LIVE "now" must pass `now + 1`, not `now`: a ledger
|
|
60
|
+
* row's timestamp is `Date.now()` and so is the caller's, in the same
|
|
61
|
+
* millisecond resolution, so an exclusive `now` silently drops whatever was
|
|
62
|
+
* recorded in the current millisecond (MEASURED — the CLI's default window did
|
|
63
|
+
* exactly this and reported "no ledger rows for <provider> in this window").
|
|
64
|
+
*/
|
|
65
|
+
until: number;
|
|
66
|
+
/** Where the number came from, printed verbatim (e.g. "dashboard, Oct 2026"). */
|
|
67
|
+
note?: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* How the invoice and the ledger relate. Three values, because "off by $0.40"
|
|
71
|
+
* and "off by $6.30 in the same direction" are different claims with different
|
|
72
|
+
* remedies, and a boolean would flatten them.
|
|
73
|
+
*/
|
|
74
|
+
export type ReconciliationVerdict = 'match' | 'under-evidenced' | 'over-recorded';
|
|
75
|
+
export interface CostReconciliation {
|
|
76
|
+
provider: string;
|
|
77
|
+
since: number;
|
|
78
|
+
until: number;
|
|
79
|
+
/** The provider's own figure — the ONE number here that is not ours. */
|
|
80
|
+
invoiceUsd: number;
|
|
81
|
+
note?: string;
|
|
82
|
+
/** Ledger rows for this provider inside the window (test-origin excluded). */
|
|
83
|
+
requests: number;
|
|
84
|
+
tokens: number;
|
|
85
|
+
/** Evidence-backed bill: provider-reported + list-priced with a declared paid account. */
|
|
86
|
+
billedUsd: number;
|
|
87
|
+
/** The exact part (provider stated each call's charge). */
|
|
88
|
+
billedReportedUsd: number;
|
|
89
|
+
/** The estimated part (list price, account declared `paid`). */
|
|
90
|
+
billedListedUsd: number;
|
|
91
|
+
/** List-priced but the tier is undeclared: real tokens, no claim of spend. */
|
|
92
|
+
unverifiedListedUsd: number;
|
|
93
|
+
/** List price of calls a free/subscription tier absorbed — never spend. */
|
|
94
|
+
notBilledValueUsd: number;
|
|
95
|
+
/** Calls with no price evidence at all (unknown provider or pass-through gateway). */
|
|
96
|
+
unpricedCalls: number;
|
|
97
|
+
/** Test-origin rows in the window — excluded from every figure above. */
|
|
98
|
+
testOriginCalls: number;
|
|
99
|
+
/** `invoiceUsd - billedUsd`. Positive = the provider charged more than we can evidence. */
|
|
100
|
+
deltaUsd: number;
|
|
101
|
+
/** The band inside which the two are called equal (see `toleranceFor`). */
|
|
102
|
+
toleranceUsd: number;
|
|
103
|
+
verdict: ReconciliationVerdict;
|
|
104
|
+
/** One line naming exactly what was compared. Always printed. */
|
|
105
|
+
basis: string;
|
|
106
|
+
/** What the invoice PROVES about the key's billing tier, when it proves it. */
|
|
107
|
+
tierEvidence?: {
|
|
108
|
+
tier: BillingTier;
|
|
109
|
+
basis: string;
|
|
110
|
+
};
|
|
111
|
+
/** What could account for a non-zero delta — the actionable part. */
|
|
112
|
+
hints: string[];
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Reconcile a provider's invoice against the ledger rows in the same window.
|
|
116
|
+
*
|
|
117
|
+
* Pure with respect to the ledger: it reads rows and returns a report; nothing is
|
|
118
|
+
* written and no row is modified. `provider` matching is exact — a per-provider
|
|
119
|
+
* invoice must never be compared against a whole-profile total.
|
|
120
|
+
*
|
|
121
|
+
* Test-origin rows are excluded from EVERY figure (a fixture call is not a charge)
|
|
122
|
+
* and counted in `testOriginCalls`, so a window can be checked for the rows that
|
|
123
|
+
* were hidden rather than silently shrinking.
|
|
124
|
+
*/
|
|
125
|
+
export declare function reconcileEntries(claim: InvoiceClaim, entries: readonly ClassifiableEntry[], ctx?: PricingContext): CostReconciliation;
|
|
126
|
+
/**
|
|
127
|
+
* A reconciliation as stored. `recordedAt` is when the operator entered the
|
|
128
|
+
* invoice figure — a reconciliation is a statement about a moment, and a stored
|
|
129
|
+
* one without a date invites being read as current.
|
|
130
|
+
*/
|
|
131
|
+
export interface StoredReconciliation extends CostReconciliation {
|
|
132
|
+
recordedAt: number;
|
|
133
|
+
}
|
|
134
|
+
/** Where the reconciliation records live, resolved lazily (see cost-tracker). */
|
|
135
|
+
export declare function reconciliationLedgerPath(): string;
|
|
136
|
+
/**
|
|
137
|
+
* Every recorded reconciliation, newest first. Never throws — a corrupt file
|
|
138
|
+
* reads as empty, because this is display/audit data and a broken history must
|
|
139
|
+
* not take down the cost page.
|
|
140
|
+
*/
|
|
141
|
+
export declare function readReconciliations(opts?: {
|
|
142
|
+
file?: string;
|
|
143
|
+
}): StoredReconciliation[];
|
|
144
|
+
/**
|
|
145
|
+
* Persist a reconciliation. Appending rather than replacing on purpose: the
|
|
146
|
+
* HISTORY is the audit trail ("this was checked against the October invoice, and
|
|
147
|
+
* the delta was 3 cents"), and a later check must not erase what an earlier one
|
|
148
|
+
* established.
|
|
149
|
+
*/
|
|
150
|
+
export declare function recordReconciliation(rec: CostReconciliation, opts?: {
|
|
151
|
+
file?: string;
|
|
152
|
+
at?: number;
|
|
153
|
+
}): StoredReconciliation;
|
|
154
|
+
/** The most recent record for a provider, or undefined. */
|
|
155
|
+
export declare function lastReconciliationFor(provider: string, opts?: {
|
|
156
|
+
file?: string;
|
|
157
|
+
}): StoredReconciliation | undefined;
|
|
158
|
+
/** The reconciliation, as text — shared by the CLI and any surface that prints it. */
|
|
159
|
+
export declare function formatReconciliation(rec: StoredReconciliation | CostReconciliation): string;
|
|
160
|
+
//# sourceMappingURL=cost-reconcile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cost-reconcile.d.ts","sourceRoot":"","sources":["../../src/learning/cost-reconcile.ts"],"names":[],"mappings":"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"}
|