@itookit/dsht 0.3.0 → 0.3.3
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.i18n.yaml +2 -2
- package/README.md +39 -23
- package/README.zh.md +40 -24
- package/dist/catalog/controller.d.ts +32 -0
- package/dist/catalog/controller.js +88 -0
- package/dist/catalog/index.d.ts +2 -0
- package/dist/catalog/index.js +2 -0
- package/dist/{cli.js → cli/index.js} +42 -29
- package/dist/controller/connection.d.ts +80 -0
- package/dist/controller/connection.js +190 -0
- package/dist/controller/controller.d.ts +269 -0
- package/dist/controller/controller.js +372 -0
- package/dist/controller/index.d.ts +5 -0
- package/dist/controller/index.js +3 -0
- package/dist/controller/memory-log.d.ts +35 -0
- package/dist/controller/memory-log.js +95 -0
- package/dist/cost/config.d.ts +17 -0
- package/dist/cost/config.js +68 -0
- package/dist/cost/controller.d.ts +41 -0
- package/dist/cost/controller.js +115 -0
- package/dist/cost/index.d.ts +10 -0
- package/dist/cost/index.js +8 -0
- package/dist/cost/ledger-files.d.ts +16 -0
- package/dist/cost/ledger-files.js +103 -0
- package/dist/cost/ledger.d.ts +78 -0
- package/dist/cost/ledger.js +146 -0
- package/dist/cost/pricing.d.ts +86 -0
- package/dist/cost/pricing.js +223 -0
- package/dist/cost/records.d.ts +17 -0
- package/dist/cost/records.js +79 -0
- package/dist/cost/scanner.d.ts +22 -0
- package/dist/cost/scanner.js +95 -0
- package/dist/cost/types.d.ts +85 -0
- package/dist/cost/types.js +7 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/session/connection-view.d.ts +18 -0
- package/dist/session/connection-view.js +1 -0
- package/dist/{controller.d.ts → session/controller.d.ts} +102 -136
- package/dist/session/controller.js +616 -0
- package/dist/session/export-html.d.ts +9 -0
- package/dist/session/export-html.js +39 -0
- package/dist/{export.d.ts → session/export.d.ts} +1 -1
- package/dist/{export.js → session/export.js} +6 -20
- package/dist/{history.d.ts → session/history.d.ts} +17 -0
- package/dist/{history.js → session/history.js} +163 -2
- package/dist/session/index.d.ts +17 -0
- package/dist/session/index.js +10 -0
- package/dist/session/markdown.d.ts +39 -0
- package/dist/session/markdown.js +255 -0
- package/dist/session/math.d.ts +11 -0
- package/dist/session/math.js +82 -0
- package/dist/session/navigation.d.ts +37 -0
- package/dist/session/navigation.js +84 -0
- package/dist/{references.js → session/references.js} +1 -1
- package/dist/{telemetry.d.ts → session/telemetry.d.ts} +1 -1
- package/dist/{telemetry.js → session/telemetry.js} +1 -1
- package/dist/{transcript.d.ts → session/transcript.d.ts} +65 -1
- package/dist/{transcript.js → session/transcript.js} +141 -20
- package/dist/session/types.d.ts +18 -0
- package/dist/session/types.js +2 -0
- package/dist/state.d.ts +41 -0
- package/dist/state.js +9 -0
- package/dist/storage/directories.d.ts +14 -0
- package/dist/storage/directories.js +24 -0
- package/dist/storage/files.d.ts +51 -0
- package/dist/storage/files.js +152 -0
- package/dist/storage/heap-snapshot.d.ts +19 -0
- package/dist/storage/heap-snapshot.js +29 -0
- package/dist/storage/index.d.ts +4 -0
- package/dist/storage/index.js +4 -0
- package/dist/transport/auth.js +65 -0
- package/dist/transport/host.d.ts +13 -0
- package/dist/transport/host.js +1 -0
- package/dist/ui/app.d.ts +11 -0
- package/dist/ui/app.js +790 -0
- package/dist/ui/chat/header.d.ts +11 -0
- package/dist/ui/chat/header.js +14 -0
- package/dist/ui/chat/history-view.d.ts +12 -0
- package/dist/ui/chat/history-view.js +17 -0
- package/dist/ui/chat/status.d.ts +91 -0
- package/dist/ui/chat/status.js +386 -0
- package/dist/ui/chat/viewport.d.ts +17 -0
- package/dist/ui/chat/viewport.js +14 -0
- package/dist/ui/commands/parse.d.ts +99 -0
- package/dist/ui/commands/parse.js +126 -0
- package/dist/ui/commands/registry.d.ts +33 -0
- package/dist/ui/commands/registry.js +73 -0
- package/dist/ui/copy-mode.d.ts +4 -0
- package/dist/ui/copy-mode.js +6 -0
- package/dist/{cost-view.d.ts → ui/dialogs/cost.d.ts} +1 -1
- package/dist/{cost-view.js → ui/dialogs/cost.js} +6 -6
- package/dist/ui/dialogs/index.d.ts +120 -0
- package/dist/ui/dialogs/index.js +113 -0
- package/dist/ui/dialogs/picker.d.ts +18 -0
- package/dist/ui/dialogs/picker.js +38 -0
- package/dist/ui/frozen.d.ts +8 -0
- package/dist/ui/frozen.js +7 -0
- package/dist/ui/input/references.d.ts +12 -0
- package/dist/ui/input/references.js +15 -0
- package/dist/ui/mount.d.ts +6 -0
- package/dist/ui/mount.js +11 -0
- package/dist/{theme.d.ts → ui/theme/index.d.ts} +1 -1
- package/package.json +19 -13
- package/dist/app.d.ts +0 -21
- package/dist/app.js +0 -805
- package/dist/auth.js +0 -108
- package/dist/controller.js +0 -961
- package/dist/cost.d.ts +0 -119
- package/dist/cost.js +0 -313
- package/dist/history-view.d.ts +0 -8
- package/dist/history-view.js +0 -12
- package/dist/navigation.d.ts +0 -11
- package/dist/navigation.js +0 -36
- package/dist/status.d.ts +0 -28
- package/dist/status.js +0 -157
- /package/dist/{cli.d.ts → cli/index.d.ts} +0 -0
- /package/dist/{memory.d.ts → session/memory.d.ts} +0 -0
- /package/dist/{memory.js → session/memory.js} +0 -0
- /package/dist/{references.d.ts → session/references.d.ts} +0 -0
- /package/dist/{auth.d.ts → transport/auth.d.ts} +0 -0
- /package/dist/{client.d.ts → transport/client.d.ts} +0 -0
- /package/dist/{client.js → transport/client.js} +0 -0
- /package/dist/{endpoint.d.ts → transport/endpoint.d.ts} +0 -0
- /package/dist/{endpoint.js → transport/endpoint.js} +0 -0
- /package/dist/{wire.d.ts → transport/wire.d.ts} +0 -0
- /package/dist/{wire.js → transport/wire.js} +0 -0
- /package/dist/{input-history.d.ts → ui/input/history.d.ts} +0 -0
- /package/dist/{input-history.js → ui/input/history.js} +0 -0
- /package/dist/{input.d.ts → ui/input/input.d.ts} +0 -0
- /package/dist/{input.js → ui/input/input.js} +0 -0
- /package/dist/{mouse.d.ts → ui/input/mouse.d.ts} +0 -0
- /package/dist/{mouse.js → ui/input/mouse.js} +0 -0
- /package/dist/{theme.js → ui/theme/index.js} +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { chargeFor, costDay, DEFAULT_PRICES, pricesDigest, PRICING_ENGINE_VERSION } from "./pricing.js";
|
|
2
|
+
import { foldSamples } from "./records.js";
|
|
3
|
+
import { loadLedgers, saveLedger } from "./ledger-files.js";
|
|
4
|
+
/** Reasons one slice keeps at most, so a broken table cannot grow the ledger without bound. */
|
|
5
|
+
const UNPRICED_LIMIT = 8;
|
|
6
|
+
/** Per-origin cache of folded session totals; every scan replaces a session at its cut. */
|
|
7
|
+
export class CostLedger {
|
|
8
|
+
prices;
|
|
9
|
+
directory;
|
|
10
|
+
customPrices;
|
|
11
|
+
sessions = new Map();
|
|
12
|
+
totals = new Map();
|
|
13
|
+
catalog;
|
|
14
|
+
scannedAt;
|
|
15
|
+
scanning = false;
|
|
16
|
+
error = '';
|
|
17
|
+
/** Work the last completed scan performed, so a memory sample can attribute its allocation. */
|
|
18
|
+
lastScan;
|
|
19
|
+
constructor(prices = DEFAULT_PRICES, directory,
|
|
20
|
+
/** Whether the table came from a file the user maintains, rather than the shipped one. */
|
|
21
|
+
customPrices = false) {
|
|
22
|
+
this.prices = prices;
|
|
23
|
+
this.directory = directory;
|
|
24
|
+
this.customPrices = customPrices;
|
|
25
|
+
this.catalog = pricesDigest(prices);
|
|
26
|
+
}
|
|
27
|
+
/** Cached totals count as complete; only a failed scan or an empty ledger is partial.
|
|
28
|
+
* @returns Coverage of the current totals, so callers can mark them without re-deriving the rule.
|
|
29
|
+
*/
|
|
30
|
+
get coverage() {
|
|
31
|
+
if (this.scanning)
|
|
32
|
+
return 'scanning';
|
|
33
|
+
if (this.error)
|
|
34
|
+
return 'partial';
|
|
35
|
+
return this.scannedAt !== undefined || this.sessions.size > 0 ? 'complete' : 'partial';
|
|
36
|
+
}
|
|
37
|
+
/** Load the newest cut per session; a stored total is read as it was decided. */
|
|
38
|
+
async load() {
|
|
39
|
+
this.totals.clear();
|
|
40
|
+
this.sessions = await loadLedgers(this.directory);
|
|
41
|
+
}
|
|
42
|
+
/** Replace one session using all billing events through the opening snapshot cut.
|
|
43
|
+
*
|
|
44
|
+
* The fold is the projection: every sample is decided again with the table loaded now, so a
|
|
45
|
+
* corrected table reaches history on the next scan and a request no table covered yet is priced
|
|
46
|
+
* as soon as one does. Only the totals are kept, and the cut and engine keep an older scan from
|
|
47
|
+
* replacing a newer one.
|
|
48
|
+
* @param sessionId - Host session identity.
|
|
49
|
+
* @param cut - Opening cursor, preventing a stale scan from overwriting a newer scan.
|
|
50
|
+
* @param events - Minimal events returned by `costRecords`, across all history pages.
|
|
51
|
+
* @param now - Clock that names the calendar day the day bucket covers.
|
|
52
|
+
*/
|
|
53
|
+
async replace(sessionId, cut, events, now = Date.now()) {
|
|
54
|
+
const current = this.sessions.get(sessionId);
|
|
55
|
+
if ((current?.cut ?? -2) > cut)
|
|
56
|
+
return;
|
|
57
|
+
const day = costDay(now);
|
|
58
|
+
const total = { amount: 0, unknown: 0, records: 0 };
|
|
59
|
+
const today = { day, amount: 0, unknown: 0, records: 0 };
|
|
60
|
+
const unpriced = new Set();
|
|
61
|
+
for (const sample of foldSamples(events)) {
|
|
62
|
+
const decision = chargeFor(this.prices, sample.provider, sample.model, sample.time, sample.usage);
|
|
63
|
+
// A request belongs to the day it settled on, so the day bucket only counts the requests of
|
|
64
|
+
// the calendar day this scan is running on; another day reads as nothing spent today. A request
|
|
65
|
+
// with no settlement time belongs to no day, so it is counted as unknown wherever it is read.
|
|
66
|
+
const buckets = sample.time === undefined || costDay(sample.time) === day ? [total, today] : [total];
|
|
67
|
+
for (const bucket of buckets) {
|
|
68
|
+
bucket.records++;
|
|
69
|
+
if (decision.amount === undefined)
|
|
70
|
+
bucket.unknown++;
|
|
71
|
+
else
|
|
72
|
+
bucket.amount += decision.amount;
|
|
73
|
+
}
|
|
74
|
+
if (decision.amount === undefined && unpriced.size < UNPRICED_LIMIT)
|
|
75
|
+
unpriced.add(`${sample.provider}/${sample.model}: ${decision.reason}`);
|
|
76
|
+
}
|
|
77
|
+
const saved = { version: 3, sessionId, cut, engine: PRICING_ENGINE_VERSION, catalog: this.catalog,
|
|
78
|
+
total, day: today, unpriced: [...unpriced] };
|
|
79
|
+
// Another process may have persisted a newer cut of this session since it was last read.
|
|
80
|
+
if (this.directory && !await saveLedger(this.directory, saved))
|
|
81
|
+
return;
|
|
82
|
+
this.sessions.set(sessionId, saved);
|
|
83
|
+
this.totals.clear();
|
|
84
|
+
}
|
|
85
|
+
/** Count the retained ledger so a memory sample can separate it from the transcript window.
|
|
86
|
+
* @returns Sessions, requests and unpriced requests currently held.
|
|
87
|
+
*/
|
|
88
|
+
summary() {
|
|
89
|
+
let records = 0, unpriced = 0;
|
|
90
|
+
for (const session of this.sessions.values()) {
|
|
91
|
+
records += session.total.records;
|
|
92
|
+
unpriced += session.total.unknown;
|
|
93
|
+
}
|
|
94
|
+
return { sessions: this.sessions.size, records, unpriced };
|
|
95
|
+
}
|
|
96
|
+
/** Whether this session has a complete cached scan.
|
|
97
|
+
* @param sessionId - Selected session identity, which may be unset before one is picked.
|
|
98
|
+
* @returns True when a complete scan is available, narrowing the identity to a string.
|
|
99
|
+
*/
|
|
100
|
+
hasSession(sessionId) { return sessionId !== undefined && this.sessions.has(sessionId); }
|
|
101
|
+
/** Describe unpriced model/usage combinations without exposing conversation content.
|
|
102
|
+
* @returns Unique reasons across cached sessions.
|
|
103
|
+
*/
|
|
104
|
+
missing() { return [...new Set([...this.sessions.values()].flatMap(s => s.unpriced))]; }
|
|
105
|
+
/** One session's stored totals.
|
|
106
|
+
* @param sessionId - Session identity to report.
|
|
107
|
+
* @returns The total a scan folded for it, or zeros when no scan has covered it.
|
|
108
|
+
*/
|
|
109
|
+
total(sessionId) {
|
|
110
|
+
const cached = this.totals.get(`s:${sessionId}`);
|
|
111
|
+
if (cached)
|
|
112
|
+
return cached;
|
|
113
|
+
const session = this.sessions.get(sessionId);
|
|
114
|
+
const result = session === undefined ? { amount: 0, unknown: 0, records: 0 } : { ...session.total };
|
|
115
|
+
this.totals.set(`s:${sessionId}`, result);
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
/** Every session's requests on one Beijing calendar day.
|
|
119
|
+
*
|
|
120
|
+
* A slice keeps only the day its last scan ran on, so another day reads as nothing spent today
|
|
121
|
+
* rather than as the last day that was scanned.
|
|
122
|
+
* @param now - Clock that names the day to report.
|
|
123
|
+
* @returns The day's total across cached sessions.
|
|
124
|
+
*/
|
|
125
|
+
today(now = Date.now()) {
|
|
126
|
+
const day = costDay(now);
|
|
127
|
+
const cached = this.totals.get(`d:${day}`);
|
|
128
|
+
if (cached)
|
|
129
|
+
return cached;
|
|
130
|
+
const result = { amount: 0, unknown: 0, records: 0 };
|
|
131
|
+
for (const session of this.sessions.values()) {
|
|
132
|
+
if (session.day.day !== day)
|
|
133
|
+
continue;
|
|
134
|
+
result.amount += session.day.amount;
|
|
135
|
+
result.unknown += session.day.unknown;
|
|
136
|
+
result.records += session.day.records;
|
|
137
|
+
}
|
|
138
|
+
this.totals.set(`d:${day}`, result);
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** Compact estimates retain an asterisk whenever a subtotal is not exact.
|
|
143
|
+
* @param total - Summary from the ledger.
|
|
144
|
+
* @returns Yuan amount and incompleteness marker.
|
|
145
|
+
*/
|
|
146
|
+
export function costText(total) { return `~¥${total.amount.toFixed(4)}${total.unknown ? '*' : ''}`; }
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { type PriceDecision, type PriceVersion, type Rates, type Usage } from './types.ts';
|
|
2
|
+
export declare const DEFAULT_PRICES: PriceVersion[];
|
|
3
|
+
/** Revision of the pricing decision rules, recorded with the totals they decided.
|
|
4
|
+
*
|
|
5
|
+
* Bump it whenever the rules change what an amount would be — the matching of a model name, the
|
|
6
|
+
* token buckets an amount covers, or the timestamp it is priced at — so a stored total can be traced
|
|
7
|
+
* to the rules that produced it. Version 1 matched a model by the substring `pro` and priced a
|
|
8
|
+
* request with no settlement time at the cheapest off-peak rate.
|
|
9
|
+
*/
|
|
10
|
+
export declare const PRICING_ENGINE_VERSION = 2;
|
|
11
|
+
/** Revision of the shipped table, recorded beside a seeded file so a correction can replace it. */
|
|
12
|
+
export declare const PRICES_REVISION = "2026-09-12";
|
|
13
|
+
/** Whether a table is the seed an earlier revision wrote, which a corrected ship must replace.
|
|
14
|
+
*
|
|
15
|
+
* `prices.json` overrides the shipped table, so an install seeded before the Flash rates were
|
|
16
|
+
* corrected keeps charging 1.5x for input and 2.5x for cache reads for as long as that file lives.
|
|
17
|
+
* Only an exact match to the superseded revision qualifies, so a rate the user chose is never rewritten.
|
|
18
|
+
* @param prices - Table loaded from the configuration file.
|
|
19
|
+
* @returns True when every entry carries the superseded revision's rates.
|
|
20
|
+
*/
|
|
21
|
+
export declare function isUncorrectedSeed(prices: readonly PriceVersion[]): boolean;
|
|
22
|
+
/** Validate user-maintained price versions, rejecting ambiguous overlapping intervals.
|
|
23
|
+
* @param value - Parsed prices.json array.
|
|
24
|
+
* @returns Price versions with validated rates and schedules.
|
|
25
|
+
*/
|
|
26
|
+
export declare function pricesFrom(value: unknown): PriceVersion[];
|
|
27
|
+
/** Return the Beijing calendar date a request is attributed to.
|
|
28
|
+
* @param time - Epoch milliseconds.
|
|
29
|
+
* @returns Beijing calendar date, YYYY-MM-DD.
|
|
30
|
+
*/
|
|
31
|
+
export declare function costDay(time: number): string;
|
|
32
|
+
/** Canonical form of a model name before matching.
|
|
33
|
+
*
|
|
34
|
+
* The host reports names that differ from the published table by width, case, surrounding space, or
|
|
35
|
+
* the CJK full stop a display path can substitute for a period. Normalizing here keeps the table
|
|
36
|
+
* readable and keeps a name variant from silently missing its entry. NFKC does not fold the CJK
|
|
37
|
+
* stops, so they are mapped explicitly.
|
|
38
|
+
* @param model - Model name exactly as the recorded request reported it.
|
|
39
|
+
* @returns The name in the form the table is matched against.
|
|
40
|
+
*/
|
|
41
|
+
export declare function canonicalModel(model: string): string;
|
|
42
|
+
/** Candidate versions for one request, in the order the table is searched: the exact model, then
|
|
43
|
+
* the aliases a version declares. A name the table does not cover stays unpriced rather than
|
|
44
|
+
* falling back to a family guess, because guessing a rate is indistinguishable from a wrong one.
|
|
45
|
+
* @param prices - Validated versions.
|
|
46
|
+
* @param provider - Provider identity from the recorded request.
|
|
47
|
+
* @param model - Recorded model name.
|
|
48
|
+
* @returns Matching versions, each with the rule that matched it.
|
|
49
|
+
*/
|
|
50
|
+
export declare function candidates(prices: PriceVersion[], provider: string, model: string): {
|
|
51
|
+
price: PriceVersion;
|
|
52
|
+
matchedBy: 'exact' | 'alias';
|
|
53
|
+
}[];
|
|
54
|
+
/** Stable identity of a loaded price table.
|
|
55
|
+
*
|
|
56
|
+
* The identity a price version carries can be edited in place while keeping its `id` — which is how
|
|
57
|
+
* a corrected table once kept charging superseded rates under one id — so a stored total records a
|
|
58
|
+
* digest of the whole table it was decided from, not only the version names.
|
|
59
|
+
* @param prices - Price versions loaded for this process.
|
|
60
|
+
* @returns Short digest of the table.
|
|
61
|
+
*/
|
|
62
|
+
export declare function pricesDigest(prices: readonly PriceVersion[]): string;
|
|
63
|
+
/** Select a price by event time, applying half-open local peak windows.
|
|
64
|
+
* @param prices - Validated versions.
|
|
65
|
+
* @param provider - Provider identity from the recorded request.
|
|
66
|
+
* @param model - Recorded model name.
|
|
67
|
+
* @param time - Recorded settlement timestamp used as the billing instant.
|
|
68
|
+
* @returns Matching price version, the rule that matched it, and its per-million-token rates.
|
|
69
|
+
*/
|
|
70
|
+
export declare function priceAt(prices: PriceVersion[], provider: string, model: string, time: number): {
|
|
71
|
+
price: PriceVersion;
|
|
72
|
+
rates: Rates;
|
|
73
|
+
matchedBy: 'exact' | 'alias';
|
|
74
|
+
} | undefined;
|
|
75
|
+
/** Decide the amount for one request sample using the table loaded at decision time.
|
|
76
|
+
*
|
|
77
|
+
* A decision is a number or a reason; nothing about the rates that produced it is kept, because the
|
|
78
|
+
* ledger stores totals rather than requests and the next scan decides the sample again.
|
|
79
|
+
* @param prices - Validated versions currently loaded.
|
|
80
|
+
* @param provider - Provider identity from the recorded request.
|
|
81
|
+
* @param model - Recorded model name.
|
|
82
|
+
* @param time - Recorded settlement timestamp, when the host logged one.
|
|
83
|
+
* @param usage - Disjoint token buckets, when the host reported valid counts.
|
|
84
|
+
* @returns The amount, or the reason no amount exists.
|
|
85
|
+
*/
|
|
86
|
+
export declare function chargeFor(prices: PriceVersion[], provider: string, model: string, time: number | undefined, usage: Usage | undefined): PriceDecision;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/** Versioned CNY price tables and the price decision taken for one request sample. */
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { object } from "../transport/wire.js";
|
|
4
|
+
import { MISSING_TIME, MISSING_USAGE, UNSUPPORTED_USAGE } from "./types.js";
|
|
5
|
+
const clocks = new Map();
|
|
6
|
+
/** Published rates verified on 2026-09-12; preceding dates require historical configuration.
|
|
7
|
+
* Flash and Pro are priced independently, and a separate cache write uses the cache-miss input rate.
|
|
8
|
+
*/
|
|
9
|
+
const OFFICIAL_PRICING = 'https://api-docs.deepseek.com/zh-cn/quick_start/pricing/';
|
|
10
|
+
const PEAK_SCHEDULE = { weekdays: [1, 2, 3, 4, 5], windows: [[540, 720], [840, 1080]] };
|
|
11
|
+
const FLASH_RATES = { peak: { input: 2, cacheRead: 0.04, cacheWrite: 2, output: 8 },
|
|
12
|
+
offPeak: { input: 1, cacheRead: 0.02, cacheWrite: 1, output: 4 } };
|
|
13
|
+
const PRO_RATES = { peak: { input: 9, cacheRead: 0.3, cacheWrite: 9, output: 27 },
|
|
14
|
+
offPeak: { input: 4.5, cacheRead: 0.15, cacheWrite: 4.5, output: 13.5 } };
|
|
15
|
+
export const DEFAULT_PRICES = [
|
|
16
|
+
// The published table states that superseded Flash names stay callable and are served by
|
|
17
|
+
// V4.1-Flash at Flash rates, so every name the host can report is listed instead of guessed at.
|
|
18
|
+
{ id: 'deepseek-2026-09-10-flash', provider: 'deepseek-official', model: 'deepseek-flash',
|
|
19
|
+
aliases: ['deepseek-v4-flash', 'deepseek-v4-flash-vision-exp', 'deepseek-v4-flash*', 'deepseek-v4.1-flash*', 'deepseek-v4.1-flash'],
|
|
20
|
+
from: '2026-09-10T00:00:00+08:00', currency: 'CNY', source: OFFICIAL_PRICING, timezone: 'Asia/Shanghai',
|
|
21
|
+
...PEAK_SCHEDULE, ...FLASH_RATES },
|
|
22
|
+
// The published table keeps V4 Pro available after 2026-09-14 at these rates, so the interval
|
|
23
|
+
// stays open until a later page names an end.
|
|
24
|
+
{ id: 'deepseek-2026-09-10-pro', provider: 'deepseek-official', model: 'deepseek-v4-pro',
|
|
25
|
+
// No `deepseek-pro*`: a prefix that broad would also claim `deepseek-proxy-*` or `deepseek-prompt-*`.
|
|
26
|
+
aliases: ['deepseek-v4-pro', 'deepseek-v4-pro*', 'deepseek-v4.1-pro*'],
|
|
27
|
+
from: '2026-09-10T00:00:00+08:00', currency: 'CNY', source: OFFICIAL_PRICING, timezone: 'Asia/Shanghai',
|
|
28
|
+
...PEAK_SCHEDULE, ...PRO_RATES },
|
|
29
|
+
];
|
|
30
|
+
/** Revision of the pricing decision rules, recorded with the totals they decided.
|
|
31
|
+
*
|
|
32
|
+
* Bump it whenever the rules change what an amount would be — the matching of a model name, the
|
|
33
|
+
* token buckets an amount covers, or the timestamp it is priced at — so a stored total can be traced
|
|
34
|
+
* to the rules that produced it. Version 1 matched a model by the substring `pro` and priced a
|
|
35
|
+
* request with no settlement time at the cheapest off-peak rate.
|
|
36
|
+
*/
|
|
37
|
+
export const PRICING_ENGINE_VERSION = 2;
|
|
38
|
+
/** Revision of the shipped table, recorded beside a seeded file so a correction can replace it. */
|
|
39
|
+
export const PRICES_REVISION = '2026-09-12';
|
|
40
|
+
/** Rates the first published revision charged, rebuilt with the same arithmetic so the values compare equal.
|
|
41
|
+
* It only recognizes that seed; it never prices a request.
|
|
42
|
+
*/
|
|
43
|
+
const UNCORRECTED_SEED_RATES = Object.fromEntries(['deepseek-v4-flash', 'deepseek-v4-pro', 'deepseek-v4-flash-vision-exp'].map(model => {
|
|
44
|
+
const scale = model === 'deepseek-v4-pro' ? 3 : 1;
|
|
45
|
+
return [`deepseek-2026-09-10-${model}`, {
|
|
46
|
+
peak: { input: 3 * scale, cacheRead: 0.1 * scale, cacheWrite: 3 * scale, output: 9 * scale },
|
|
47
|
+
offPeak: { input: 1.5 * scale, cacheRead: 0.05 * scale, cacheWrite: 1.5 * scale, output: 4.5 * scale },
|
|
48
|
+
}];
|
|
49
|
+
}));
|
|
50
|
+
/** Whether a table is the seed an earlier revision wrote, which a corrected ship must replace.
|
|
51
|
+
*
|
|
52
|
+
* `prices.json` overrides the shipped table, so an install seeded before the Flash rates were
|
|
53
|
+
* corrected keeps charging 1.5x for input and 2.5x for cache reads for as long as that file lives.
|
|
54
|
+
* Only an exact match to the superseded revision qualifies, so a rate the user chose is never rewritten.
|
|
55
|
+
* @param prices - Table loaded from the configuration file.
|
|
56
|
+
* @returns True when every entry carries the superseded revision's rates.
|
|
57
|
+
*/
|
|
58
|
+
export function isUncorrectedSeed(prices) {
|
|
59
|
+
const superseded = Object.keys(UNCORRECTED_SEED_RATES);
|
|
60
|
+
return prices.length === superseded.length && prices.every(price => {
|
|
61
|
+
const legacy = UNCORRECTED_SEED_RATES[price.id];
|
|
62
|
+
return legacy !== undefined && sameRates(price.peak, legacy.peak) && sameRates(price.offPeak, legacy.offPeak);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/** Compare two rate sets, allowing the representation error a JSON round trip can introduce.
|
|
66
|
+
* @param a - One rate set.
|
|
67
|
+
* @param b - The other rate set.
|
|
68
|
+
* @returns True when every rate agrees.
|
|
69
|
+
*/
|
|
70
|
+
function sameRates(a, b) {
|
|
71
|
+
return ['input', 'cacheRead', 'cacheWrite', 'output'].every(bucket => Math.abs(a[bucket] - b[bucket]) < 1e-9);
|
|
72
|
+
}
|
|
73
|
+
/** Validate user-maintained price versions, rejecting ambiguous overlapping intervals.
|
|
74
|
+
* @param value - Parsed prices.json array.
|
|
75
|
+
* @returns Price versions with validated rates and schedules.
|
|
76
|
+
*/
|
|
77
|
+
export function pricesFrom(value) {
|
|
78
|
+
if (!Array.isArray(value))
|
|
79
|
+
throw new Error('prices.json must contain an array');
|
|
80
|
+
const ids = new Set();
|
|
81
|
+
for (const raw of value) {
|
|
82
|
+
const p = object(raw);
|
|
83
|
+
for (const key of ['id', 'provider', 'model', 'source', 'timezone', 'from'])
|
|
84
|
+
if (typeof p[key] !== 'string' || !p[key])
|
|
85
|
+
throw new Error(`Invalid price ${key}`);
|
|
86
|
+
if (ids.has(String(p.id)))
|
|
87
|
+
throw new Error('Duplicate price id');
|
|
88
|
+
ids.add(String(p.id));
|
|
89
|
+
if (p.aliases !== undefined && (!Array.isArray(p.aliases) || p.aliases.some(alias => typeof alias !== 'string' || alias === '')))
|
|
90
|
+
throw new Error('Invalid price aliases');
|
|
91
|
+
const from = Date.parse(String(p.from));
|
|
92
|
+
const until = p.until === undefined ? Infinity : Date.parse(String(p.until));
|
|
93
|
+
if (!Number.isFinite(from) || !(until > from) || p.currency !== 'CNY')
|
|
94
|
+
throw new Error('Invalid price interval or currency');
|
|
95
|
+
new Intl.DateTimeFormat('en', { timeZone: String(p.timezone) }).format();
|
|
96
|
+
for (const key of ['peak', 'offPeak'])
|
|
97
|
+
for (const bucket of ['input', 'cacheRead', 'cacheWrite', 'output']) {
|
|
98
|
+
const rate = object(p[key])[bucket];
|
|
99
|
+
if (typeof rate !== 'number' || !Number.isFinite(rate) || rate < 0)
|
|
100
|
+
throw new Error('Invalid token rate');
|
|
101
|
+
}
|
|
102
|
+
if (!Array.isArray(p.weekdays) || p.weekdays.some(d => typeof d !== 'number' || !Number.isInteger(d) || d < 0 || d > 6)
|
|
103
|
+
|| !Array.isArray(p.windows) || p.windows.some(w => !Array.isArray(w) || w.length !== 2 || w.some(n => typeof n !== 'number' || !Number.isInteger(n)) || Number(w[0]) < 0 || Number(w[1]) > 1440 || Number(w[0]) >= Number(w[1])))
|
|
104
|
+
throw new Error('Invalid peak schedule');
|
|
105
|
+
}
|
|
106
|
+
const prices = value;
|
|
107
|
+
for (const [index, p] of prices.entries())
|
|
108
|
+
for (const q of prices.slice(index + 1)) {
|
|
109
|
+
if (p.provider === q.provider && p.model === q.model && Date.parse(p.from) < (q.until ? Date.parse(q.until) : Infinity)
|
|
110
|
+
&& Date.parse(q.from) < (p.until ? Date.parse(p.until) : Infinity))
|
|
111
|
+
throw new Error('Overlapping price intervals');
|
|
112
|
+
}
|
|
113
|
+
return prices;
|
|
114
|
+
}
|
|
115
|
+
/** Return the Beijing calendar date a request is attributed to.
|
|
116
|
+
* @param time - Epoch milliseconds.
|
|
117
|
+
* @returns Beijing calendar date, YYYY-MM-DD.
|
|
118
|
+
*/
|
|
119
|
+
export function costDay(time) { return new Date(time + 8 * 3600_000).toISOString().slice(0, 10); }
|
|
120
|
+
/** Canonical form of a model name before matching.
|
|
121
|
+
*
|
|
122
|
+
* The host reports names that differ from the published table by width, case, surrounding space, or
|
|
123
|
+
* the CJK full stop a display path can substitute for a period. Normalizing here keeps the table
|
|
124
|
+
* readable and keeps a name variant from silently missing its entry. NFKC does not fold the CJK
|
|
125
|
+
* stops, so they are mapped explicitly.
|
|
126
|
+
* @param model - Model name exactly as the recorded request reported it.
|
|
127
|
+
* @returns The name in the form the table is matched against.
|
|
128
|
+
*/
|
|
129
|
+
export function canonicalModel(model) {
|
|
130
|
+
return model.normalize('NFKC').replace(/[\u3002\uff0e\uff61]/g, '.').trim().toLowerCase();
|
|
131
|
+
}
|
|
132
|
+
/** Whether an alias matches a canonical model name, treating a trailing `*` as a prefix.
|
|
133
|
+
* @param alias - Alias declared by a price version.
|
|
134
|
+
* @param model - Canonical model name.
|
|
135
|
+
* @returns True when the alias covers the name.
|
|
136
|
+
*/
|
|
137
|
+
function aliasMatches(alias, model) {
|
|
138
|
+
const canonical = canonicalModel(alias);
|
|
139
|
+
return canonical.endsWith('*') ? model.startsWith(canonical.slice(0, -1)) : model === canonical;
|
|
140
|
+
}
|
|
141
|
+
/** Candidate versions for one request, in the order the table is searched: the exact model, then
|
|
142
|
+
* the aliases a version declares. A name the table does not cover stays unpriced rather than
|
|
143
|
+
* falling back to a family guess, because guessing a rate is indistinguishable from a wrong one.
|
|
144
|
+
* @param prices - Validated versions.
|
|
145
|
+
* @param provider - Provider identity from the recorded request.
|
|
146
|
+
* @param model - Recorded model name.
|
|
147
|
+
* @returns Matching versions, each with the rule that matched it.
|
|
148
|
+
*/
|
|
149
|
+
export function candidates(prices, provider, model) {
|
|
150
|
+
const canonical = canonicalModel(model);
|
|
151
|
+
const exact = prices.filter(p => p.provider === provider && canonicalModel(p.model) === canonical);
|
|
152
|
+
if (exact.length)
|
|
153
|
+
return exact.map(price => ({ price, matchedBy: 'exact' }));
|
|
154
|
+
return prices.filter(p => p.provider === provider && (p.aliases ?? []).some(alias => aliasMatches(alias, canonical)))
|
|
155
|
+
.map(price => ({ price, matchedBy: 'alias' }));
|
|
156
|
+
}
|
|
157
|
+
/** Stable identity of a loaded price table.
|
|
158
|
+
*
|
|
159
|
+
* The identity a price version carries can be edited in place while keeping its `id` — which is how
|
|
160
|
+
* a corrected table once kept charging superseded rates under one id — so a stored total records a
|
|
161
|
+
* digest of the whole table it was decided from, not only the version names.
|
|
162
|
+
* @param prices - Price versions loaded for this process.
|
|
163
|
+
* @returns Short digest of the table.
|
|
164
|
+
*/
|
|
165
|
+
export function pricesDigest(prices) {
|
|
166
|
+
return createHash('sha256').update(JSON.stringify(prices)).digest('hex').slice(0, 12);
|
|
167
|
+
}
|
|
168
|
+
/** Select a price by event time, applying half-open local peak windows.
|
|
169
|
+
* @param prices - Validated versions.
|
|
170
|
+
* @param provider - Provider identity from the recorded request.
|
|
171
|
+
* @param model - Recorded model name.
|
|
172
|
+
* @param time - Recorded settlement timestamp used as the billing instant.
|
|
173
|
+
* @returns Matching price version, the rule that matched it, and its per-million-token rates.
|
|
174
|
+
*/
|
|
175
|
+
export function priceAt(prices, provider, model, time) {
|
|
176
|
+
const candidate = candidates(prices, provider, model)
|
|
177
|
+
.find(({ price }) => Date.parse(price.from) <= time && (price.until === undefined || time < Date.parse(price.until)));
|
|
178
|
+
if (candidate === undefined)
|
|
179
|
+
return;
|
|
180
|
+
const { price, matchedBy } = candidate;
|
|
181
|
+
let clock = clocks.get(price.timezone);
|
|
182
|
+
if (!clock) {
|
|
183
|
+
clock = new Intl.DateTimeFormat('en-US', { timeZone: price.timezone, weekday: 'short', hour: '2-digit', minute: '2-digit', hourCycle: 'h23' });
|
|
184
|
+
clocks.set(price.timezone, clock);
|
|
185
|
+
}
|
|
186
|
+
const parts = clock.formatToParts(time);
|
|
187
|
+
const part = (name) => parts.find(p => p.type === name).value;
|
|
188
|
+
const day = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].indexOf(part('weekday'));
|
|
189
|
+
const minute = Number(part('hour')) * 60 + Number(part('minute'));
|
|
190
|
+
return { price, matchedBy, rates: price.weekdays.includes(day) && price.windows.some(([a, b]) => minute >= a && minute < b) ? price.peak : price.offPeak };
|
|
191
|
+
}
|
|
192
|
+
/** Decide the amount for one request sample using the table loaded at decision time.
|
|
193
|
+
*
|
|
194
|
+
* A decision is a number or a reason; nothing about the rates that produced it is kept, because the
|
|
195
|
+
* ledger stores totals rather than requests and the next scan decides the sample again.
|
|
196
|
+
* @param prices - Validated versions currently loaded.
|
|
197
|
+
* @param provider - Provider identity from the recorded request.
|
|
198
|
+
* @param model - Recorded model name.
|
|
199
|
+
* @param time - Recorded settlement timestamp, when the host logged one.
|
|
200
|
+
* @param usage - Disjoint token buckets, when the host reported valid counts.
|
|
201
|
+
* @returns The amount, or the reason no amount exists.
|
|
202
|
+
*/
|
|
203
|
+
export function chargeFor(prices, provider, model, time, usage) {
|
|
204
|
+
if (!usage)
|
|
205
|
+
return { reason: MISSING_USAGE };
|
|
206
|
+
// The published DeepSeek table prices cache hits, cache misses and output; a fourth bucket means
|
|
207
|
+
// the usage mapping is wrong, and inventing a rate for it would hide that.
|
|
208
|
+
if (provider === 'deepseek-official' && usage.cacheWrite !== 0)
|
|
209
|
+
return { reason: UNSUPPORTED_USAGE };
|
|
210
|
+
// No settlement time means the host did not say when the request was billed, and the two peak
|
|
211
|
+
// bands differ by a factor of two: a floor amount would enter the total while belonging to no day,
|
|
212
|
+
// so the request is reported unresolved instead of guessed.
|
|
213
|
+
if (time === undefined)
|
|
214
|
+
return { reason: MISSING_TIME };
|
|
215
|
+
const selected = priceAt(prices, provider, model, time);
|
|
216
|
+
if (!selected)
|
|
217
|
+
return { reason: 'no price version' };
|
|
218
|
+
const amount = (usage.input * selected.rates.input + usage.output * selected.rates.output
|
|
219
|
+
+ usage.cacheRead * selected.rates.cacheRead + usage.cacheWrite * selected.rates.cacheWrite) / 1e6;
|
|
220
|
+
if (!Number.isFinite(amount))
|
|
221
|
+
return { reason: 'invalid estimate' };
|
|
222
|
+
return { amount };
|
|
223
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Fold host history records into per-request samples; conversation text never enters the ledger. */
|
|
2
|
+
import { type ObjectValue } from '../transport/wire.ts';
|
|
3
|
+
import type { ChargeSample } from './types.ts';
|
|
4
|
+
/** Keep only billing-relevant fields; prompts, tool bodies, cookies and keys never enter the ledger.
|
|
5
|
+
* @param records - One HTTP history page's records.
|
|
6
|
+
* @returns Minimal durable events for a deterministic usage fold.
|
|
7
|
+
*/
|
|
8
|
+
export declare function costRecords(records: unknown): ObjectValue[];
|
|
9
|
+
/** Fold minimal billing events into one sample per model attempt.
|
|
10
|
+
*
|
|
11
|
+
* A replacement sample in the same turn and step updates its attempt's sample, a retry starts a
|
|
12
|
+
* new one, and fork-inherited records are excluded. The sample carries no amount: deciding one
|
|
13
|
+
* belongs to the ledger, which records the decision once.
|
|
14
|
+
* @param events - Minimal events returned by `costRecords`, across all history pages.
|
|
15
|
+
* @returns Ordered samples with the last valid usage observed for each attempt.
|
|
16
|
+
*/
|
|
17
|
+
export declare function foldSamples(events: readonly ObjectValue[]): ChargeSample[];
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** Fold host history records into per-request samples; conversation text never enters the ledger. */
|
|
2
|
+
import { array, object } from "../transport/wire.js";
|
|
3
|
+
/** Host event types that carry billing-relevant usage or route context.
|
|
4
|
+
*
|
|
5
|
+
* `compaction/summary` is a provider request like any other — it reads the whole context to write the
|
|
6
|
+
* summary — and it records its own provider, model and usage instead of appearing as an assistant
|
|
7
|
+
* message, so a fold without it under-reports the most expensive requests in a session.
|
|
8
|
+
*/
|
|
9
|
+
const BILLING_EVENTS = new Set(['request/context', 'assistant/message', 'assistant/attempt', 'llm/retry-started', 'session/end-seed', 'compaction/summary']);
|
|
10
|
+
/** Keep only billing-relevant fields; prompts, tool bodies, cookies and keys never enter the ledger.
|
|
11
|
+
* @param records - One HTTP history page's records.
|
|
12
|
+
* @returns Minimal durable events for a deterministic usage fold.
|
|
13
|
+
*/
|
|
14
|
+
export function costRecords(records) {
|
|
15
|
+
return array(records).map(raw => object(object(raw).event)).filter(e => BILLING_EVENTS.has(String(e.type)))
|
|
16
|
+
// A summary written without a model call — an unmarked template or remote summarizer — has no
|
|
17
|
+
// usage and no cost, so it is not a billable sample at all.
|
|
18
|
+
.filter(e => e.type !== 'compaction/summary' || object(e.data).usage !== undefined).map(e => {
|
|
19
|
+
const d = object(e.data);
|
|
20
|
+
const m = object(d.message ?? {});
|
|
21
|
+
const stream = array(d.stream ?? []).map(r => object(object(r).chunk ?? {})).filter(c => c.type === 'usage');
|
|
22
|
+
// A summary names its own provider and model, and carries no turn or step to fold against, so
|
|
23
|
+
// its route travels as the source a message would carry rather than as folded context.
|
|
24
|
+
const source = e.type === 'compaction/summary' ? { provider: d.provider ?? null, model: d.model ?? null } : m.source ?? null;
|
|
25
|
+
return { seq: e.seq ?? null, time: e.time ?? null, type: e.type, data: {
|
|
26
|
+
inherited: d.inherited ?? false, turn: d.turn ?? null, step: d.step ?? null,
|
|
27
|
+
provider: d.provider ?? null, model: d.model ?? null,
|
|
28
|
+
source, usage: d.usage ?? stream.at(-1)?.usage ?? null,
|
|
29
|
+
} };
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** Fold minimal billing events into one sample per model attempt.
|
|
33
|
+
*
|
|
34
|
+
* A replacement sample in the same turn and step updates its attempt's sample, a retry starts a
|
|
35
|
+
* new one, and fork-inherited records are excluded. The sample carries no amount: deciding one
|
|
36
|
+
* belongs to the ledger, which records the decision once.
|
|
37
|
+
* @param events - Minimal events returned by `costRecords`, across all history pages.
|
|
38
|
+
* @returns Ordered samples with the last valid usage observed for each attempt.
|
|
39
|
+
*/
|
|
40
|
+
export function foldSamples(events) {
|
|
41
|
+
const inheritedCut = Math.max(-1, ...events.filter(e => e.type === 'session/end-seed' && object(e.data).inherited === true).map(e => Number(e.seq)));
|
|
42
|
+
const samples = [];
|
|
43
|
+
let route = {};
|
|
44
|
+
let last;
|
|
45
|
+
for (const e of [...new Map(events.map(e => [Number(e.seq), e])).values()].sort((a, b) => Number(a.seq) - Number(b.seq))) {
|
|
46
|
+
const d = object(e.data);
|
|
47
|
+
if (e.type === 'request/context') {
|
|
48
|
+
route = d;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (Number(e.seq) <= inheritedCut || e.type === 'session/end-seed')
|
|
52
|
+
continue;
|
|
53
|
+
if (e.type === 'llm/retry-started') {
|
|
54
|
+
if (last?.turn === d.turn && last?.step === d.step)
|
|
55
|
+
last = undefined;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
// A message or summary carries its own route; only the remaining events inherit the folded one.
|
|
59
|
+
const source = object(d.source ?? {});
|
|
60
|
+
const provider = String(source.provider ?? route.provider ?? '');
|
|
61
|
+
const model = String(source.model ?? route.model ?? '');
|
|
62
|
+
const time = typeof e.time === 'number' && Number.isFinite(e.time) && e.time >= 0 && e.time <= 8.64e15 ? e.time : undefined;
|
|
63
|
+
const usage = validUsage(d.usage);
|
|
64
|
+
const index = last && d.turn !== null && d.step !== null && last.turn === d.turn && last.step === d.step ? last.index : samples.length;
|
|
65
|
+
if (!usage && samples[index]?.usage)
|
|
66
|
+
continue;
|
|
67
|
+
samples[index] = { key: samples[index]?.key ?? String(e.seq), ...(time === undefined ? {} : { time }), provider, model, ...(usage ? { usage } : {}) };
|
|
68
|
+
last = { turn: d.turn, step: d.step, index };
|
|
69
|
+
}
|
|
70
|
+
return samples;
|
|
71
|
+
}
|
|
72
|
+
/** Accept a token report only when every bucket is a non-negative integer and totals agree. */
|
|
73
|
+
function validUsage(value) {
|
|
74
|
+
const u = object(value ?? {});
|
|
75
|
+
const buckets = [u.inputTokens, u.outputTokens, u.cacheReadTokens ?? 0, u.cacheWriteTokens ?? 0];
|
|
76
|
+
const valid = buckets.every(n => typeof n === 'number' && Number.isSafeInteger(n) && n >= 0)
|
|
77
|
+
&& (u.totalTokens === undefined || typeof u.totalTokens === 'number' && Number.isSafeInteger(u.totalTokens) && u.totalTokens === buckets.reduce((sum, n) => sum + Number(n), 0));
|
|
78
|
+
return valid ? { input: Number(buckets[0]), output: Number(buckets[1]), cacheRead: Number(buckets[2]), cacheWrite: Number(buckets[3]) } : undefined;
|
|
79
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Address and page one session's complete billing history over the host connection. */
|
|
2
|
+
import { type Client } from '../transport/client.ts';
|
|
3
|
+
import { type ObjectValue } from '../transport/wire.ts';
|
|
4
|
+
/** Wire addresses for one `session/list` row, in the order the cost scan should try them.
|
|
5
|
+
*
|
|
6
|
+
* A subagent child is reachable only under its durable parent, and the list row omits the delivery
|
|
7
|
+
* mode, so both modes are offered with the continuable form first.
|
|
8
|
+
* @param session - One row from the host session list.
|
|
9
|
+
* @returns One plain-session address, or both subagent forms when the row is a child.
|
|
10
|
+
*/
|
|
11
|
+
export declare function costAddresses(session: ObjectValue): ObjectValue[];
|
|
12
|
+
/** Read one session's complete cost history, retrying a subagent child with its other delivery mode.
|
|
13
|
+
* @param client - Authenticated host transport.
|
|
14
|
+
* @param session - One row from the host session list.
|
|
15
|
+
* @param signal - Cancels paging without cancelling any agent work.
|
|
16
|
+
* @param onPage - Counts each history request, so a scan can report how much it re-read.
|
|
17
|
+
* @returns Opening cursor and the minimal billing events behind it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function sessionCostHistory(client: Client, session: ObjectValue, signal: AbortSignal, onPage?: () => void): Promise<{
|
|
20
|
+
cursor: number;
|
|
21
|
+
events: ObjectValue[];
|
|
22
|
+
}>;
|