@rayadesu/dsh-llm-billing 0.3.15 → 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 +791 -423
- 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 -614
- 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 +8 -5
- 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
package/lib/typert.host.js
CHANGED
|
@@ -54,6 +54,11 @@ const _rayadesu_dsh_llm_billing_billing_getSessionSpend_result$schema = () => (_
|
|
|
54
54
|
'cacheMissInputCost': z.number(),
|
|
55
55
|
'outputCost': z.number(),
|
|
56
56
|
})),
|
|
57
|
+
'unpriced': z.union([z.undefined(), z.object({
|
|
58
|
+
'events': z.number(),
|
|
59
|
+
'tokens': z.number(),
|
|
60
|
+
'models': z.array(z.string()),
|
|
61
|
+
})]).optional(),
|
|
57
62
|
}))
|
|
58
63
|
let _rayadesu_dsh_llm_billing_billing_getSessionTurnSpends_parameter_0$schema$value
|
|
59
64
|
const _rayadesu_dsh_llm_billing_billing_getSessionTurnSpends_parameter_0$schema = () => (_rayadesu_dsh_llm_billing_billing_getSessionTurnSpends_parameter_0$schema$value ??= z.intersection(z.string(), z.unknown()))
|
|
@@ -93,6 +98,11 @@ const _rayadesu_dsh_llm_billing_billing_getTodaySpend_result$schema = () => (_ra
|
|
|
93
98
|
'cacheMissInputCost': z.number(),
|
|
94
99
|
'outputCost': z.number(),
|
|
95
100
|
})),
|
|
101
|
+
'unpriced': z.union([z.undefined(), z.object({
|
|
102
|
+
'events': z.number(),
|
|
103
|
+
'tokens': z.number(),
|
|
104
|
+
'models': z.array(z.string()),
|
|
105
|
+
})]).optional(),
|
|
96
106
|
}))
|
|
97
107
|
let _rayadesu_dsh_llm_billing_billing_getTurnSpend_parameter_0$schema$value
|
|
98
108
|
const _rayadesu_dsh_llm_billing_billing_getTurnSpend_parameter_0$schema = () => (_rayadesu_dsh_llm_billing_billing_getTurnSpend_parameter_0$schema$value ??= z.intersection(z.string(), z.unknown()))
|
|
@@ -133,7 +143,7 @@ export const TYPERT = {
|
|
|
133
143
|
typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekBalance',
|
|
134
144
|
create: _rayadesu_dsh_llm_billing_billing_getBalance_result$schema,
|
|
135
145
|
},
|
|
136
|
-
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":
|
|
146
|
+
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":258,"column":3},
|
|
137
147
|
},
|
|
138
148
|
{
|
|
139
149
|
id: '@rayadesu/dsh-llm-billing#billing/getDelegatedSpend',
|
|
@@ -169,7 +179,7 @@ export const TYPERT = {
|
|
|
169
179
|
typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekDelegatedSpend',
|
|
170
180
|
create: _rayadesu_dsh_llm_billing_billing_getDelegatedSpend_result$schema,
|
|
171
181
|
},
|
|
172
|
-
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":
|
|
182
|
+
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":312,"column":3},
|
|
173
183
|
},
|
|
174
184
|
{
|
|
175
185
|
id: '@rayadesu/dsh-llm-billing#billing/getSessionSpend',
|
|
@@ -194,7 +204,7 @@ export const TYPERT = {
|
|
|
194
204
|
typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekSessionSpend',
|
|
195
205
|
create: _rayadesu_dsh_llm_billing_billing_getSessionSpend_result$schema,
|
|
196
206
|
},
|
|
197
|
-
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":
|
|
207
|
+
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":269,"column":3},
|
|
198
208
|
},
|
|
199
209
|
{
|
|
200
210
|
id: '@rayadesu/dsh-llm-billing#billing/getSessionTurnSpends',
|
|
@@ -219,7 +229,7 @@ export const TYPERT = {
|
|
|
219
229
|
typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekSessionTurnSpends',
|
|
220
230
|
create: _rayadesu_dsh_llm_billing_billing_getSessionTurnSpends_result$schema,
|
|
221
231
|
},
|
|
222
|
-
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":
|
|
232
|
+
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":337,"column":3},
|
|
223
233
|
},
|
|
224
234
|
{
|
|
225
235
|
id: '@rayadesu/dsh-llm-billing#billing/getTodaySessionsSpend',
|
|
@@ -245,7 +255,7 @@ export const TYPERT = {
|
|
|
245
255
|
typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekTodaySessionsSpend',
|
|
246
256
|
create: _rayadesu_dsh_llm_billing_billing_getTodaySessionsSpend_result$schema,
|
|
247
257
|
},
|
|
248
|
-
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":
|
|
258
|
+
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":297,"column":3},
|
|
249
259
|
},
|
|
250
260
|
{
|
|
251
261
|
id: '@rayadesu/dsh-llm-billing#billing/getTodaySpend',
|
|
@@ -271,7 +281,7 @@ export const TYPERT = {
|
|
|
271
281
|
typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekTodaySpend',
|
|
272
282
|
create: _rayadesu_dsh_llm_billing_billing_getTodaySpend_result$schema,
|
|
273
283
|
},
|
|
274
|
-
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":
|
|
284
|
+
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":282,"column":3},
|
|
275
285
|
},
|
|
276
286
|
{
|
|
277
287
|
id: '@rayadesu/dsh-llm-billing#billing/getTurnSpend',
|
|
@@ -306,7 +316,7 @@ export const TYPERT = {
|
|
|
306
316
|
typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekTurnSpend',
|
|
307
317
|
create: _rayadesu_dsh_llm_billing_billing_getTurnSpend_result$schema,
|
|
308
318
|
},
|
|
309
|
-
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":
|
|
319
|
+
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":325,"column":3},
|
|
310
320
|
},
|
|
311
321
|
],
|
|
312
322
|
model: {
|
|
@@ -54,6 +54,11 @@ const _rayadesu_dsh_llm_billing_billing_getSessionSpend_result$schema = () => (_
|
|
|
54
54
|
'cacheMissInputCost': z.number(),
|
|
55
55
|
'outputCost': z.number(),
|
|
56
56
|
})),
|
|
57
|
+
'unpriced': z.union([z.undefined(), z.object({
|
|
58
|
+
'events': z.number(),
|
|
59
|
+
'tokens': z.number(),
|
|
60
|
+
'models': z.array(z.string()),
|
|
61
|
+
})]).optional(),
|
|
57
62
|
}))
|
|
58
63
|
let _rayadesu_dsh_llm_billing_billing_getSessionTurnSpends_parameter_0$schema$value
|
|
59
64
|
const _rayadesu_dsh_llm_billing_billing_getSessionTurnSpends_parameter_0$schema = () => (_rayadesu_dsh_llm_billing_billing_getSessionTurnSpends_parameter_0$schema$value ??= z.intersection(z.string(), z.unknown()))
|
|
@@ -93,6 +98,11 @@ const _rayadesu_dsh_llm_billing_billing_getTodaySpend_result$schema = () => (_ra
|
|
|
93
98
|
'cacheMissInputCost': z.number(),
|
|
94
99
|
'outputCost': z.number(),
|
|
95
100
|
})),
|
|
101
|
+
'unpriced': z.union([z.undefined(), z.object({
|
|
102
|
+
'events': z.number(),
|
|
103
|
+
'tokens': z.number(),
|
|
104
|
+
'models': z.array(z.string()),
|
|
105
|
+
})]).optional(),
|
|
96
106
|
}))
|
|
97
107
|
let _rayadesu_dsh_llm_billing_billing_getTurnSpend_parameter_0$schema$value
|
|
98
108
|
const _rayadesu_dsh_llm_billing_billing_getTurnSpend_parameter_0$schema = () => (_rayadesu_dsh_llm_billing_billing_getTurnSpend_parameter_0$schema$value ??= z.intersection(z.string(), z.unknown()))
|
|
@@ -130,7 +140,7 @@ export const TYPERT_REMOTE = {
|
|
|
130
140
|
typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekBalance',
|
|
131
141
|
create: _rayadesu_dsh_llm_billing_billing_getBalance_result$schema,
|
|
132
142
|
},
|
|
133
|
-
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":
|
|
143
|
+
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":258,"column":3},
|
|
134
144
|
},
|
|
135
145
|
{
|
|
136
146
|
id: '@rayadesu/dsh-llm-billing#billing/getDelegatedSpend',
|
|
@@ -166,7 +176,7 @@ export const TYPERT_REMOTE = {
|
|
|
166
176
|
typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekDelegatedSpend',
|
|
167
177
|
create: _rayadesu_dsh_llm_billing_billing_getDelegatedSpend_result$schema,
|
|
168
178
|
},
|
|
169
|
-
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":
|
|
179
|
+
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":312,"column":3},
|
|
170
180
|
},
|
|
171
181
|
{
|
|
172
182
|
id: '@rayadesu/dsh-llm-billing#billing/getSessionSpend',
|
|
@@ -191,7 +201,7 @@ export const TYPERT_REMOTE = {
|
|
|
191
201
|
typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekSessionSpend',
|
|
192
202
|
create: _rayadesu_dsh_llm_billing_billing_getSessionSpend_result$schema,
|
|
193
203
|
},
|
|
194
|
-
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":
|
|
204
|
+
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":269,"column":3},
|
|
195
205
|
},
|
|
196
206
|
{
|
|
197
207
|
id: '@rayadesu/dsh-llm-billing#billing/getSessionTurnSpends',
|
|
@@ -216,7 +226,7 @@ export const TYPERT_REMOTE = {
|
|
|
216
226
|
typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekSessionTurnSpends',
|
|
217
227
|
create: _rayadesu_dsh_llm_billing_billing_getSessionTurnSpends_result$schema,
|
|
218
228
|
},
|
|
219
|
-
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":
|
|
229
|
+
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":337,"column":3},
|
|
220
230
|
},
|
|
221
231
|
{
|
|
222
232
|
id: '@rayadesu/dsh-llm-billing#billing/getTodaySessionsSpend',
|
|
@@ -242,7 +252,7 @@ export const TYPERT_REMOTE = {
|
|
|
242
252
|
typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekTodaySessionsSpend',
|
|
243
253
|
create: _rayadesu_dsh_llm_billing_billing_getTodaySessionsSpend_result$schema,
|
|
244
254
|
},
|
|
245
|
-
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":
|
|
255
|
+
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":297,"column":3},
|
|
246
256
|
},
|
|
247
257
|
{
|
|
248
258
|
id: '@rayadesu/dsh-llm-billing#billing/getTodaySpend',
|
|
@@ -268,7 +278,7 @@ export const TYPERT_REMOTE = {
|
|
|
268
278
|
typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekTodaySpend',
|
|
269
279
|
create: _rayadesu_dsh_llm_billing_billing_getTodaySpend_result$schema,
|
|
270
280
|
},
|
|
271
|
-
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":
|
|
281
|
+
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":282,"column":3},
|
|
272
282
|
},
|
|
273
283
|
{
|
|
274
284
|
id: '@rayadesu/dsh-llm-billing#billing/getTurnSpend',
|
|
@@ -303,7 +313,7 @@ export const TYPERT_REMOTE = {
|
|
|
303
313
|
typeSymbol: '@rayadesu/dsh-llm-billing/types#DeepSeekTurnSpend',
|
|
304
314
|
create: _rayadesu_dsh_llm_billing_billing_getTurnSpend_result$schema,
|
|
305
315
|
},
|
|
306
|
-
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":
|
|
316
|
+
sourceLocation: {"file":"packages/llm-billing/src/balance.ts","line":325,"column":3},
|
|
307
317
|
},
|
|
308
318
|
],
|
|
309
319
|
}
|
package/lib/types/balance.d.ts
CHANGED
|
@@ -17,8 +17,18 @@ import type { DeepSeekBalance, DeepSeekDelegatedSpend, DeepSeekSessionSpend, Dee
|
|
|
17
17
|
* @throws {@link LlmError} with code `TRANSPORT` when the body is malformed.
|
|
18
18
|
*/
|
|
19
19
|
export declare function parseDeepSeekBalance(body: unknown): DeepSeekBalance;
|
|
20
|
+
/** Attempts one balance fetch makes before giving up on a transient fault. */
|
|
21
|
+
export declare const BALANCE_FETCH_ATTEMPTS = 3;
|
|
22
|
+
/** The first retry's delay in milliseconds; doubled for each further attempt. */
|
|
23
|
+
export declare const BALANCE_RETRY_BASE_MS = 150;
|
|
20
24
|
/**
|
|
21
25
|
* Fetch one account-balance snapshot from `{baseURL}/user/balance`.
|
|
26
|
+
*
|
|
27
|
+
* Retries the faults that can plausibly clear — a dropped socket, a 429, a 5xx
|
|
28
|
+
* — up to {@link BALANCE_FETCH_ATTEMPTS} attempts, because the badge polls
|
|
29
|
+
* every few minutes and one lost packet would otherwise blank the balance
|
|
30
|
+
* until the next interval. Permanent answers (including a rejected key) fail
|
|
31
|
+
* on the first attempt.
|
|
22
32
|
* @param baseURL - resolved endpoint base; `/user/balance` is appended.
|
|
23
33
|
* @param apiKey - resolved bearer token for this endpoint.
|
|
24
34
|
* @param signal - optional cancellation.
|
package/lib/types/balance.js
CHANGED
|
@@ -87,15 +87,41 @@ export function parseDeepSeekBalance(body) {
|
|
|
87
87
|
});
|
|
88
88
|
return { isAvailable, lines };
|
|
89
89
|
}
|
|
90
|
+
/** Attempts one balance fetch makes before giving up on a transient fault. */
|
|
91
|
+
export const BALANCE_FETCH_ATTEMPTS = 3;
|
|
92
|
+
/** The first retry's delay in milliseconds; doubled for each further attempt. */
|
|
93
|
+
export const BALANCE_RETRY_BASE_MS = 150;
|
|
90
94
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* @returns the validated balance snapshot.
|
|
96
|
-
* @throws {@link LlmError} for transport, HTTP, or malformed-response failures.
|
|
95
|
+
* Whether another attempt can plausibly succeed. Only faults transient BY
|
|
96
|
+
* NATURE qualify: a dropped socket (no status), a rate limit, or a server-side
|
|
97
|
+
* error. A settled 4xx is final — retrying a bad key three times only reports
|
|
98
|
+
* the truth three times later.
|
|
97
99
|
*/
|
|
98
|
-
|
|
100
|
+
function isRetryableStatus(status) {
|
|
101
|
+
if (status === undefined)
|
|
102
|
+
return true;
|
|
103
|
+
return status === 429 || status >= 500;
|
|
104
|
+
}
|
|
105
|
+
/** Wait `ms` before the next attempt, abandoning the wait if the caller aborts. */
|
|
106
|
+
function waitBalanceRetry(ms, signal) {
|
|
107
|
+
return new Promise((resolve, reject) => {
|
|
108
|
+
if (signal?.aborted === true) {
|
|
109
|
+
reject(signal.reason);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const onAbort = () => {
|
|
113
|
+
clearTimeout(timer);
|
|
114
|
+
reject(signal?.reason);
|
|
115
|
+
};
|
|
116
|
+
const timer = setTimeout(() => {
|
|
117
|
+
signal?.removeEventListener('abort', onAbort);
|
|
118
|
+
resolve();
|
|
119
|
+
}, ms);
|
|
120
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/** One balance request attempt, REPORTED rather than thrown so the caller decides. */
|
|
124
|
+
async function requestBalanceOnce(baseURL, apiKey, signal) {
|
|
99
125
|
let response;
|
|
100
126
|
try {
|
|
101
127
|
response = await fetch(`${baseURL}/user/balance`, {
|
|
@@ -108,21 +134,61 @@ export async function fetchDeepSeekBalance(baseURL, apiKey, signal) {
|
|
|
108
134
|
});
|
|
109
135
|
}
|
|
110
136
|
catch (error) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
137
|
+
const wrapped = signal?.aborted === true
|
|
138
|
+
? new LlmError('DeepSeek balance request aborted by caller', 'ABORTED', { cause: error })
|
|
139
|
+
: new LlmError(`DeepSeek balance request to ${baseURL} failed`, 'TRANSPORT', { cause: error });
|
|
140
|
+
return { ok: false, status: undefined, error: wrapped };
|
|
114
141
|
}
|
|
115
142
|
if (!response.ok) {
|
|
116
|
-
|
|
143
|
+
return {
|
|
144
|
+
ok: false,
|
|
145
|
+
status: response.status,
|
|
146
|
+
error: new LlmError(`DeepSeek balance request failed (HTTP ${response.status})`, httpErrorCode(response.status), { status: response.status }),
|
|
147
|
+
};
|
|
117
148
|
}
|
|
118
149
|
let body;
|
|
119
150
|
try {
|
|
120
151
|
body = await response.json();
|
|
121
152
|
}
|
|
122
153
|
catch (error) {
|
|
123
|
-
|
|
154
|
+
// A malformed body is never transient, hence `status` rather than
|
|
155
|
+
// `undefined`: no retry, just the failure.
|
|
156
|
+
return { ok: false, status: response.status, error: new LlmError('DeepSeek balance response was not valid JSON', 'TRANSPORT', { cause: error }) };
|
|
157
|
+
}
|
|
158
|
+
return { ok: true, value: parseDeepSeekBalance(body) };
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Fetch one account-balance snapshot from `{baseURL}/user/balance`.
|
|
162
|
+
*
|
|
163
|
+
* Retries the faults that can plausibly clear — a dropped socket, a 429, a 5xx
|
|
164
|
+
* — up to {@link BALANCE_FETCH_ATTEMPTS} attempts, because the badge polls
|
|
165
|
+
* every few minutes and one lost packet would otherwise blank the balance
|
|
166
|
+
* until the next interval. Permanent answers (including a rejected key) fail
|
|
167
|
+
* on the first attempt.
|
|
168
|
+
* @param baseURL - resolved endpoint base; `/user/balance` is appended.
|
|
169
|
+
* @param apiKey - resolved bearer token for this endpoint.
|
|
170
|
+
* @param signal - optional cancellation.
|
|
171
|
+
* @returns the validated balance snapshot.
|
|
172
|
+
* @throws {@link LlmError} for transport, HTTP, or malformed-response failures.
|
|
173
|
+
*/
|
|
174
|
+
export async function fetchDeepSeekBalance(baseURL, apiKey, signal) {
|
|
175
|
+
for (let attempt = 1;; attempt += 1) {
|
|
176
|
+
const outcome = await requestBalanceOnce(baseURL, apiKey, signal);
|
|
177
|
+
if (outcome.ok)
|
|
178
|
+
return outcome.value;
|
|
179
|
+
const exhausted = attempt >= BALANCE_FETCH_ATTEMPTS;
|
|
180
|
+
const aborted = signal?.aborted === true;
|
|
181
|
+
if (!exhausted && !aborted && isRetryableStatus(outcome.status)) {
|
|
182
|
+
await waitBalanceRetry(BALANCE_RETRY_BASE_MS * 2 ** (attempt - 1), signal);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
// A rejected credential is the one failure a user can act on, and retrying
|
|
186
|
+
// never fixes it: name what to check, not just the status code.
|
|
187
|
+
if (outcome.status === 401 || outcome.status === 403) {
|
|
188
|
+
throw new LlmError(`DeepSeek balance request was rejected (HTTP ${outcome.status}) — the API key in effect for ${baseURL} is missing or unauthorized (check DEEPSEEK_API_KEY)`, httpErrorCode(outcome.status), { status: outcome.status });
|
|
189
|
+
}
|
|
190
|
+
throw outcome.error;
|
|
124
191
|
}
|
|
125
|
-
return parseDeepSeekBalance(body);
|
|
126
192
|
}
|
|
127
193
|
/**
|
|
128
194
|
* Remote-only service exposing the DeepSeek account balance and session spend.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Beijing wall-clock arithmetic (Asia/Shanghai: a fixed UTC+8, no DST) — the
|
|
3
|
+
* calendar fields a sample's rate depends on, and the day boundaries a scan is
|
|
4
|
+
* clipped to. Split out of `billing.ts`: pricing asks this module questions, it
|
|
5
|
+
* never asks about pricing.
|
|
6
|
+
* @module @rayadesu/dsh-llm-billing/beijing-time
|
|
7
|
+
*/
|
|
8
|
+
/** One shifted-timestamp view of a Beijing (UTC+8, no DST) instant. */
|
|
9
|
+
export interface BeijingParts {
|
|
10
|
+
/**
|
|
11
|
+
* The instant in epoch milliseconds. Pricing needs it back to resolve the
|
|
12
|
+
* rate revision in effect at the sample's own timestamp (see
|
|
13
|
+
* {@link ratesAt}), so the view carries it instead of a second parse.
|
|
14
|
+
*/
|
|
15
|
+
time: number;
|
|
16
|
+
/** Beijing hour, `0`–`23`. */
|
|
17
|
+
hour: number;
|
|
18
|
+
/** Beijing weekday as `getUTCDay()`: `0` is Sunday, `6` is Saturday. */
|
|
19
|
+
weekday: number;
|
|
20
|
+
/** Beijing calendar-day key (`YYYY-MM-DD`). */
|
|
21
|
+
dayKey: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Derive the Beijing hour, weekday, and calendar-day key of one timestamp with
|
|
25
|
+
* pure integer arithmetic — every timezone-sensitive read shares this one
|
|
26
|
+
* implementation, so the pieces cannot drift apart. Callers that filter by
|
|
27
|
+
* day and then price the same event reuse the returned view, so each event is
|
|
28
|
+
* parsed exactly once. (The hot fold path runs this per committed event; the
|
|
29
|
+
* previous `Date` + `toISOString().slice()` version allocated a `Date` and a
|
|
30
|
+
* 24-character string per call.)
|
|
31
|
+
* @param time - epoch milliseconds.
|
|
32
|
+
* @throws {RangeError} when `time` is not a finite number.
|
|
33
|
+
*/
|
|
34
|
+
export declare function beijingPartsOf(time: number): BeijingParts;
|
|
35
|
+
/** The Beijing (Asia/Shanghai, UTC+8, no DST) calendar-day key of a timestamp. */
|
|
36
|
+
export declare function beijingDayKey(now: Date): string;
|
|
37
|
+
/**
|
|
38
|
+
* The Beijing calendar day NAMED by `dayKey` as an epoch-millisecond range:
|
|
39
|
+
* `start` inclusive, `end` exclusive. Derived from the key itself rather than
|
|
40
|
+
* from a clock, because a scan prices whatever day it was asked for — today,
|
|
41
|
+
* or a day a caller is replaying.
|
|
42
|
+
*
|
|
43
|
+
* The conversion needs no calendar arithmetic: `Date.UTC` builds midnight UTC
|
|
44
|
+
* of that civil date, and Beijing midnight is a fixed offset earlier (no DST).
|
|
45
|
+
* @param dayKey - a `YYYY-MM-DD` Beijing day key.
|
|
46
|
+
* @returns the day's bounds, or `undefined` when the key is not that shape.
|
|
47
|
+
*/
|
|
48
|
+
export declare function beijingDayRangeOfKey(dayKey: string): {
|
|
49
|
+
readonly start: number;
|
|
50
|
+
readonly end: number;
|
|
51
|
+
} | undefined;
|
|
52
|
+
//# sourceMappingURL=beijing-time.d.ts.map
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Beijing wall-clock arithmetic (Asia/Shanghai: a fixed UTC+8, no DST) — the
|
|
3
|
+
* calendar fields a sample's rate depends on, and the day boundaries a scan is
|
|
4
|
+
* clipped to. Split out of `billing.ts`: pricing asks this module questions, it
|
|
5
|
+
* never asks about pricing.
|
|
6
|
+
* @module @rayadesu/dsh-llm-billing/beijing-time
|
|
7
|
+
*/
|
|
8
|
+
/** Beijing is a fixed UTC+8 offset with no DST. */
|
|
9
|
+
const BEIJING_OFFSET_MS = 8 * 3_600_000;
|
|
10
|
+
/** Milliseconds in one day. */
|
|
11
|
+
const DAY_MS = 86_400_000;
|
|
12
|
+
/** Epoch day of 1970-01-01 in the civil-date algorithm below. */
|
|
13
|
+
const CIVIL_EPOCH_DAY = 719_468;
|
|
14
|
+
/** Two-digit zero pad for a calendar field. */
|
|
15
|
+
function pad2(value) {
|
|
16
|
+
return value < 10 ? `0${value}` : String(value);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Civil date of an epoch day (Howard Hinnant's days-from-civil inverse):
|
|
20
|
+
* pure integer arithmetic, no `Date` allocation and no ISO-string slicing.
|
|
21
|
+
*/
|
|
22
|
+
function civilDateOf(epochDay) {
|
|
23
|
+
const shifted = epochDay + CIVIL_EPOCH_DAY;
|
|
24
|
+
const era = Math.floor(shifted / 146_097);
|
|
25
|
+
const dayOfEra = shifted - era * 146_097;
|
|
26
|
+
const yearOfEra = Math.floor((dayOfEra - Math.floor(dayOfEra / 1_460) + Math.floor(dayOfEra / 36_524) - Math.floor(dayOfEra / 146_096)) / 365);
|
|
27
|
+
const year = yearOfEra + era * 400;
|
|
28
|
+
const dayOfYear = dayOfEra - (365 * yearOfEra + Math.floor(yearOfEra / 4) - Math.floor(yearOfEra / 100));
|
|
29
|
+
const monthPrime = Math.floor((5 * dayOfYear + 2) / 153);
|
|
30
|
+
const month = monthPrime + (monthPrime < 10 ? 3 : -9);
|
|
31
|
+
return {
|
|
32
|
+
// January/February belong to the civil year AFTER the era year.
|
|
33
|
+
year: month <= 2 ? year + 1 : year,
|
|
34
|
+
month,
|
|
35
|
+
day: dayOfYear - Math.floor((153 * monthPrime + 2) / 5) + 1,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Derive the Beijing hour, weekday, and calendar-day key of one timestamp with
|
|
40
|
+
* pure integer arithmetic — every timezone-sensitive read shares this one
|
|
41
|
+
* implementation, so the pieces cannot drift apart. Callers that filter by
|
|
42
|
+
* day and then price the same event reuse the returned view, so each event is
|
|
43
|
+
* parsed exactly once. (The hot fold path runs this per committed event; the
|
|
44
|
+
* previous `Date` + `toISOString().slice()` version allocated a `Date` and a
|
|
45
|
+
* 24-character string per call.)
|
|
46
|
+
* @param time - epoch milliseconds.
|
|
47
|
+
* @throws {RangeError} when `time` is not a finite number.
|
|
48
|
+
*/
|
|
49
|
+
export function beijingPartsOf(time) {
|
|
50
|
+
if (!Number.isFinite(time))
|
|
51
|
+
throw new RangeError(`billing: event time is not finite (${String(time)})`);
|
|
52
|
+
const shifted = time + BEIJING_OFFSET_MS;
|
|
53
|
+
const epochDay = Math.floor(shifted / DAY_MS);
|
|
54
|
+
const msOfDay = shifted - epochDay * DAY_MS;
|
|
55
|
+
const civil = civilDateOf(epochDay);
|
|
56
|
+
return {
|
|
57
|
+
time,
|
|
58
|
+
hour: Math.floor(msOfDay / 3_600_000),
|
|
59
|
+
// 1970-01-01 was a Thursday (4).
|
|
60
|
+
weekday: ((epochDay + 4) % 7 + 7) % 7,
|
|
61
|
+
dayKey: `${civil.year}-${pad2(civil.month)}-${pad2(civil.day)}`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** The Beijing (Asia/Shanghai, UTC+8, no DST) calendar-day key of a timestamp. */
|
|
65
|
+
export function beijingDayKey(now) {
|
|
66
|
+
return beijingPartsOf(now.getTime()).dayKey;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The Beijing calendar day NAMED by `dayKey` as an epoch-millisecond range:
|
|
70
|
+
* `start` inclusive, `end` exclusive. Derived from the key itself rather than
|
|
71
|
+
* from a clock, because a scan prices whatever day it was asked for — today,
|
|
72
|
+
* or a day a caller is replaying.
|
|
73
|
+
*
|
|
74
|
+
* The conversion needs no calendar arithmetic: `Date.UTC` builds midnight UTC
|
|
75
|
+
* of that civil date, and Beijing midnight is a fixed offset earlier (no DST).
|
|
76
|
+
* @param dayKey - a `YYYY-MM-DD` Beijing day key.
|
|
77
|
+
* @returns the day's bounds, or `undefined` when the key is not that shape.
|
|
78
|
+
*/
|
|
79
|
+
export function beijingDayRangeOfKey(dayKey) {
|
|
80
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dayKey);
|
|
81
|
+
if (match === null)
|
|
82
|
+
return undefined;
|
|
83
|
+
const utcMidnight = Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
|
|
84
|
+
if (Number.isNaN(utcMidnight))
|
|
85
|
+
return undefined;
|
|
86
|
+
return { start: utcMidnight - BEIJING_OFFSET_MS, end: utcMidnight + DAY_MS - BEIJING_OFFSET_MS };
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=beijing-time.js.map
|