@rayadesu/dsh-llm-billing 0.3.16 → 0.3.17
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 +18 -7
- package/README.zh.md +19 -8
- package/icon.svg +7 -0
- package/lib/index.js +749 -416
- package/lib/typert.host.js +17 -7
- package/lib/typert.remote-client.js +17 -7
- package/lib/types/balance.d.ts +10 -0
- package/lib/types/balance.js +79 -13
- package/lib/types/beijing-time.d.ts +52 -0
- package/lib/types/beijing-time.js +88 -0
- package/lib/types/billing.d.ts +27 -388
- package/lib/types/billing.js +28 -627
- package/lib/types/cache.d.ts +110 -0
- package/lib/types/cache.js +155 -0
- package/lib/types/fold.d.ts +93 -0
- package/lib/types/fold.js +188 -0
- package/lib/types/index.d.ts +1 -1
- package/lib/types/index.js +1 -1
- package/lib/types/persistence.d.ts +113 -0
- package/lib/types/persistence.js +63 -0
- package/lib/types/pricing-table.d.ts +43 -0
- package/lib/types/pricing-table.js +118 -0
- package/lib/types/pricing.d.ts +203 -0
- package/lib/types/pricing.js +203 -0
- package/lib/types/projection.js +14 -0
- package/lib/types/session-lineage.d.ts +70 -0
- package/lib/types/session-lineage.js +99 -0
- package/lib/types/spend.d.ts +58 -0
- package/lib/types/spend.js +190 -0
- package/lib/types/today-spend.d.ts +27 -233
- package/lib/types/today-spend.js +73 -254
- package/lib/types/types.d.ts +28 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +5 -1
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The persistence seams a cold scan reads through. DSH ships a single
|
|
3
|
+
* persistence runtime family — the handle-based `list` / `open` +
|
|
4
|
+
* `SessionHandle` surface (0.1.2-alpha.5+, the only one the plugin's
|
|
5
|
+
* `^0.1.7-alpha.2` peer targets). This module is the only place that knows
|
|
6
|
+
* the handle shape. Split out of `today-spend.ts` so the scan strategies read
|
|
7
|
+
* one shape.
|
|
8
|
+
* @module @rayadesu/dsh-llm-billing/persistence
|
|
9
|
+
*/
|
|
10
|
+
import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session';
|
|
11
|
+
import type { SessionPersistenceRevision } from '@deepseek-ai/dsh-session-persistence';
|
|
12
|
+
import type { SessionLineage } from './session-lineage.ts';
|
|
13
|
+
export interface SessionHeaderSlice extends SessionLineage {
|
|
14
|
+
/** ≤ 0.1.1-rc.2: the durable fork boundary carried by the header; absent for an unseeded session. */
|
|
15
|
+
readonly seedLength?: number;
|
|
16
|
+
/** 0.1.2-alpha.4+: whether the session has a fork-inherited prefix. */
|
|
17
|
+
readonly isSeeded?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Durable creation instant (epoch ms). The conversation read uses it to tell
|
|
20
|
+
* whether a session's spend can span more than the queried day; it is also
|
|
21
|
+
* part of the projection-cache record identity.
|
|
22
|
+
*/
|
|
23
|
+
readonly createdAt?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Structural slice of a live session the scanner reads, accepting both DSH
|
|
27
|
+
* runtime families: the ≤ 0.1.1-rc.2 baseline exposes the log as
|
|
28
|
+
* `events` (+ `header.seedLength`), while 0.1.2-alpha.4+ exposes
|
|
29
|
+
* `snapshotEvents()` and `inheritedEventCount` (and dropped the header field).
|
|
30
|
+
*/
|
|
31
|
+
export interface ScannerSession {
|
|
32
|
+
readonly id: SessionId;
|
|
33
|
+
/** ≤ 0.1.1-rc.2: the full event log snapshot. */
|
|
34
|
+
readonly events?: readonly SessionEvent[];
|
|
35
|
+
/** 0.1.2-alpha.4+: materialize an immutable log snapshot; no args = the full current log. */
|
|
36
|
+
snapshotEvents?(fromSeq?: number, toSeqExclusive?: number): readonly SessionEvent[];
|
|
37
|
+
/** 0.1.2-alpha.4+: the durable inherited-prefix length (0 for an unseeded session). */
|
|
38
|
+
readonly inheritedEventCount?: number;
|
|
39
|
+
/** Durable header slice: `seedLength` (older runtime) or `isSeeded` (newer runtime), plus lineage. */
|
|
40
|
+
readonly header?: SessionHeaderSlice;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Read one live session's complete event log across both runtime families.
|
|
44
|
+
* @throws when the session exposes neither the legacy `events` snapshot nor
|
|
45
|
+
* the newer `snapshotEvents()` reader — an unknown runtime surface must
|
|
46
|
+
* fail loudly rather than silently price an empty log.
|
|
47
|
+
*/
|
|
48
|
+
export declare function liveSessionEvents(session: ScannerSession): readonly SessionEvent[];
|
|
49
|
+
/** Structural slice of a listed persisted session (the snapshot header is a full SessionHeader). */
|
|
50
|
+
export interface ScannerPersistedHeader extends SessionHeaderSlice {
|
|
51
|
+
readonly id: SessionId;
|
|
52
|
+
/** Session format generation; part of the projection-cache record identity. */
|
|
53
|
+
readonly version?: number;
|
|
54
|
+
/** Working directory recorded on the header; part of the projection-cache record identity. */
|
|
55
|
+
readonly cwd?: string;
|
|
56
|
+
}
|
|
57
|
+
/** One stored-session read: the full event log plus the durable inherited boundary. */
|
|
58
|
+
export interface ScannerPersistedRead {
|
|
59
|
+
readonly events: readonly SessionEvent[];
|
|
60
|
+
/** Inherited-prefix length (fork seed length); 0 for an unseeded session. */
|
|
61
|
+
readonly seedLength: number;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* One handle read result across DSH generations. The handle seam first
|
|
65
|
+
* returned the bare event array; since `9b78f99dec` (2026-09-06, in the
|
|
66
|
+
* 0.1.5-alpha.1 checkout) it returns `{ eventState, events }`. Both shapes are
|
|
67
|
+
* accepted so the same build serves the npm alpha line and the checkout.
|
|
68
|
+
*/
|
|
69
|
+
export type ScannerHandleRead = readonly SessionEvent[] | {
|
|
70
|
+
readonly events: readonly SessionEvent[];
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Unwrap a handle read across both return shapes.
|
|
74
|
+
* @param read - the handle's read result.
|
|
75
|
+
* @returns the event array.
|
|
76
|
+
*/
|
|
77
|
+
export declare function handleReadEvents(read: ScannerHandleRead): readonly SessionEvent[];
|
|
78
|
+
/** 0.1.2-alpha.5+ handle-based persistence slice: service-level `list` / `open` + `SessionHandle`. */
|
|
79
|
+
export interface ScannerPersistenceHandle {
|
|
80
|
+
list(): Promise<readonly {
|
|
81
|
+
header: ScannerPersistedHeader;
|
|
82
|
+
revision: SessionPersistenceRevision;
|
|
83
|
+
}[]>;
|
|
84
|
+
open(id: SessionId, access: 'read'): Promise<{
|
|
85
|
+
readonly header?: SessionHeaderSlice;
|
|
86
|
+
/** 0.1.2-alpha.5+: the handle carries the exact inherited cut beside the header. */
|
|
87
|
+
readonly inheritedEventCount?: number;
|
|
88
|
+
read(): Promise<ScannerHandleRead>;
|
|
89
|
+
close(): Promise<void>;
|
|
90
|
+
}>;
|
|
91
|
+
}
|
|
92
|
+
/** The persistence service slice the scanner reads through (handle-based). */
|
|
93
|
+
export type ScannerPersistence = ScannerPersistenceHandle;
|
|
94
|
+
/**
|
|
95
|
+
* List every stored session snapshot through the handle-based persistence
|
|
96
|
+
* service (`list`).
|
|
97
|
+
* @param persistence - the persistence service slice.
|
|
98
|
+
* @returns one snapshot per stored session.
|
|
99
|
+
*/
|
|
100
|
+
export declare function persistenceListSnapshots(persistence: ScannerPersistence): Promise<readonly {
|
|
101
|
+
header: ScannerPersistedHeader;
|
|
102
|
+
revision: SessionPersistenceRevision;
|
|
103
|
+
}[]>;
|
|
104
|
+
/**
|
|
105
|
+
* Read one stored session's complete event log and durable inherited boundary
|
|
106
|
+
* through the handle-based persistence service (`open` + handle `read`; the
|
|
107
|
+
* handle is closed after the read). Throws when the session does not exist.
|
|
108
|
+
* @param persistence - the persistence service slice.
|
|
109
|
+
* @param id - the stored session to read.
|
|
110
|
+
* @returns the session's complete event log plus its inherited-prefix boundary.
|
|
111
|
+
*/
|
|
112
|
+
export declare function persistenceInspect(persistence: ScannerPersistence, id: SessionId): Promise<ScannerPersistedRead>;
|
|
113
|
+
//# sourceMappingURL=persistence.d.ts.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The persistence seams a cold scan reads through. DSH ships a single
|
|
3
|
+
* persistence runtime family — the handle-based `list` / `open` +
|
|
4
|
+
* `SessionHandle` surface (0.1.2-alpha.5+, the only one the plugin's
|
|
5
|
+
* `^0.1.7-alpha.2` peer targets). This module is the only place that knows
|
|
6
|
+
* the handle shape. Split out of `today-spend.ts` so the scan strategies read
|
|
7
|
+
* one shape.
|
|
8
|
+
* @module @rayadesu/dsh-llm-billing/persistence
|
|
9
|
+
*/
|
|
10
|
+
import { forkBoundaryOf } from "./billing.js";
|
|
11
|
+
/**
|
|
12
|
+
* Read one live session's complete event log across both runtime families.
|
|
13
|
+
* @throws when the session exposes neither the legacy `events` snapshot nor
|
|
14
|
+
* the newer `snapshotEvents()` reader — an unknown runtime surface must
|
|
15
|
+
* fail loudly rather than silently price an empty log.
|
|
16
|
+
*/
|
|
17
|
+
export function liveSessionEvents(session) {
|
|
18
|
+
if (session.events !== undefined)
|
|
19
|
+
return session.events;
|
|
20
|
+
// Optional-call form keeps the receiver bound (snapshotEvents uses `this`).
|
|
21
|
+
if (session.snapshotEvents !== undefined)
|
|
22
|
+
return session.snapshotEvents();
|
|
23
|
+
throw new Error('llm-billing: session log surface is neither Session.events nor Session.snapshotEvents');
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Unwrap a handle read across both return shapes.
|
|
27
|
+
* @param read - the handle's read result.
|
|
28
|
+
* @returns the event array.
|
|
29
|
+
*/
|
|
30
|
+
export function handleReadEvents(read) {
|
|
31
|
+
// `Array.isArray` does not narrow readonly arrays out of a union, so the
|
|
32
|
+
// branches are asserted explicitly.
|
|
33
|
+
if (Array.isArray(read))
|
|
34
|
+
return read;
|
|
35
|
+
return read.events;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* List every stored session snapshot through the handle-based persistence
|
|
39
|
+
* service (`list`).
|
|
40
|
+
* @param persistence - the persistence service slice.
|
|
41
|
+
* @returns one snapshot per stored session.
|
|
42
|
+
*/
|
|
43
|
+
export function persistenceListSnapshots(persistence) {
|
|
44
|
+
return persistence.list();
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Read one stored session's complete event log and durable inherited boundary
|
|
48
|
+
* through the handle-based persistence service (`open` + handle `read`; the
|
|
49
|
+
* handle is closed after the read). Throws when the session does not exist.
|
|
50
|
+
* @param persistence - the persistence service slice.
|
|
51
|
+
* @param id - the stored session to read.
|
|
52
|
+
* @returns the session's complete event log plus its inherited-prefix boundary.
|
|
53
|
+
*/
|
|
54
|
+
export async function persistenceInspect(persistence, id) {
|
|
55
|
+
const handle = await persistence.open(id, 'read');
|
|
56
|
+
try {
|
|
57
|
+
return { events: handleReadEvents(await handle.read()), seedLength: forkBoundaryOf(handle) };
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
await handle.close();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=persistence.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The published rate schedule: which models cost what, and since when.
|
|
3
|
+
*
|
|
4
|
+
* Data only — every policy decision about how a rate is chosen (revision
|
|
5
|
+
* lookup, peak windows, day boundaries) lives in `billing.ts`. Keeping them
|
|
6
|
+
* apart means adding a model never touches the pricing engine: the change is
|
|
7
|
+
* one row here, plus a test.
|
|
8
|
+
*
|
|
9
|
+
* Rates are CNY per 1M tokens. Rows sharing a model are that model's rate
|
|
10
|
+
* history (see {@link resolveBilling}).
|
|
11
|
+
* @module @rayadesu/dsh-llm-billing/pricing-table
|
|
12
|
+
*/
|
|
13
|
+
import type { BillingConfigModel, PeakHourWindow } from './billing.ts';
|
|
14
|
+
/**
|
|
15
|
+
* Published peak-hour windows (Beijing time): 09:00–12:00 and 14:00–18:00,
|
|
16
|
+
* applied on weekdays (Monday–Friday) only — weekends are always off-peak
|
|
17
|
+
* (effective 2026-08-23).
|
|
18
|
+
*/
|
|
19
|
+
export declare const DEFAULT_PEAK_HOURS: readonly PeakHourWindow[];
|
|
20
|
+
/**
|
|
21
|
+
* Inclusive epoch ms of the published V4 Flash series re-pricing:
|
|
22
|
+
* 2026-09-10 12:00 Beijing time (UTC+8, no DST) = 04:00 UTC. Samples before
|
|
23
|
+
* this instant keep the base rates; samples at or after it bill at the second
|
|
24
|
+
* revision.
|
|
25
|
+
*/
|
|
26
|
+
export declare const FLASH_SERIES_RATE_CHANGE_AT: number;
|
|
27
|
+
/**
|
|
28
|
+
* Inclusive epoch ms of the announced V4 Pro route switch: 2026-09-14 12:00
|
|
29
|
+
* Beijing time (UTC+8, no DST) = 04:00 UTC. From that instant the V4 Pro route
|
|
30
|
+
* is served by V4.1 Flash and billed at the V4.1 Flash rates.
|
|
31
|
+
*/
|
|
32
|
+
export declare const V4_PRO_ROUTE_SWITCH_AT: number;
|
|
33
|
+
/**
|
|
34
|
+
* Official peak/off-peak rates (CNY per 1M tokens) per model, as dated
|
|
35
|
+
* revisions. Base rows are the schedule effective 2026-08-17; the V4 Flash
|
|
36
|
+
* series (V4.1 Flash, V4 Flash, V4 Flash Vision Exp) carries the second
|
|
37
|
+
* revision effective 2026-09-10 12:00 Beijing, and the V4 Pro row the V4.1
|
|
38
|
+
* Flash rates from its announced route switch (2026-09-14 12:00 Beijing) —
|
|
39
|
+
* the MiMo series (V2.5, and V2.6 which kept V2.5's pricing) is untouched by
|
|
40
|
+
* either adjustment. Rows sharing a model are that model's rate history.
|
|
41
|
+
*/
|
|
42
|
+
export declare const DEFAULT_MODEL_PRICING: readonly BillingConfigModel[];
|
|
43
|
+
//# sourceMappingURL=pricing-table.d.ts.map
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The published rate schedule: which models cost what, and since when.
|
|
3
|
+
*
|
|
4
|
+
* Data only — every policy decision about how a rate is chosen (revision
|
|
5
|
+
* lookup, peak windows, day boundaries) lives in `billing.ts`. Keeping them
|
|
6
|
+
* apart means adding a model never touches the pricing engine: the change is
|
|
7
|
+
* one row here, plus a test.
|
|
8
|
+
*
|
|
9
|
+
* Rates are CNY per 1M tokens. Rows sharing a model are that model's rate
|
|
10
|
+
* history (see {@link resolveBilling}).
|
|
11
|
+
* @module @rayadesu/dsh-llm-billing/pricing-table
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Published peak-hour windows (Beijing time): 09:00–12:00 and 14:00–18:00,
|
|
15
|
+
* applied on weekdays (Monday–Friday) only — weekends are always off-peak
|
|
16
|
+
* (effective 2026-08-23).
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_PEAK_HOURS = [
|
|
19
|
+
{ start: 9, end: 12 },
|
|
20
|
+
{ start: 14, end: 18 },
|
|
21
|
+
];
|
|
22
|
+
/**
|
|
23
|
+
* Inclusive epoch ms of the published V4 Flash series re-pricing:
|
|
24
|
+
* 2026-09-10 12:00 Beijing time (UTC+8, no DST) = 04:00 UTC. Samples before
|
|
25
|
+
* this instant keep the base rates; samples at or after it bill at the second
|
|
26
|
+
* revision.
|
|
27
|
+
*/
|
|
28
|
+
export const FLASH_SERIES_RATE_CHANGE_AT = Date.UTC(2026, 8, 10, 4, 0, 0);
|
|
29
|
+
/**
|
|
30
|
+
* Inclusive epoch ms of the announced V4 Pro route switch: 2026-09-14 12:00
|
|
31
|
+
* Beijing time (UTC+8, no DST) = 04:00 UTC. From that instant the V4 Pro route
|
|
32
|
+
* is served by V4.1 Flash and billed at the V4.1 Flash rates.
|
|
33
|
+
*/
|
|
34
|
+
export const V4_PRO_ROUTE_SWITCH_AT = Date.UTC(2026, 8, 14, 4, 0, 0);
|
|
35
|
+
/** The V4 Flash series' base rates (effective 2026-08-17), CNY per 1M tokens. */
|
|
36
|
+
const FLASH_BASE_RATES = {
|
|
37
|
+
peak: { cacheHitInput: 0.10, cacheMissInput: 3.0, output: 9.0 },
|
|
38
|
+
offPeak: { cacheHitInput: 0.05, cacheMissInput: 1.5, output: 4.5 },
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* The V4 Flash series' second revision (effective
|
|
42
|
+
* {@link FLASH_SERIES_RATE_CHANGE_AT}): off-peak 0.02 / 1.0 / 4.0, peak at
|
|
43
|
+
* twice those prices.
|
|
44
|
+
*/
|
|
45
|
+
const FLASH_REPRICED_RATES = {
|
|
46
|
+
effectiveFrom: FLASH_SERIES_RATE_CHANGE_AT,
|
|
47
|
+
peak: { cacheHitInput: 0.04, cacheMissInput: 2.0, output: 8.0 },
|
|
48
|
+
offPeak: { cacheHitInput: 0.02, cacheMissInput: 1.0, output: 4.0 },
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* The V4.1 Flash rates as they reach the retired V4 Pro route from
|
|
52
|
+
* {@link V4_PRO_ROUTE_SWITCH_AT}: the same price pair as the flash series'
|
|
53
|
+
* second revision, carried at its own effective instant.
|
|
54
|
+
*/
|
|
55
|
+
const V4_PRO_SWITCHED_RATES = {
|
|
56
|
+
effectiveFrom: V4_PRO_ROUTE_SWITCH_AT,
|
|
57
|
+
peak: FLASH_REPRICED_RATES.peak,
|
|
58
|
+
offPeak: FLASH_REPRICED_RATES.offPeak,
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Official peak/off-peak rates (CNY per 1M tokens) per model, as dated
|
|
62
|
+
* revisions. Base rows are the schedule effective 2026-08-17; the V4 Flash
|
|
63
|
+
* series (V4.1 Flash, V4 Flash, V4 Flash Vision Exp) carries the second
|
|
64
|
+
* revision effective 2026-09-10 12:00 Beijing, and the V4 Pro row the V4.1
|
|
65
|
+
* Flash rates from its announced route switch (2026-09-14 12:00 Beijing) —
|
|
66
|
+
* the MiMo series (V2.5, and V2.6 which kept V2.5's pricing) is untouched by
|
|
67
|
+
* either adjustment. Rows sharing a model are that model's rate history.
|
|
68
|
+
*/
|
|
69
|
+
export const DEFAULT_MODEL_PRICING = [
|
|
70
|
+
// deepseek-flash is the V4.1 Flash route, DSH's default catalog entry; image
|
|
71
|
+
// inputs are converted to tokens at the same per-token price.
|
|
72
|
+
{ model: 'deepseek-flash', ...FLASH_BASE_RATES },
|
|
73
|
+
{ model: 'deepseek-flash', ...FLASH_REPRICED_RATES },
|
|
74
|
+
{ model: 'deepseek-v4-flash', ...FLASH_BASE_RATES },
|
|
75
|
+
{ model: 'deepseek-v4-flash', ...FLASH_REPRICED_RATES },
|
|
76
|
+
// deepseek-v4.1-flash-expires-on-0910 was the V4.1 Flash preview route,
|
|
77
|
+
// retired when the model was released on 2026-09-10; its rows stay so the
|
|
78
|
+
// logs that used it keep pricing.
|
|
79
|
+
{ model: 'deepseek-v4.1-flash-expires-on-0910', ...FLASH_BASE_RATES },
|
|
80
|
+
{ model: 'deepseek-v4.1-flash-expires-on-0910', ...FLASH_REPRICED_RATES },
|
|
81
|
+
{
|
|
82
|
+
model: 'deepseek-v4-pro',
|
|
83
|
+
peak: { cacheHitInput: 0.30, cacheMissInput: 9.0, output: 27.0 },
|
|
84
|
+
offPeak: { cacheHitInput: 0.15, cacheMissInput: 4.5, output: 13.5 },
|
|
85
|
+
},
|
|
86
|
+
// From the announced route switch V4 Pro is served by V4.1 Flash and billed
|
|
87
|
+
// at the V4.1 Flash rates.
|
|
88
|
+
{ model: 'deepseek-v4-pro', ...V4_PRO_SWITCHED_RATES },
|
|
89
|
+
// deepseek-v4-flash-vision-exp bills at the same rates as deepseek-v4-flash;
|
|
90
|
+
// images are converted to tokens at the same per-token price.
|
|
91
|
+
{ model: 'deepseek-v4-flash-vision-exp', ...FLASH_BASE_RATES },
|
|
92
|
+
{ model: 'deepseek-v4-flash-vision-exp', ...FLASH_REPRICED_RATES },
|
|
93
|
+
// MiMo series (Xiaomi): flat rate, no peak/off-peak distinction. V2.6 keeps
|
|
94
|
+
// V2.5's published API pricing (2026-09-22 launch, "API pricing unchanged
|
|
95
|
+
// from V2.5"); cache writes bill at the miss rate, though Xiaomi's launch
|
|
96
|
+
// window makes them free provider-side for a limited time.
|
|
97
|
+
{
|
|
98
|
+
model: 'mimo-v2.5-pro',
|
|
99
|
+
peak: { cacheHitInput: 0.025, cacheMissInput: 3.0, output: 6.0 },
|
|
100
|
+
offPeak: { cacheHitInput: 0.025, cacheMissInput: 3.0, output: 6.0 },
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
model: 'mimo-v2.5',
|
|
104
|
+
peak: { cacheHitInput: 0.02, cacheMissInput: 1.0, output: 2.0 },
|
|
105
|
+
offPeak: { cacheHitInput: 0.02, cacheMissInput: 1.0, output: 2.0 },
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
model: 'mimo-v2.6-pro',
|
|
109
|
+
peak: { cacheHitInput: 0.025, cacheMissInput: 3.0, output: 6.0 },
|
|
110
|
+
offPeak: { cacheHitInput: 0.025, cacheMissInput: 3.0, output: 6.0 },
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
model: 'mimo-v2.6-flash',
|
|
114
|
+
peak: { cacheHitInput: 0.02, cacheMissInput: 1.0, output: 2.0 },
|
|
115
|
+
offPeak: { cacheHitInput: 0.02, cacheMissInput: 1.0, output: 2.0 },
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
//# sourceMappingURL=pricing-table.js.map
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSeek pricing: the peak/off-peak pricing table, the per-event pricing, and
|
|
3
|
+
* the resolved-billing shape. Pure functions over token usage and the pricing
|
|
4
|
+
* table, so the Remote gateway stays transport-free and the whole spend is
|
|
5
|
+
* testable without a key.
|
|
6
|
+
*
|
|
7
|
+
* The per-event pricing lives in {@link priceEvent} / {@link priceUsage}; the
|
|
8
|
+
* fold state machine that consumes it (`BillingFolder` / {@link applyBillingEvent})
|
|
9
|
+
* lives in `fold.ts`, and every scan path prices through these primitives so a
|
|
10
|
+
* pricing-table change cannot drift one path from the others.
|
|
11
|
+
* @module @rayadesu/dsh-llm-billing/pricing
|
|
12
|
+
*/
|
|
13
|
+
import type { TokenUsage } from '@deepseek-ai/dsh-llm';
|
|
14
|
+
import type { SessionEvent } from '@deepseek-ai/dsh-session';
|
|
15
|
+
import type { BeijingParts } from './beijing-time.ts';
|
|
16
|
+
/** One token price point, in CNY per 1M tokens. */
|
|
17
|
+
export interface DeepSeekTokenPrice {
|
|
18
|
+
/** 1M input cache-hit tokens. */
|
|
19
|
+
cacheHitInput: number;
|
|
20
|
+
/** 1M input cache-miss tokens (cache writes bill at this rate too). */
|
|
21
|
+
cacheMissInput: number;
|
|
22
|
+
/** 1M output tokens. */
|
|
23
|
+
output: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* One published peak/off-peak rate revision of a model: the price pair plus the
|
|
27
|
+
* instant it took effect. A provider re-prices a series without repricing its
|
|
28
|
+
* history, so the table keeps every revision and prices each sample at the
|
|
29
|
+
* rates of the sample's own timestamp.
|
|
30
|
+
*/
|
|
31
|
+
export interface DeepSeekRateRevision {
|
|
32
|
+
/** Price during peak hours. */
|
|
33
|
+
peak: DeepSeekTokenPrice;
|
|
34
|
+
/** Price during off-peak hours. */
|
|
35
|
+
offPeak: DeepSeekTokenPrice;
|
|
36
|
+
/**
|
|
37
|
+
* Inclusive epoch ms this revision takes effect: it prices every sample at or
|
|
38
|
+
* after that instant. `undefined` on a model's base revision, which also
|
|
39
|
+
* covers every earlier instant.
|
|
40
|
+
*/
|
|
41
|
+
effectiveFrom?: number;
|
|
42
|
+
}
|
|
43
|
+
/** Resolved pricing for one model: its published rate revisions, oldest first. */
|
|
44
|
+
export interface DeepSeekModelPricing {
|
|
45
|
+
/** Peak-hour price of the newest revision (the rates in effect now). */
|
|
46
|
+
peak: DeepSeekTokenPrice;
|
|
47
|
+
/** Off-peak price of the newest revision (the rates in effect now). */
|
|
48
|
+
offPeak: DeepSeekTokenPrice;
|
|
49
|
+
/**
|
|
50
|
+
* Every published revision of this model, ascending by `effectiveFrom` (an
|
|
51
|
+
* undated base revision first); always at least one. {@link priceUsage} picks
|
|
52
|
+
* the revision in effect at the priced sample's own timestamp.
|
|
53
|
+
*/
|
|
54
|
+
revisions: readonly DeepSeekRateRevision[];
|
|
55
|
+
}
|
|
56
|
+
/** One model's pricing-table row in configuration form. */
|
|
57
|
+
export interface BillingConfigModel extends DeepSeekRateRevision {
|
|
58
|
+
/** Wire model id. */
|
|
59
|
+
model: string;
|
|
60
|
+
}
|
|
61
|
+
/** One peak-hour window on a 24h Beijing-time clock, applied weekdays only. */
|
|
62
|
+
export interface PeakHourWindow {
|
|
63
|
+
/** Inclusive start hour, `0`–`23`. */
|
|
64
|
+
start: number;
|
|
65
|
+
/** Exclusive end hour, `1`–`24`. */
|
|
66
|
+
end: number;
|
|
67
|
+
}
|
|
68
|
+
/** Optional billing configuration; omission uses the published defaults. */
|
|
69
|
+
export interface BillingConfig {
|
|
70
|
+
/**
|
|
71
|
+
* Peak-hour windows in Beijing time, applied on weekdays (Monday–Friday)
|
|
72
|
+
* only; weekends (Saturday and Sunday) are always off-peak.
|
|
73
|
+
*/
|
|
74
|
+
peakHours?: PeakHourWindow[];
|
|
75
|
+
/**
|
|
76
|
+
* Per-model pricing rows; omission uses the published V4 and MiMo rates.
|
|
77
|
+
* Several rows for one model declare that model's rate history, priced per
|
|
78
|
+
* sample by `effectiveFrom` (see {@link BillingConfigModel}).
|
|
79
|
+
*/
|
|
80
|
+
models?: BillingConfigModel[];
|
|
81
|
+
}
|
|
82
|
+
export { DEFAULT_MODEL_PRICING, DEFAULT_PEAK_HOURS, FLASH_SERIES_RATE_CHANGE_AT, V4_PRO_ROUTE_SWITCH_AT } from './pricing-table.ts';
|
|
83
|
+
/** Resolved billing configuration: a pricing table plus peak-hour windows. */
|
|
84
|
+
export interface ResolvedBilling {
|
|
85
|
+
peakHours: readonly {
|
|
86
|
+
start: number;
|
|
87
|
+
end: number;
|
|
88
|
+
}[];
|
|
89
|
+
models: ReadonlyMap<string, DeepSeekModelPricing>;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Resolve optional configuration to a pricing table, defaulting omitted or
|
|
93
|
+
* empty rows to the published rates. Schemastery materializes an absent
|
|
94
|
+
* `z.array` as `[]` rather than `undefined`, so emptiness — not just absence —
|
|
95
|
+
* selects the defaults. Explicit non-empty rows override the same model; a
|
|
96
|
+
* supplied non-empty `models` list is authoritative.
|
|
97
|
+
*
|
|
98
|
+
* Rows sharing a model are that model's rate revisions, kept in ascending
|
|
99
|
+
* `effectiveFrom` order (an undated base revision first). Two rows declaring
|
|
100
|
+
* the same effective instant are one revision and the later row wins — the
|
|
101
|
+
* historical override rule — so re-declaring a model can neither duplicate a
|
|
102
|
+
* revision nor install a second undated base.
|
|
103
|
+
* @param config - optional raw billing configuration.
|
|
104
|
+
* @returns the resolved table (per model: its revisions plus the newest rates) and peak-hour windows.
|
|
105
|
+
*/
|
|
106
|
+
export declare function resolveBilling(config: BillingConfig | undefined): ResolvedBilling;
|
|
107
|
+
/**
|
|
108
|
+
* Whether a timestamp falls inside any peak-hour window (Beijing time,
|
|
109
|
+
* weekdays Monday–Friday only). Weekends (Saturday and Sunday) are always
|
|
110
|
+
* off-peak, matching the published peak-hours rule.
|
|
111
|
+
* @param billing - resolved pricing with peak-hour windows.
|
|
112
|
+
* @param now - the moment to classify.
|
|
113
|
+
* @returns true during a weekday peak hour.
|
|
114
|
+
*/
|
|
115
|
+
export declare function isPeak(billing: ResolvedBilling, now: Date): boolean;
|
|
116
|
+
/**
|
|
117
|
+
* One priced billed event's contribution: the model row plus the Beijing
|
|
118
|
+
* calendar day its timestamp falls on. `undefined` when the event carries no
|
|
119
|
+
* priced usage (not an `assistant/message`, no usage report, or a model
|
|
120
|
+
* without a pricing row).
|
|
121
|
+
*/
|
|
122
|
+
export interface BillingEventContribution {
|
|
123
|
+
/** Beijing-time calendar-day key of the event's timestamp. */
|
|
124
|
+
dayKey: string;
|
|
125
|
+
/** Wire model id, e.g. `deepseek-v4-flash`. */
|
|
126
|
+
model: string;
|
|
127
|
+
/** Selector label for the model. */
|
|
128
|
+
displayName: string;
|
|
129
|
+
/** Billed cost in CNY (peak plus off-peak portions). */
|
|
130
|
+
cost: number;
|
|
131
|
+
/** Cost portion billed at peak rates. */
|
|
132
|
+
peakCost: number;
|
|
133
|
+
/** Cost portion billed at off-peak rates. */
|
|
134
|
+
offPeakCost: number;
|
|
135
|
+
/** Cache-hit input tokens billed at the hit rate. */
|
|
136
|
+
cacheHitInputTokens: number;
|
|
137
|
+
/** Cache-miss input tokens (uncached input plus cache writes), billed at the miss rate. */
|
|
138
|
+
cacheMissInputTokens: number;
|
|
139
|
+
/** Output tokens (reasoning included), billed at the output rate. */
|
|
140
|
+
outputTokens: number;
|
|
141
|
+
/** Billed cost of cache-hit input tokens in CNY. */
|
|
142
|
+
cacheHitInputCost: number;
|
|
143
|
+
/** Billed cost of cache-miss input tokens (uncached input plus cache writes) in CNY. */
|
|
144
|
+
cacheMissInputCost: number;
|
|
145
|
+
/** Billed cost of output tokens (reasoning included) in CNY. */
|
|
146
|
+
outputCost: number;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Price one event at the official per-model rates, applying the peak/off-peak
|
|
150
|
+
* table by its Beijing-time hour and weekday (peak windows apply Monday–Friday
|
|
151
|
+
* only; weekends are off-peak) and the rate revision in effect at its own
|
|
152
|
+
* timestamp. Each `assistant/message` event with usage
|
|
153
|
+
* contributes cache-hit input, cache-miss input (uncached input plus cache
|
|
154
|
+
* writes), and output (reasoning included) tokens at the rate of its own
|
|
155
|
+
* timestamp; a model with usage but no pricing row contributes nothing.
|
|
156
|
+
* @param event - the event to price.
|
|
157
|
+
* @param billing - resolved pricing with peak-hour windows.
|
|
158
|
+
* @param names - model id → display label.
|
|
159
|
+
* @returns the priced contribution, or `undefined` when the event has no priced usage.
|
|
160
|
+
*/
|
|
161
|
+
export declare function priceEvent(event: SessionEvent, billing: ResolvedBilling, names: ReadonlyMap<string, string>): BillingEventContribution | undefined;
|
|
162
|
+
/**
|
|
163
|
+
* Price one event at the official per-model rates using a precomputed
|
|
164
|
+
* Beijing-time view — the day-filtering and pricing of one event share a
|
|
165
|
+
* single timezone parse (see {@link beijingPartsOf}). Semantics are identical
|
|
166
|
+
* to {@link priceEvent}.
|
|
167
|
+
* @param parts - the event's Beijing-time view.
|
|
168
|
+
* @param event - the event to price.
|
|
169
|
+
* @param billing - resolved pricing with peak-hour windows.
|
|
170
|
+
* @param names - model id → display label.
|
|
171
|
+
* @returns the priced contribution, or `undefined` when the event has no priced usage.
|
|
172
|
+
*/
|
|
173
|
+
export declare function priceEventAt(parts: BeijingParts, event: SessionEvent, billing: ResolvedBilling, names: ReadonlyMap<string, string>): BillingEventContribution | undefined;
|
|
174
|
+
/** Every token one usage sample reports, whether or not it gets priced. */
|
|
175
|
+
export declare function usageTokens(usage: TokenUsage): number;
|
|
176
|
+
/**
|
|
177
|
+
* Report a model carrying no rate row, at most once per id (and at most
|
|
178
|
+
* {@link UNPRICED_WARN_LIMIT} ids per process). Every figure reads as zero when
|
|
179
|
+
* the upstream model is unknown, which looks exactly like "no usage" rather
|
|
180
|
+
* than a missing row — the one thing this warning exists to separate.
|
|
181
|
+
*
|
|
182
|
+
* Deliberately NOT guessed: model ids are chosen by whoever ships the model, so
|
|
183
|
+
* a similar-looking name says nothing about a similar price — and a wrong number
|
|
184
|
+
* that LOOKS authoritative is worse than a visible gap. The fix is one row under
|
|
185
|
+
* `billing.models`, which is exactly what the warning points at.
|
|
186
|
+
* @param model - the unlisted wire model id.
|
|
187
|
+
*/
|
|
188
|
+
export declare function reportUnpricedModel(model: string): void;
|
|
189
|
+
/**
|
|
190
|
+
* Price one provider-reported usage sample for one model at the rates of the
|
|
191
|
+
* sample's own Beijing-time hour and weekday — the peak or off-peak price of
|
|
192
|
+
* the rate revision in effect at the sample's own timestamp (a re-priced series
|
|
193
|
+
* bills its history at the rates that applied then). `undefined` when the model
|
|
194
|
+
* has no pricing row.
|
|
195
|
+
* @param parts - the sample's Beijing-time view.
|
|
196
|
+
* @param usage - the reported token buckets.
|
|
197
|
+
* @param model - the wire model id the sample belongs to.
|
|
198
|
+
* @param billing - resolved pricing with peak-hour windows.
|
|
199
|
+
* @param names - model id → display label.
|
|
200
|
+
* @returns the priced contribution, or `undefined` when the model has no rate row.
|
|
201
|
+
*/
|
|
202
|
+
export declare function priceUsage(parts: BeijingParts, usage: TokenUsage, model: string, billing: ResolvedBilling, names: ReadonlyMap<string, string>): BillingEventContribution | undefined;
|
|
203
|
+
//# sourceMappingURL=pricing.d.ts.map
|