dsh-usage-state 0.4.4 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +99 -52
- package/README.en.md +62 -127
- package/README.md +59 -156
- package/docs/adapters.md +65 -77
- package/lib/client.js +209 -64
- package/lib/index.js +343 -179
- package/lib/typert.js +1 -0
- package/package.json +17 -8
package/lib/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
4
5
|
import z from "@deepseek-ai/schemastery";
|
|
5
6
|
//#region src/shared/config.ts
|
|
6
7
|
const DEFAULT_CONFIG = {
|
|
@@ -198,7 +199,7 @@ const HOST_HINTS = [
|
|
|
198
199
|
},
|
|
199
200
|
{
|
|
200
201
|
sourceId: "kimi",
|
|
201
|
-
pattern: /(^|\.)(api\.kimi\.com|api\.moonshot\.cn|moonshot\.cn)$/
|
|
202
|
+
pattern: /(^|\.)(api\.kimi\.(com|ai)|api\.moonshot\.(cn|ai)|moonshot\.cn)$/
|
|
202
203
|
},
|
|
203
204
|
{
|
|
204
205
|
sourceId: "opencode",
|
|
@@ -224,6 +225,111 @@ function suggestSourceId(providerId, baseUrl) {
|
|
|
224
225
|
return "sub2api";
|
|
225
226
|
}
|
|
226
227
|
//#endregion
|
|
228
|
+
//#region src/shared/providers.ts
|
|
229
|
+
/**
|
|
230
|
+
* The scheme+host of a declared endpoint. A DSH provider profile's `baseURL` is an
|
|
231
|
+
* *API* base (`https://api.z.ai/api/paas/v4`), while every adapter here appends its
|
|
232
|
+
* own path to a bare origin, so only the origin is usable as our endpoint.
|
|
233
|
+
*/
|
|
234
|
+
function originOf(url) {
|
|
235
|
+
if (url === void 0) return void 0;
|
|
236
|
+
try {
|
|
237
|
+
const parsed = new URL(url.trim());
|
|
238
|
+
return parsed.origin === "null" ? void 0 : parsed.origin;
|
|
239
|
+
} catch {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/** Kimi Code and Moonshot use distinct keys and billing endpoints. */
|
|
244
|
+
function kimiMode(provider, endpoint) {
|
|
245
|
+
const origin = originOf(endpoint);
|
|
246
|
+
if (origin !== void 0) {
|
|
247
|
+
const host = new URL(origin).hostname;
|
|
248
|
+
if (host === "api.kimi.com" || host === "api.kimi.ai") return "coding-plan";
|
|
249
|
+
if (host === "api.moonshot.cn" || host === "api.moonshot.ai") return "api";
|
|
250
|
+
}
|
|
251
|
+
return /(?:^|[^a-z])kimi[-_ ]?(?:for[-_ ]?coding|coding|code)(?:$|[^a-z])/i.test(provider) ? "coding-plan" : "api";
|
|
252
|
+
}
|
|
253
|
+
function withTarget(resolution) {
|
|
254
|
+
if (resolution.sourceId === null || resolution.mode === null) return resolution;
|
|
255
|
+
return {
|
|
256
|
+
...resolution,
|
|
257
|
+
key: `${resolution.sourceId}:${resolution.mode}`
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Resolve one provider. `auto` picks the source the provider id (or its endpoint)
|
|
262
|
+
* suggests and that source's primary mode, so a provider DSH already has a key
|
|
263
|
+
* for starts working without touching the settings page.
|
|
264
|
+
*/
|
|
265
|
+
function resolveProvider(input) {
|
|
266
|
+
const entry = input.config.providers[input.provider];
|
|
267
|
+
const sourceId = entry?.sourceId ?? suggestSourceId(input.provider, input.endpointHint) ?? null;
|
|
268
|
+
const defaults = sourceId === null ? void 0 : input.config.sources[sourceId];
|
|
269
|
+
const declared = originOf(input.endpointHint);
|
|
270
|
+
const explicitBaseUrl = entry?.baseUrl ?? defaults?.baseUrl;
|
|
271
|
+
const baseUrl = explicitBaseUrl ?? declared;
|
|
272
|
+
const apiKeyRef = entry?.apiKeyRef ?? defaults?.apiKeyRef;
|
|
273
|
+
const base = {
|
|
274
|
+
provider: input.provider,
|
|
275
|
+
...baseUrl === void 0 ? {} : { baseUrl },
|
|
276
|
+
...explicitBaseUrl === void 0 ? {} : { baseUrlPinned: true },
|
|
277
|
+
...apiKeyRef === void 0 ? {} : { apiKeyRef }
|
|
278
|
+
};
|
|
279
|
+
if (entry?.mode === "hidden") return {
|
|
280
|
+
...base,
|
|
281
|
+
sourceId: null,
|
|
282
|
+
mode: null,
|
|
283
|
+
reason: "hidden"
|
|
284
|
+
};
|
|
285
|
+
if (sourceId === null) return {
|
|
286
|
+
...base,
|
|
287
|
+
sourceId: null,
|
|
288
|
+
mode: null,
|
|
289
|
+
reason: "unknown-source"
|
|
290
|
+
};
|
|
291
|
+
const source = input.catalog.find((candidate) => candidate.id === sourceId);
|
|
292
|
+
if (source === void 0) return {
|
|
293
|
+
...base,
|
|
294
|
+
sourceId,
|
|
295
|
+
mode: null,
|
|
296
|
+
reason: "unknown-source"
|
|
297
|
+
};
|
|
298
|
+
if (entry?.mode === "api" || entry?.mode === "coding-plan") {
|
|
299
|
+
if (!source.modes.includes(entry.mode)) return {
|
|
300
|
+
...base,
|
|
301
|
+
sourceId,
|
|
302
|
+
mode: null,
|
|
303
|
+
reason: "unsupported"
|
|
304
|
+
};
|
|
305
|
+
return withTarget({
|
|
306
|
+
...base,
|
|
307
|
+
sourceId,
|
|
308
|
+
mode: entry.mode,
|
|
309
|
+
reason: "configured"
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
if (source.requiresBaseUrl && baseUrl === void 0) return {
|
|
313
|
+
...base,
|
|
314
|
+
sourceId,
|
|
315
|
+
mode: null,
|
|
316
|
+
reason: "needs-endpoint"
|
|
317
|
+
};
|
|
318
|
+
const mode = sourceId === "kimi" ? kimiMode(input.provider, baseUrl) : source.modes[0];
|
|
319
|
+
if (mode === void 0 || !source.modes.includes(mode)) return {
|
|
320
|
+
...base,
|
|
321
|
+
sourceId,
|
|
322
|
+
mode: null,
|
|
323
|
+
reason: "unsupported"
|
|
324
|
+
};
|
|
325
|
+
return withTarget({
|
|
326
|
+
...base,
|
|
327
|
+
sourceId,
|
|
328
|
+
mode,
|
|
329
|
+
reason: "auto"
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
//#endregion
|
|
227
333
|
//#region src/host/sources/normalize.ts
|
|
228
334
|
/** Shared normalization helpers for provider payloads. */
|
|
229
335
|
/** Numbers and numeric strings to a finite number; anything else is absent. */
|
|
@@ -309,10 +415,12 @@ function pickBalanceInfo(infos) {
|
|
|
309
415
|
for (const raw of infos) {
|
|
310
416
|
if (raw === null || typeof raw !== "object") continue;
|
|
311
417
|
const entry = raw;
|
|
418
|
+
const amount = toFiniteNumber(entry.total_balance);
|
|
419
|
+
if (amount === void 0) continue;
|
|
312
420
|
const granted = toFiniteNumber(entry.granted_balance);
|
|
313
421
|
const toppedUp = toFiniteNumber(entry.topped_up_balance);
|
|
314
422
|
entries.push({
|
|
315
|
-
amount
|
|
423
|
+
amount,
|
|
316
424
|
currency: typeof entry.currency === "string" ? entry.currency : "",
|
|
317
425
|
...granted === void 0 ? {} : { granted },
|
|
318
426
|
...toppedUp === void 0 ? {} : { toppedUp }
|
|
@@ -363,7 +471,9 @@ const KIMI_CLI_USER_AGENT = "KimiCLI/1.6";
|
|
|
363
471
|
const WINDOW_RANK$2 = {
|
|
364
472
|
"5h": 0,
|
|
365
473
|
"1d": 1,
|
|
366
|
-
"7d": 2
|
|
474
|
+
"7d": 2,
|
|
475
|
+
month: 3,
|
|
476
|
+
"month-code": 4
|
|
367
477
|
};
|
|
368
478
|
function asRecord$4(value) {
|
|
369
479
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
@@ -375,35 +485,36 @@ function firstFinite(candidates) {
|
|
|
375
485
|
}
|
|
376
486
|
}
|
|
377
487
|
/**
|
|
378
|
-
* Moonshot's balance
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
* Verified against a real key before trusting the absolute value.
|
|
488
|
+
* Moonshot's official /v1/users/me/balance fields are already in yuan.
|
|
489
|
+
* https://platform.kimi.com/docs/api/balance
|
|
490
|
+
* Amount size never changes the unit; retain the existing two-decimal rounding.
|
|
382
491
|
*/
|
|
383
492
|
function moonshotBalanceToYuan(value) {
|
|
384
|
-
|
|
385
|
-
return Math.round(yuan * 100) / 100;
|
|
493
|
+
return Math.round(value * 100) / 100;
|
|
386
494
|
}
|
|
387
495
|
/** Used percentage from an explicit used/limit pair, or by inverting remaining. */
|
|
388
496
|
function percentFrom(record) {
|
|
389
497
|
const limit = toFiniteNumber(record.limit);
|
|
390
498
|
if (limit === void 0 || limit <= 0) return null;
|
|
391
499
|
const used = toFiniteNumber(record.used);
|
|
392
|
-
if (used !== void 0) return clampPercent(used / limit * 100);
|
|
500
|
+
if (used !== void 0 && used >= 0) return clampPercent(Math.min(1, used / limit) * 100);
|
|
393
501
|
const remaining = toFiniteNumber(record.remaining);
|
|
394
|
-
if (remaining !== void 0) return clampPercent((limit - remaining) / limit * 100);
|
|
502
|
+
if (remaining !== void 0 && remaining >= 0 && remaining <= limit) return clampPercent((limit - remaining) / limit * 100);
|
|
395
503
|
return null;
|
|
396
504
|
}
|
|
397
505
|
/** `{ duration: 5, timeUnit: 'hour' }` -> `5h`, and weekly variants -> `7d`. */
|
|
398
506
|
function windowIdFrom(window) {
|
|
399
507
|
if (window === void 0) return void 0;
|
|
400
|
-
const unit = typeof window.timeUnit === "string" ? window.timeUnit.trim().toLowerCase() : "";
|
|
508
|
+
const unit = typeof window.timeUnit === "string" ? window.timeUnit.trim().toLowerCase().replace(/^time_unit_/, "") : "";
|
|
401
509
|
const duration = toFiniteNumber(window.duration);
|
|
402
510
|
if (unit === "" || duration === void 0 || duration <= 0) return void 0;
|
|
403
|
-
if (unit.startsWith("hour")) return `${duration}h`;
|
|
511
|
+
if (unit.startsWith("hour")) return duration % 24 === 0 ? `${duration / 24}d` : `${duration}h`;
|
|
404
512
|
if (unit.startsWith("day")) return duration === 7 ? "7d" : `${duration}d`;
|
|
405
513
|
if (unit.startsWith("week")) return duration === 1 ? "7d" : `${duration * 7}d`;
|
|
406
|
-
if (unit.startsWith("minute")) return
|
|
514
|
+
if (unit.startsWith("minute")) return windowIdFrom({
|
|
515
|
+
duration: duration / 60,
|
|
516
|
+
timeUnit: "hour"
|
|
517
|
+
});
|
|
407
518
|
if (unit.startsWith("month")) return "30d";
|
|
408
519
|
return `${duration}${unit.slice(0, 4)}`;
|
|
409
520
|
}
|
|
@@ -413,6 +524,7 @@ function sortWindows$2(windows) {
|
|
|
413
524
|
/** `GET /v1/users/me/balance` — pay-as-you-go wallet, CNY. */
|
|
414
525
|
function parseMoonshotBalance(payload) {
|
|
415
526
|
const root = asRecord$4(payload);
|
|
527
|
+
rejectErrorEnvelope(root);
|
|
416
528
|
const nested = root === void 0 ? void 0 : asRecord$4(root.data);
|
|
417
529
|
const value = firstFinite([
|
|
418
530
|
root?.available_balance,
|
|
@@ -430,19 +542,44 @@ function parseMoonshotBalance(payload) {
|
|
|
430
542
|
windows: []
|
|
431
543
|
};
|
|
432
544
|
}
|
|
545
|
+
function rejectErrorEnvelope(root) {
|
|
546
|
+
if (root?.error != null || root?.status === false || root?.code !== void 0 && root.code !== 0 && root.code !== "0") throw new SourceError("parse", "Kimi response contains an API error");
|
|
547
|
+
}
|
|
433
548
|
/**
|
|
434
549
|
* `GET /coding/v1/usages` — Kimi Code subscription. The top-level `usage` is the
|
|
435
|
-
* weekly quota; `limits[]` carries
|
|
436
|
-
*
|
|
550
|
+
* weekly quota in legacy responses; `limits[]` carries rolling windows. Current
|
|
551
|
+
* official client reads ratio pools in `usages` (see docs/adapters.md).
|
|
437
552
|
*/
|
|
438
553
|
function parseKimiCodeUsage(payload) {
|
|
439
554
|
const root = asRecord$4(payload);
|
|
440
555
|
if (root === void 0) throw new SourceError("parse", "Kimi Code usage response is not a JSON object");
|
|
556
|
+
rejectErrorEnvelope(root);
|
|
441
557
|
const found = /* @__PURE__ */ new Map();
|
|
558
|
+
const usages = asRecord$4(root.usages);
|
|
559
|
+
for (const [field, id] of [
|
|
560
|
+
["limit_5h", "5h"],
|
|
561
|
+
["limit_7d", "7d"],
|
|
562
|
+
["limit_month_total", "month"],
|
|
563
|
+
["limit_month_code", "month-code"]
|
|
564
|
+
]) {
|
|
565
|
+
const pool = asRecord$4(usages?.[field]);
|
|
566
|
+
const ratio = toFiniteNumber(pool?.used_ratio);
|
|
567
|
+
if (ratio === void 0 || ratio < 0) continue;
|
|
568
|
+
const usedPercent = clampPercent(Math.min(1, ratio) * 100);
|
|
569
|
+
const resetsAt = normalizeResetAt(pool?.reset_time);
|
|
570
|
+
found.set(id, resetsAt === void 0 ? {
|
|
571
|
+
id,
|
|
572
|
+
usedPercent
|
|
573
|
+
} : {
|
|
574
|
+
id,
|
|
575
|
+
usedPercent,
|
|
576
|
+
resetsAt
|
|
577
|
+
});
|
|
578
|
+
}
|
|
442
579
|
const usage = asRecord$4(root.usage);
|
|
443
580
|
if (usage !== void 0) {
|
|
444
581
|
const usedPercent = percentFrom(usage);
|
|
445
|
-
if (usedPercent !== null) {
|
|
582
|
+
if (usedPercent !== null && !found.has("7d")) {
|
|
446
583
|
const resetsAt = normalizeResetAt(usage.resetTime ?? usage.reset_at ?? usage.resetsAt);
|
|
447
584
|
found.set("7d", resetsAt === void 0 ? {
|
|
448
585
|
id: "7d",
|
|
@@ -500,7 +637,8 @@ const kimi = {
|
|
|
500
637
|
},
|
|
501
638
|
request(input) {
|
|
502
639
|
const codingPlan = input.mode === "coding-plan";
|
|
503
|
-
const
|
|
640
|
+
const normalized = normalizeBaseUrl(input.baseUrl) ?? (codingPlan ? KIMI_CODE_BASE_URL : MOONSHOT_BASE_URL);
|
|
641
|
+
const base = codingPlan ? normalized.replace(/\/coding$/i, "") : normalized;
|
|
504
642
|
return {
|
|
505
643
|
url: codingPlan ? `${base}/coding/v1/usages` : `${base}/v1/users/me/balance`,
|
|
506
644
|
headers: {
|
|
@@ -1128,15 +1266,23 @@ function providerCredentialRefs(input) {
|
|
|
1128
1266
|
for (const [providerId, raw] of Object.entries(providers)) {
|
|
1129
1267
|
const profile = asRecord(raw);
|
|
1130
1268
|
if (profile === void 0) continue;
|
|
1131
|
-
|
|
1269
|
+
const baseUrl = typeof profile.baseURL === "string" ? profile.baseURL : void 0;
|
|
1270
|
+
if (suggest(providerId, baseUrl) !== input.sourceId) continue;
|
|
1271
|
+
if (input.sourceId === "kimi" && input.mode !== void 0) {
|
|
1272
|
+
const entry = input.config?.providers[providerId];
|
|
1273
|
+
const choice = entry?.mode;
|
|
1274
|
+
if (choice === "hidden") continue;
|
|
1275
|
+
const endpoint = entry?.baseUrl ?? input.config?.sources.kimi?.baseUrl ?? baseUrl;
|
|
1276
|
+
if ((choice === "api" || choice === "coding-plan" ? choice : kimiMode(providerId, endpoint)) !== input.mode) continue;
|
|
1277
|
+
}
|
|
1132
1278
|
push(profile.apiKeyEnv);
|
|
1133
1279
|
}
|
|
1134
1280
|
return refs;
|
|
1135
1281
|
}
|
|
1136
1282
|
/**
|
|
1137
1283
|
* The endpoint each configured DSH provider declares (`llm-pi-ai` profiles carry a
|
|
1138
|
-
* `baseURL`).
|
|
1139
|
-
*
|
|
1284
|
+
* `baseURL`). Its origin also supplies the account request endpoint, and a
|
|
1285
|
+
* sanitized origin is shared with the browser for consistent resolution.
|
|
1140
1286
|
*/
|
|
1141
1287
|
function providerEndpointHints(piAiSettings) {
|
|
1142
1288
|
const providers = asRecord(asRecord(piAiSettings)?.providers);
|
|
@@ -1206,7 +1352,8 @@ async function fetchPayload(request, deps) {
|
|
|
1206
1352
|
try {
|
|
1207
1353
|
return await response.json();
|
|
1208
1354
|
} catch (error) {
|
|
1209
|
-
throw new SourceError("parse", `invalid JSON: ${messageOf(error)}`);
|
|
1355
|
+
if (error instanceof SyntaxError) throw new SourceError("parse", `invalid JSON: ${messageOf(error)}`);
|
|
1356
|
+
throw new SourceError("network", messageOf(error));
|
|
1210
1357
|
}
|
|
1211
1358
|
}
|
|
1212
1359
|
/** Adapt `readUsage` to the shape `UsageStateStore` expects. */
|
|
@@ -1221,16 +1368,25 @@ function createTargetReader(deps) {
|
|
|
1221
1368
|
}
|
|
1222
1369
|
//#endregion
|
|
1223
1370
|
//#region src/host/refresh.ts
|
|
1224
|
-
function toSnapshotError(error) {
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1371
|
+
function toSnapshotError(error, secret) {
|
|
1372
|
+
const kind = error instanceof SourceError ? error.kind : "unknown";
|
|
1373
|
+
if (!(error instanceof Error)) return { kind };
|
|
1374
|
+
const message = secret === void 0 || secret === "" ? error.message : error.message.replaceAll(secret, "[redacted]");
|
|
1375
|
+
return message === "" ? { kind } : {
|
|
1376
|
+
kind,
|
|
1377
|
+
detail: message
|
|
1228
1378
|
};
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1379
|
+
}
|
|
1380
|
+
function targetSignature(target) {
|
|
1381
|
+
return JSON.stringify(target === void 0 ? null : [
|
|
1382
|
+
target.key,
|
|
1383
|
+
target.sourceId,
|
|
1384
|
+
target.mode,
|
|
1385
|
+
target.baseUrl,
|
|
1386
|
+
target.baseUrlPinned === true,
|
|
1387
|
+
target.apiKeyRef,
|
|
1388
|
+
target.preferredRefs
|
|
1389
|
+
]);
|
|
1234
1390
|
}
|
|
1235
1391
|
/**
|
|
1236
1392
|
* Holds the last known reading of every configured source and decides when to
|
|
@@ -1247,23 +1403,56 @@ var UsageStateStore = class {
|
|
|
1247
1403
|
deps;
|
|
1248
1404
|
readings = /* @__PURE__ */ new Map();
|
|
1249
1405
|
succeededAt = /* @__PURE__ */ new Map();
|
|
1406
|
+
identities = /* @__PURE__ */ new Map();
|
|
1407
|
+
generations = /* @__PURE__ */ new Map();
|
|
1408
|
+
targetSignatures = /* @__PURE__ */ new Map();
|
|
1250
1409
|
inflight = /* @__PURE__ */ new Map();
|
|
1251
1410
|
turnEndCancel;
|
|
1252
1411
|
idleCancel;
|
|
1412
|
+
idleIntervalMs;
|
|
1413
|
+
stopped = false;
|
|
1253
1414
|
constructor(deps) {
|
|
1254
1415
|
this.deps = deps;
|
|
1255
1416
|
}
|
|
1417
|
+
invalidate(key) {
|
|
1418
|
+
this.generations.set(key, (this.generations.get(key) ?? 0) + 1);
|
|
1419
|
+
this.identities.delete(key);
|
|
1420
|
+
this.readings.delete(key);
|
|
1421
|
+
this.succeededAt.delete(key);
|
|
1422
|
+
this.inflight.delete(key);
|
|
1423
|
+
}
|
|
1424
|
+
currentTargets() {
|
|
1425
|
+
const targets = this.deps.targets().map((target) => ({ ...target }));
|
|
1426
|
+
const active = new Set(targets.map((target) => target.key));
|
|
1427
|
+
for (const key of this.targetSignatures.keys()) {
|
|
1428
|
+
if (active.has(key)) continue;
|
|
1429
|
+
this.invalidate(key);
|
|
1430
|
+
this.targetSignatures.delete(key);
|
|
1431
|
+
}
|
|
1432
|
+
for (const target of targets) {
|
|
1433
|
+
const signature = targetSignature(target);
|
|
1434
|
+
const previous = this.targetSignatures.get(target.key);
|
|
1435
|
+
if (previous !== void 0 && previous !== signature) this.invalidate(target.key);
|
|
1436
|
+
this.targetSignatures.set(target.key, signature);
|
|
1437
|
+
}
|
|
1438
|
+
return targets;
|
|
1439
|
+
}
|
|
1256
1440
|
/** Everything known so far, keyed by target key. */
|
|
1257
1441
|
snapshots() {
|
|
1442
|
+
if (this.stopped) return {};
|
|
1443
|
+
this.currentTargets();
|
|
1258
1444
|
const result = {};
|
|
1259
1445
|
for (const [key, snapshot] of this.readings) result[key] = snapshot;
|
|
1260
1446
|
return result;
|
|
1261
1447
|
}
|
|
1262
1448
|
snapshot(key) {
|
|
1449
|
+
if (this.stopped) return void 0;
|
|
1450
|
+
this.currentTargets();
|
|
1263
1451
|
return this.readings.get(key);
|
|
1264
1452
|
}
|
|
1265
1453
|
/** Refresh every configured target; failures are captured, never thrown. */
|
|
1266
1454
|
async refreshAll() {
|
|
1455
|
+
if (this.stopped) return;
|
|
1267
1456
|
await Promise.all(this.deps.targets().map((target) => this.refresh(target.key)));
|
|
1268
1457
|
}
|
|
1269
1458
|
/**
|
|
@@ -1271,45 +1460,83 @@ var UsageStateStore = class {
|
|
|
1271
1460
|
* not elapsed yet, unless `force` is set.
|
|
1272
1461
|
*/
|
|
1273
1462
|
async refresh(key, options = {}) {
|
|
1274
|
-
|
|
1275
|
-
if (target === void 0) return this.fail({
|
|
1276
|
-
key,
|
|
1463
|
+
if (this.stopped) return {
|
|
1277
1464
|
sourceId: key.split(":")[0] ?? "",
|
|
1278
|
-
mode: "api"
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1465
|
+
mode: key.endsWith(":coding-plan") ? "coding-plan" : "api",
|
|
1466
|
+
balances: [],
|
|
1467
|
+
windows: [],
|
|
1468
|
+
fetchedAt: this.deps.clock.now(),
|
|
1469
|
+
error: {
|
|
1470
|
+
kind: "config",
|
|
1471
|
+
detail: "refresh stopped"
|
|
1472
|
+
}
|
|
1473
|
+
};
|
|
1474
|
+
const target = this.currentTargets().find((candidate) => candidate.key === key);
|
|
1475
|
+
if (target === void 0) return {
|
|
1476
|
+
sourceId: key.split(":")[0] ?? "",
|
|
1477
|
+
mode: key.endsWith(":coding-plan") ? "coding-plan" : "api",
|
|
1478
|
+
balances: [],
|
|
1479
|
+
windows: [],
|
|
1480
|
+
fetchedAt: this.deps.clock.now(),
|
|
1481
|
+
error: {
|
|
1482
|
+
kind: "config",
|
|
1483
|
+
detail: `unknown target ${key}`
|
|
1484
|
+
}
|
|
1485
|
+
};
|
|
1486
|
+
const signature = targetSignature(target);
|
|
1487
|
+
const generationAtLookup = this.generations.get(key) ?? 0;
|
|
1296
1488
|
const source = this.deps.findSource(target.sourceId);
|
|
1297
1489
|
if (source === void 0) return this.fail(target, {
|
|
1298
1490
|
kind: "config",
|
|
1299
1491
|
detail: `unknown source ${target.sourceId}`
|
|
1300
|
-
},
|
|
1492
|
+
}, this.readings.get(key));
|
|
1301
1493
|
let credential;
|
|
1302
1494
|
try {
|
|
1303
1495
|
credential = await this.deps.credentials.resolve(target);
|
|
1304
1496
|
} catch (error) {
|
|
1305
|
-
return this.
|
|
1497
|
+
if (this.stopped) return this.refresh(key, options);
|
|
1498
|
+
if (signature !== targetSignature(this.currentTargets().find((candidate) => candidate.key === key)) || (this.generations.get(key) ?? 0) !== generationAtLookup) return this.refresh(key, options);
|
|
1499
|
+
return this.fail(target, toSnapshotError(error), this.readings.get(key));
|
|
1500
|
+
}
|
|
1501
|
+
if (this.stopped) return this.refresh(key, options);
|
|
1502
|
+
if (signature !== targetSignature(this.currentTargets().find((candidate) => candidate.key === key))) return this.refresh(key, options);
|
|
1503
|
+
const identity = createHash("sha256").update(JSON.stringify([
|
|
1504
|
+
target.sourceId,
|
|
1505
|
+
target.mode,
|
|
1506
|
+
credential?.baseUrl ?? target.baseUrl ?? source.defaultBaseUrl(target.mode),
|
|
1507
|
+
credential?.baseUrlPinned === true || target.baseUrlPinned === true,
|
|
1508
|
+
credential?.ref ?? target.apiKeyRef,
|
|
1509
|
+
credential?.apiKey
|
|
1510
|
+
])).digest("hex");
|
|
1511
|
+
if ((this.generations.get(key) ?? 0) !== generationAtLookup && this.identities.get(key) !== identity) return this.refresh(key, options);
|
|
1512
|
+
if (this.identities.get(key) !== identity) {
|
|
1513
|
+
this.identities.set(key, identity);
|
|
1514
|
+
this.generations.set(key, (this.generations.get(key) ?? 0) + 1);
|
|
1515
|
+
this.readings.delete(key);
|
|
1516
|
+
this.succeededAt.delete(key);
|
|
1517
|
+
this.inflight.delete(key);
|
|
1306
1518
|
}
|
|
1307
1519
|
if (credential === void 0) return this.fail(target, {
|
|
1308
1520
|
kind: "config",
|
|
1309
1521
|
detail: "no credential configured"
|
|
1310
|
-
},
|
|
1522
|
+
}, this.readings.get(key));
|
|
1523
|
+
const cached = this.readings.get(key);
|
|
1524
|
+
const lastSuccess = this.succeededAt.get(key);
|
|
1525
|
+
if (options.force !== true && cached !== void 0 && (options.onlyIfMissing === true || cached.error === void 0 && lastSuccess !== void 0 && this.deps.clock.now() - lastSuccess < this.deps.policy().minIntervalSeconds * 1e3)) return cached;
|
|
1526
|
+
const running = this.inflight.get(key);
|
|
1527
|
+
if (running !== void 0) return running;
|
|
1528
|
+
const generation = this.generations.get(key) ?? 0;
|
|
1529
|
+
const promise = this.run(target, credential, source, generation).finally(() => {
|
|
1530
|
+
if (this.inflight.get(key) === promise) this.inflight.delete(key);
|
|
1531
|
+
});
|
|
1532
|
+
this.inflight.set(key, promise);
|
|
1533
|
+
return promise;
|
|
1534
|
+
}
|
|
1535
|
+
async run(target, credential, source, generation) {
|
|
1536
|
+
const previous = this.readings.get(target.key);
|
|
1311
1537
|
try {
|
|
1312
1538
|
const reading = await this.deps.read(target, credential, source);
|
|
1539
|
+
if (this.generations.get(target.key) !== generation) return this.refresh(target.key);
|
|
1313
1540
|
const fetchedAt = this.deps.clock.now();
|
|
1314
1541
|
const snapshot = {
|
|
1315
1542
|
sourceId: target.sourceId,
|
|
@@ -1322,7 +1549,8 @@ var UsageStateStore = class {
|
|
|
1322
1549
|
this.succeededAt.set(target.key, fetchedAt);
|
|
1323
1550
|
return snapshot;
|
|
1324
1551
|
} catch (error) {
|
|
1325
|
-
|
|
1552
|
+
if (this.generations.get(target.key) !== generation) return this.refresh(target.key);
|
|
1553
|
+
return this.fail(target, toSnapshotError(error, credential.apiKey), previous);
|
|
1326
1554
|
}
|
|
1327
1555
|
}
|
|
1328
1556
|
fail(target, error, previous) {
|
|
@@ -1353,10 +1581,22 @@ var UsageStateStore = class {
|
|
|
1353
1581
|
});
|
|
1354
1582
|
this.turnEndCancel = cancel;
|
|
1355
1583
|
}
|
|
1584
|
+
/** Apply a live policy edit without leaving the previous idle timer running. */
|
|
1585
|
+
reconfigure() {
|
|
1586
|
+
if (this.stopped) return;
|
|
1587
|
+
this.currentTargets();
|
|
1588
|
+
if (this.idleCancel === void 0) return;
|
|
1589
|
+
if (this.idleIntervalMs === this.deps.policy().intervalMinutes * 6e4) return;
|
|
1590
|
+
this.idleCancel();
|
|
1591
|
+
this.idleCancel = void 0;
|
|
1592
|
+
this.start();
|
|
1593
|
+
}
|
|
1356
1594
|
/** Start the idle fallback timer; returns a stop function. */
|
|
1357
1595
|
start() {
|
|
1596
|
+
this.stopped = false;
|
|
1358
1597
|
if (this.idleCancel === void 0) {
|
|
1359
1598
|
const intervalMs = this.deps.policy().intervalMinutes * 6e4;
|
|
1599
|
+
this.idleIntervalMs = intervalMs;
|
|
1360
1600
|
this.idleCancel = this.deps.clock.every(intervalMs, () => {
|
|
1361
1601
|
this.refreshAll();
|
|
1362
1602
|
});
|
|
@@ -1364,6 +1604,8 @@ var UsageStateStore = class {
|
|
|
1364
1604
|
return () => this.stop();
|
|
1365
1605
|
}
|
|
1366
1606
|
stop() {
|
|
1607
|
+
this.stopped = true;
|
|
1608
|
+
for (const key of this.targetSignatures.keys()) this.invalidate(key);
|
|
1367
1609
|
this.turnEndCancel?.();
|
|
1368
1610
|
this.turnEndCancel = void 0;
|
|
1369
1611
|
this.idleCancel?.();
|
|
@@ -1387,18 +1629,20 @@ var UsageStateService = class {
|
|
|
1387
1629
|
this.deps = deps;
|
|
1388
1630
|
}
|
|
1389
1631
|
/**
|
|
1390
|
-
*
|
|
1391
|
-
*
|
|
1632
|
+
* Initialize a missing account identity, otherwise report cached readings.
|
|
1633
|
+
* The host owns periodic/turn refreshes; polling this method cannot shorten
|
|
1634
|
+
* that cadence. Forcing is reserved for an explicit refresh.
|
|
1392
1635
|
*
|
|
1393
1636
|
* Settled rather than all: one unreachable provider must not blank the whole
|
|
1394
1637
|
* status line for the others.
|
|
1395
1638
|
*/
|
|
1396
1639
|
async getState(force) {
|
|
1397
|
-
const options = force === true ? { force: true } : {};
|
|
1640
|
+
const options = force === true ? { force: true } : { onlyIfMissing: true };
|
|
1398
1641
|
await Promise.allSettled(this.deps.targets().map((target) => this.deps.store.refresh(target.key, options)));
|
|
1399
1642
|
return {
|
|
1400
1643
|
sources: this.deps.catalog(),
|
|
1401
1644
|
snapshots: this.deps.store.snapshots(),
|
|
1645
|
+
...this.deps.endpointHints === void 0 ? {} : { endpointHints: this.deps.endpointHints() },
|
|
1402
1646
|
checkedAt: this.deps.now()
|
|
1403
1647
|
};
|
|
1404
1648
|
}
|
|
@@ -1450,98 +1694,6 @@ function installUsageStateSettings(ctx, reference, onConfig) {
|
|
|
1450
1694
|
});
|
|
1451
1695
|
}
|
|
1452
1696
|
//#endregion
|
|
1453
|
-
//#region src/shared/providers.ts
|
|
1454
|
-
/**
|
|
1455
|
-
* The scheme+host of a declared endpoint. A DSH provider profile's `baseURL` is an
|
|
1456
|
-
* *API* base (`https://api.z.ai/api/paas/v4`), while every adapter here appends its
|
|
1457
|
-
* own path to a bare origin, so only the origin is usable as our endpoint.
|
|
1458
|
-
*/
|
|
1459
|
-
function originOf(url) {
|
|
1460
|
-
if (url === void 0) return void 0;
|
|
1461
|
-
try {
|
|
1462
|
-
const parsed = new URL(url.trim());
|
|
1463
|
-
return parsed.origin === "null" ? void 0 : parsed.origin;
|
|
1464
|
-
} catch {
|
|
1465
|
-
return;
|
|
1466
|
-
}
|
|
1467
|
-
}
|
|
1468
|
-
function withTarget(resolution) {
|
|
1469
|
-
if (resolution.sourceId === null || resolution.mode === null) return resolution;
|
|
1470
|
-
return {
|
|
1471
|
-
...resolution,
|
|
1472
|
-
key: `${resolution.sourceId}:${resolution.mode}`
|
|
1473
|
-
};
|
|
1474
|
-
}
|
|
1475
|
-
/**
|
|
1476
|
-
* Resolve one provider. `auto` picks the source the provider id (or its endpoint)
|
|
1477
|
-
* suggests and that source's primary mode, so a provider DSH already has a key
|
|
1478
|
-
* for starts working without touching the settings page.
|
|
1479
|
-
*/
|
|
1480
|
-
function resolveProvider(input) {
|
|
1481
|
-
const entry = input.config.providers[input.provider];
|
|
1482
|
-
const declared = originOf(input.endpointHint);
|
|
1483
|
-
const baseUrl = entry?.baseUrl ?? declared;
|
|
1484
|
-
const base = {
|
|
1485
|
-
provider: input.provider,
|
|
1486
|
-
...baseUrl === void 0 ? {} : { baseUrl },
|
|
1487
|
-
...entry?.baseUrl === void 0 ? {} : { baseUrlPinned: true },
|
|
1488
|
-
...entry?.apiKeyRef === void 0 ? {} : { apiKeyRef: entry.apiKeyRef }
|
|
1489
|
-
};
|
|
1490
|
-
if (entry?.mode === "hidden") return {
|
|
1491
|
-
...base,
|
|
1492
|
-
sourceId: null,
|
|
1493
|
-
mode: null,
|
|
1494
|
-
reason: "hidden"
|
|
1495
|
-
};
|
|
1496
|
-
const sourceId = entry?.sourceId ?? suggestSourceId(input.provider, input.endpointHint) ?? null;
|
|
1497
|
-
if (sourceId === null) return {
|
|
1498
|
-
...base,
|
|
1499
|
-
sourceId: null,
|
|
1500
|
-
mode: null,
|
|
1501
|
-
reason: "unknown-source"
|
|
1502
|
-
};
|
|
1503
|
-
const source = input.catalog.find((candidate) => candidate.id === sourceId);
|
|
1504
|
-
if (source === void 0) return {
|
|
1505
|
-
...base,
|
|
1506
|
-
sourceId,
|
|
1507
|
-
mode: null,
|
|
1508
|
-
reason: "unknown-source"
|
|
1509
|
-
};
|
|
1510
|
-
if (entry?.mode === "api" || entry?.mode === "coding-plan") {
|
|
1511
|
-
if (!source.modes.includes(entry.mode)) return {
|
|
1512
|
-
...base,
|
|
1513
|
-
sourceId,
|
|
1514
|
-
mode: null,
|
|
1515
|
-
reason: "unsupported"
|
|
1516
|
-
};
|
|
1517
|
-
return withTarget({
|
|
1518
|
-
...base,
|
|
1519
|
-
sourceId,
|
|
1520
|
-
mode: entry.mode,
|
|
1521
|
-
reason: "configured"
|
|
1522
|
-
});
|
|
1523
|
-
}
|
|
1524
|
-
if (source.requiresBaseUrl && baseUrl === void 0) return {
|
|
1525
|
-
...base,
|
|
1526
|
-
sourceId,
|
|
1527
|
-
mode: null,
|
|
1528
|
-
reason: "needs-endpoint"
|
|
1529
|
-
};
|
|
1530
|
-
const mode = source.modes[0];
|
|
1531
|
-
if (mode === void 0) return {
|
|
1532
|
-
...base,
|
|
1533
|
-
sourceId,
|
|
1534
|
-
mode: null,
|
|
1535
|
-
reason: "unsupported"
|
|
1536
|
-
};
|
|
1537
|
-
return withTarget({
|
|
1538
|
-
...base,
|
|
1539
|
-
sourceId,
|
|
1540
|
-
mode,
|
|
1541
|
-
reason: "auto"
|
|
1542
|
-
});
|
|
1543
|
-
}
|
|
1544
|
-
//#endregion
|
|
1545
1697
|
//#region src/host/targets.ts
|
|
1546
1698
|
function targetKey(sourceId, mode) {
|
|
1547
1699
|
return `${sourceId}:${mode}`;
|
|
@@ -1554,7 +1706,8 @@ function targetKey(sourceId, mode) {
|
|
|
1554
1706
|
* line never shows the same account twice. Providers that are hidden, map to no
|
|
1555
1707
|
* known source, or ask for a mode the source cannot serve are skipped silently.
|
|
1556
1708
|
*/
|
|
1557
|
-
function resolveTargets(
|
|
1709
|
+
function resolveTargets(rawConfig, options = {}) {
|
|
1710
|
+
const config = normalizeConfig(rawConfig);
|
|
1558
1711
|
const sources = options.sources ?? ALL_SOURCES;
|
|
1559
1712
|
const catalog = toSourceCatalog(sources);
|
|
1560
1713
|
const targets = [];
|
|
@@ -1570,26 +1723,21 @@ function resolveTargets(config, options = {}) {
|
|
|
1570
1723
|
sourceId,
|
|
1571
1724
|
mode,
|
|
1572
1725
|
...endpoint?.baseUrl === void 0 ? {} : { baseUrl: endpoint.baseUrl },
|
|
1573
|
-
...endpoint?.pinned === true ? { baseUrlPinned: true } : {}
|
|
1726
|
+
...endpoint?.pinned === true ? { baseUrlPinned: true } : {},
|
|
1727
|
+
...endpoint?.apiKeyRef === void 0 ? {} : { apiKeyRef: endpoint.apiKeyRef }
|
|
1574
1728
|
});
|
|
1575
1729
|
};
|
|
1576
1730
|
/**
|
|
1577
|
-
* The live runtime's provider ids, when
|
|
1578
|
-
* not
|
|
1579
|
-
* it must not suppress the configured entries.
|
|
1731
|
+
* The live runtime's provider ids, when known. `undefined` means the host
|
|
1732
|
+
* could not answer; an empty list is authoritative and stops stale targets.
|
|
1580
1733
|
*/
|
|
1581
|
-
const live = options.providers
|
|
1734
|
+
const live = options.providers === void 0 ? void 0 : new Set(options.providers);
|
|
1582
1735
|
/**
|
|
1583
1736
|
* Whether a stored entry may still produce requests. The entry itself is kept — a
|
|
1584
1737
|
* provider that comes back keeps the mode the user chose for it — but a provider
|
|
1585
1738
|
* DSH no longer has must not keep an account being polled behind an invisible row.
|
|
1586
1739
|
*/
|
|
1587
1740
|
const pollable = (provider) => live === void 0 || live.has(provider);
|
|
1588
|
-
for (const entry of config.models) {
|
|
1589
|
-
if (entry.mode === "hidden" || entry.sourceId === null) continue;
|
|
1590
|
-
if (!pollable(entry.provider)) continue;
|
|
1591
|
-
push(entry.sourceId, entry.mode);
|
|
1592
|
-
}
|
|
1593
1741
|
const providers = /* @__PURE__ */ new Set();
|
|
1594
1742
|
for (const provider of [...config.order, ...Object.keys(config.providers)]) if (pollable(provider)) providers.add(provider);
|
|
1595
1743
|
for (const provider of options.providers ?? []) providers.add(provider);
|
|
@@ -1602,7 +1750,8 @@ function resolveTargets(config, options = {}) {
|
|
|
1602
1750
|
});
|
|
1603
1751
|
if (resolution.sourceId !== null && resolution.mode !== null) push(resolution.sourceId, resolution.mode, {
|
|
1604
1752
|
...resolution.baseUrl === void 0 ? {} : { baseUrl: resolution.baseUrl },
|
|
1605
|
-
...resolution.baseUrlPinned === true ? { pinned: true } : {}
|
|
1753
|
+
...resolution.baseUrlPinned === true ? { pinned: true } : {},
|
|
1754
|
+
...resolution.apiKeyRef === void 0 ? {} : { apiKeyRef: resolution.apiKeyRef }
|
|
1606
1755
|
});
|
|
1607
1756
|
}
|
|
1608
1757
|
return targets;
|
|
@@ -1716,8 +1865,10 @@ function createUsageState(ctx, deps = {}) {
|
|
|
1716
1865
|
const catalog = toSourceCatalog(ALL_SOURCES);
|
|
1717
1866
|
const lookup = createCredentialLookup(ctx, deps.credentialFallback ?? {});
|
|
1718
1867
|
let config = DEFAULT_CONFIG;
|
|
1868
|
+
let reconfigure = () => {};
|
|
1719
1869
|
installUsageStateSettings(ctx, deps.config, (next) => {
|
|
1720
1870
|
config = next;
|
|
1871
|
+
reconfigure();
|
|
1721
1872
|
});
|
|
1722
1873
|
/**
|
|
1723
1874
|
* The plugin's own configuration says nothing about which providers exist, so
|
|
@@ -1725,11 +1876,16 @@ function createUsageState(ctx, deps = {}) {
|
|
|
1725
1876
|
* provider nobody configured still resolves to its suggested source.
|
|
1726
1877
|
*/
|
|
1727
1878
|
const providerFacts = () => {
|
|
1728
|
-
|
|
1729
|
-
const ids = [];
|
|
1879
|
+
let ids;
|
|
1730
1880
|
try {
|
|
1731
|
-
|
|
1732
|
-
|
|
1881
|
+
const llm = ctx.get("llm");
|
|
1882
|
+
if (typeof llm?.listProviders === "function") {
|
|
1883
|
+
ids = [];
|
|
1884
|
+
for (const provider of llm.listProviders()) if (typeof provider?.id === "string" && provider.id !== "") ids.push(provider.id);
|
|
1885
|
+
}
|
|
1886
|
+
} catch {
|
|
1887
|
+
ids = void 0;
|
|
1888
|
+
}
|
|
1733
1889
|
return {
|
|
1734
1890
|
ids,
|
|
1735
1891
|
hints: providerEndpointHints(readNamespace(ctx, "llm-pi-ai"))
|
|
@@ -1740,17 +1896,22 @@ function createUsageState(ctx, deps = {}) {
|
|
|
1740
1896
|
return resolveTargets(config, {
|
|
1741
1897
|
providers: ids,
|
|
1742
1898
|
endpointHints: hints
|
|
1743
|
-
})
|
|
1744
|
-
|
|
1745
|
-
const optionsFor = (target) => {
|
|
1746
|
-
const overrideRef = config.sources[target.sourceId]?.apiKeyRef;
|
|
1747
|
-
return {
|
|
1748
|
-
...overrideRef === void 0 ? {} : { overrideRef },
|
|
1899
|
+
}).map((target) => ({
|
|
1900
|
+
...target,
|
|
1749
1901
|
preferredRefs: providerCredentialRefs({
|
|
1750
1902
|
sourceId: target.sourceId,
|
|
1903
|
+
mode: target.mode,
|
|
1904
|
+
config,
|
|
1751
1905
|
deepseekSettings: readNamespace(ctx, "llm-deepseek"),
|
|
1752
1906
|
piAiSettings: readNamespace(ctx, "llm-pi-ai")
|
|
1753
1907
|
})
|
|
1908
|
+
}));
|
|
1909
|
+
};
|
|
1910
|
+
const optionsFor = (target) => {
|
|
1911
|
+
const overrideRef = target.apiKeyRef;
|
|
1912
|
+
return {
|
|
1913
|
+
...overrideRef === void 0 ? {} : { overrideRef },
|
|
1914
|
+
preferredRefs: target.preferredRefs ?? []
|
|
1754
1915
|
};
|
|
1755
1916
|
};
|
|
1756
1917
|
const store = new UsageStateStore({
|
|
@@ -1767,21 +1928,24 @@ function createUsageState(ctx, deps = {}) {
|
|
|
1767
1928
|
if (source === void 0) return void 0;
|
|
1768
1929
|
const resolved = await resolveApiKey(source, target.mode, optionsFor(target), lookup);
|
|
1769
1930
|
if (resolved === void 0) return void 0;
|
|
1770
|
-
|
|
1771
|
-
const baseUrl = pinnedBaseUrl ?? target.baseUrl;
|
|
1772
|
-
if (baseUrl === void 0) return resolved;
|
|
1931
|
+
if (target.baseUrl === void 0) return resolved;
|
|
1773
1932
|
return {
|
|
1774
1933
|
...resolved,
|
|
1775
|
-
baseUrl,
|
|
1776
|
-
...
|
|
1934
|
+
baseUrl: target.baseUrl,
|
|
1935
|
+
...target.baseUrlPinned === true ? { baseUrlPinned: true } : {}
|
|
1777
1936
|
};
|
|
1778
1937
|
} },
|
|
1779
1938
|
read: createTargetReader({ fetch: fetchImpl })
|
|
1780
1939
|
});
|
|
1940
|
+
reconfigure = () => store.reconfigure();
|
|
1781
1941
|
const service = new UsageStateService({
|
|
1782
1942
|
store,
|
|
1783
1943
|
targets,
|
|
1784
1944
|
catalog: () => catalog,
|
|
1945
|
+
endpointHints: () => Object.fromEntries(Object.entries(providerFacts().hints).flatMap(([provider, endpoint]) => {
|
|
1946
|
+
const origin = originOf(endpoint);
|
|
1947
|
+
return origin === void 0 ? [] : [[provider, origin]];
|
|
1948
|
+
})),
|
|
1785
1949
|
describe: async (target) => {
|
|
1786
1950
|
const source = findSource(target.sourceId);
|
|
1787
1951
|
if (source === void 0) return {
|
|
@@ -1795,7 +1959,7 @@ function createUsageState(ctx, deps = {}) {
|
|
|
1795
1959
|
ctx.on("session/event", (...args) => {
|
|
1796
1960
|
if (isTurnEnd(args[1])) store.onTurnEnd();
|
|
1797
1961
|
});
|
|
1798
|
-
store.start();
|
|
1962
|
+
ctx.effect(() => store.start(), "dsh-usage-state: refresh scheduling");
|
|
1799
1963
|
return provideUsageState(ctx, service);
|
|
1800
1964
|
}
|
|
1801
1965
|
/**
|