dsh-usage-state 0.4.4 → 0.4.5
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 +85 -52
- package/README.en.md +61 -128
- package/README.md +58 -157
- package/docs/adapters.md +62 -77
- package/lib/client.js +190 -59
- package/lib/index.js +279 -167
- package/lib/typert.js +1 -0
- package/package.json +16 -7
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 = {
|
|
@@ -224,6 +225,101 @@ 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
|
+
function withTarget(resolution) {
|
|
244
|
+
if (resolution.sourceId === null || resolution.mode === null) return resolution;
|
|
245
|
+
return {
|
|
246
|
+
...resolution,
|
|
247
|
+
key: `${resolution.sourceId}:${resolution.mode}`
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Resolve one provider. `auto` picks the source the provider id (or its endpoint)
|
|
252
|
+
* suggests and that source's primary mode, so a provider DSH already has a key
|
|
253
|
+
* for starts working without touching the settings page.
|
|
254
|
+
*/
|
|
255
|
+
function resolveProvider(input) {
|
|
256
|
+
const entry = input.config.providers[input.provider];
|
|
257
|
+
const sourceId = entry?.sourceId ?? suggestSourceId(input.provider, input.endpointHint) ?? null;
|
|
258
|
+
const defaults = sourceId === null ? void 0 : input.config.sources[sourceId];
|
|
259
|
+
const declared = originOf(input.endpointHint);
|
|
260
|
+
const explicitBaseUrl = entry?.baseUrl ?? defaults?.baseUrl;
|
|
261
|
+
const baseUrl = explicitBaseUrl ?? declared;
|
|
262
|
+
const apiKeyRef = entry?.apiKeyRef ?? defaults?.apiKeyRef;
|
|
263
|
+
const base = {
|
|
264
|
+
provider: input.provider,
|
|
265
|
+
...baseUrl === void 0 ? {} : { baseUrl },
|
|
266
|
+
...explicitBaseUrl === void 0 ? {} : { baseUrlPinned: true },
|
|
267
|
+
...apiKeyRef === void 0 ? {} : { apiKeyRef }
|
|
268
|
+
};
|
|
269
|
+
if (entry?.mode === "hidden") return {
|
|
270
|
+
...base,
|
|
271
|
+
sourceId: null,
|
|
272
|
+
mode: null,
|
|
273
|
+
reason: "hidden"
|
|
274
|
+
};
|
|
275
|
+
if (sourceId === null) return {
|
|
276
|
+
...base,
|
|
277
|
+
sourceId: null,
|
|
278
|
+
mode: null,
|
|
279
|
+
reason: "unknown-source"
|
|
280
|
+
};
|
|
281
|
+
const source = input.catalog.find((candidate) => candidate.id === sourceId);
|
|
282
|
+
if (source === void 0) return {
|
|
283
|
+
...base,
|
|
284
|
+
sourceId,
|
|
285
|
+
mode: null,
|
|
286
|
+
reason: "unknown-source"
|
|
287
|
+
};
|
|
288
|
+
if (entry?.mode === "api" || entry?.mode === "coding-plan") {
|
|
289
|
+
if (!source.modes.includes(entry.mode)) return {
|
|
290
|
+
...base,
|
|
291
|
+
sourceId,
|
|
292
|
+
mode: null,
|
|
293
|
+
reason: "unsupported"
|
|
294
|
+
};
|
|
295
|
+
return withTarget({
|
|
296
|
+
...base,
|
|
297
|
+
sourceId,
|
|
298
|
+
mode: entry.mode,
|
|
299
|
+
reason: "configured"
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
if (source.requiresBaseUrl && baseUrl === void 0) return {
|
|
303
|
+
...base,
|
|
304
|
+
sourceId,
|
|
305
|
+
mode: null,
|
|
306
|
+
reason: "needs-endpoint"
|
|
307
|
+
};
|
|
308
|
+
const mode = source.modes[0];
|
|
309
|
+
if (mode === void 0) return {
|
|
310
|
+
...base,
|
|
311
|
+
sourceId,
|
|
312
|
+
mode: null,
|
|
313
|
+
reason: "unsupported"
|
|
314
|
+
};
|
|
315
|
+
return withTarget({
|
|
316
|
+
...base,
|
|
317
|
+
sourceId,
|
|
318
|
+
mode,
|
|
319
|
+
reason: "auto"
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
//#endregion
|
|
227
323
|
//#region src/host/sources/normalize.ts
|
|
228
324
|
/** Shared normalization helpers for provider payloads. */
|
|
229
325
|
/** Numbers and numeric strings to a finite number; anything else is absent. */
|
|
@@ -309,10 +405,12 @@ function pickBalanceInfo(infos) {
|
|
|
309
405
|
for (const raw of infos) {
|
|
310
406
|
if (raw === null || typeof raw !== "object") continue;
|
|
311
407
|
const entry = raw;
|
|
408
|
+
const amount = toFiniteNumber(entry.total_balance);
|
|
409
|
+
if (amount === void 0) continue;
|
|
312
410
|
const granted = toFiniteNumber(entry.granted_balance);
|
|
313
411
|
const toppedUp = toFiniteNumber(entry.topped_up_balance);
|
|
314
412
|
entries.push({
|
|
315
|
-
amount
|
|
413
|
+
amount,
|
|
316
414
|
currency: typeof entry.currency === "string" ? entry.currency : "",
|
|
317
415
|
...granted === void 0 ? {} : { granted },
|
|
318
416
|
...toppedUp === void 0 ? {} : { toppedUp }
|
|
@@ -375,14 +473,12 @@ function firstFinite(candidates) {
|
|
|
375
473
|
}
|
|
376
474
|
}
|
|
377
475
|
/**
|
|
378
|
-
* Moonshot's balance
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
* Verified against a real key before trusting the absolute value.
|
|
476
|
+
* Moonshot's official /v1/users/me/balance fields are already in yuan.
|
|
477
|
+
* https://platform.kimi.com/docs/api/balance
|
|
478
|
+
* Amount size never changes the unit; retain the existing two-decimal rounding.
|
|
382
479
|
*/
|
|
383
480
|
function moonshotBalanceToYuan(value) {
|
|
384
|
-
|
|
385
|
-
return Math.round(yuan * 100) / 100;
|
|
481
|
+
return Math.round(value * 100) / 100;
|
|
386
482
|
}
|
|
387
483
|
/** Used percentage from an explicit used/limit pair, or by inverting remaining. */
|
|
388
484
|
function percentFrom(record) {
|
|
@@ -1135,8 +1231,8 @@ function providerCredentialRefs(input) {
|
|
|
1135
1231
|
}
|
|
1136
1232
|
/**
|
|
1137
1233
|
* The endpoint each configured DSH provider declares (`llm-pi-ai` profiles carry a
|
|
1138
|
-
* `baseURL`).
|
|
1139
|
-
*
|
|
1234
|
+
* `baseURL`). Its origin also supplies the account request endpoint, and a
|
|
1235
|
+
* sanitized origin is shared with the browser for consistent resolution.
|
|
1140
1236
|
*/
|
|
1141
1237
|
function providerEndpointHints(piAiSettings) {
|
|
1142
1238
|
const providers = asRecord(asRecord(piAiSettings)?.providers);
|
|
@@ -1206,7 +1302,8 @@ async function fetchPayload(request, deps) {
|
|
|
1206
1302
|
try {
|
|
1207
1303
|
return await response.json();
|
|
1208
1304
|
} catch (error) {
|
|
1209
|
-
throw new SourceError("parse", `invalid JSON: ${messageOf(error)}`);
|
|
1305
|
+
if (error instanceof SyntaxError) throw new SourceError("parse", `invalid JSON: ${messageOf(error)}`);
|
|
1306
|
+
throw new SourceError("network", messageOf(error));
|
|
1210
1307
|
}
|
|
1211
1308
|
}
|
|
1212
1309
|
/** Adapt `readUsage` to the shape `UsageStateStore` expects. */
|
|
@@ -1221,16 +1318,25 @@ function createTargetReader(deps) {
|
|
|
1221
1318
|
}
|
|
1222
1319
|
//#endregion
|
|
1223
1320
|
//#region src/host/refresh.ts
|
|
1224
|
-
function toSnapshotError(error) {
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1321
|
+
function toSnapshotError(error, secret) {
|
|
1322
|
+
const kind = error instanceof SourceError ? error.kind : "unknown";
|
|
1323
|
+
if (!(error instanceof Error)) return { kind };
|
|
1324
|
+
const message = secret === void 0 || secret === "" ? error.message : error.message.replaceAll(secret, "[redacted]");
|
|
1325
|
+
return message === "" ? { kind } : {
|
|
1326
|
+
kind,
|
|
1327
|
+
detail: message
|
|
1228
1328
|
};
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1329
|
+
}
|
|
1330
|
+
function targetSignature(target) {
|
|
1331
|
+
return JSON.stringify(target === void 0 ? null : [
|
|
1332
|
+
target.key,
|
|
1333
|
+
target.sourceId,
|
|
1334
|
+
target.mode,
|
|
1335
|
+
target.baseUrl,
|
|
1336
|
+
target.baseUrlPinned === true,
|
|
1337
|
+
target.apiKeyRef,
|
|
1338
|
+
target.preferredRefs
|
|
1339
|
+
]);
|
|
1234
1340
|
}
|
|
1235
1341
|
/**
|
|
1236
1342
|
* Holds the last known reading of every configured source and decides when to
|
|
@@ -1247,23 +1353,56 @@ var UsageStateStore = class {
|
|
|
1247
1353
|
deps;
|
|
1248
1354
|
readings = /* @__PURE__ */ new Map();
|
|
1249
1355
|
succeededAt = /* @__PURE__ */ new Map();
|
|
1356
|
+
identities = /* @__PURE__ */ new Map();
|
|
1357
|
+
generations = /* @__PURE__ */ new Map();
|
|
1358
|
+
targetSignatures = /* @__PURE__ */ new Map();
|
|
1250
1359
|
inflight = /* @__PURE__ */ new Map();
|
|
1251
1360
|
turnEndCancel;
|
|
1252
1361
|
idleCancel;
|
|
1362
|
+
idleIntervalMs;
|
|
1363
|
+
stopped = false;
|
|
1253
1364
|
constructor(deps) {
|
|
1254
1365
|
this.deps = deps;
|
|
1255
1366
|
}
|
|
1367
|
+
invalidate(key) {
|
|
1368
|
+
this.generations.set(key, (this.generations.get(key) ?? 0) + 1);
|
|
1369
|
+
this.identities.delete(key);
|
|
1370
|
+
this.readings.delete(key);
|
|
1371
|
+
this.succeededAt.delete(key);
|
|
1372
|
+
this.inflight.delete(key);
|
|
1373
|
+
}
|
|
1374
|
+
currentTargets() {
|
|
1375
|
+
const targets = this.deps.targets().map((target) => ({ ...target }));
|
|
1376
|
+
const active = new Set(targets.map((target) => target.key));
|
|
1377
|
+
for (const key of this.targetSignatures.keys()) {
|
|
1378
|
+
if (active.has(key)) continue;
|
|
1379
|
+
this.invalidate(key);
|
|
1380
|
+
this.targetSignatures.delete(key);
|
|
1381
|
+
}
|
|
1382
|
+
for (const target of targets) {
|
|
1383
|
+
const signature = targetSignature(target);
|
|
1384
|
+
const previous = this.targetSignatures.get(target.key);
|
|
1385
|
+
if (previous !== void 0 && previous !== signature) this.invalidate(target.key);
|
|
1386
|
+
this.targetSignatures.set(target.key, signature);
|
|
1387
|
+
}
|
|
1388
|
+
return targets;
|
|
1389
|
+
}
|
|
1256
1390
|
/** Everything known so far, keyed by target key. */
|
|
1257
1391
|
snapshots() {
|
|
1392
|
+
if (this.stopped) return {};
|
|
1393
|
+
this.currentTargets();
|
|
1258
1394
|
const result = {};
|
|
1259
1395
|
for (const [key, snapshot] of this.readings) result[key] = snapshot;
|
|
1260
1396
|
return result;
|
|
1261
1397
|
}
|
|
1262
1398
|
snapshot(key) {
|
|
1399
|
+
if (this.stopped) return void 0;
|
|
1400
|
+
this.currentTargets();
|
|
1263
1401
|
return this.readings.get(key);
|
|
1264
1402
|
}
|
|
1265
1403
|
/** Refresh every configured target; failures are captured, never thrown. */
|
|
1266
1404
|
async refreshAll() {
|
|
1405
|
+
if (this.stopped) return;
|
|
1267
1406
|
await Promise.all(this.deps.targets().map((target) => this.refresh(target.key)));
|
|
1268
1407
|
}
|
|
1269
1408
|
/**
|
|
@@ -1271,45 +1410,83 @@ var UsageStateStore = class {
|
|
|
1271
1410
|
* not elapsed yet, unless `force` is set.
|
|
1272
1411
|
*/
|
|
1273
1412
|
async refresh(key, options = {}) {
|
|
1274
|
-
|
|
1275
|
-
if (target === void 0) return this.fail({
|
|
1276
|
-
key,
|
|
1413
|
+
if (this.stopped) return {
|
|
1277
1414
|
sourceId: key.split(":")[0] ?? "",
|
|
1278
|
-
mode: "api"
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1415
|
+
mode: key.endsWith(":coding-plan") ? "coding-plan" : "api",
|
|
1416
|
+
balances: [],
|
|
1417
|
+
windows: [],
|
|
1418
|
+
fetchedAt: this.deps.clock.now(),
|
|
1419
|
+
error: {
|
|
1420
|
+
kind: "config",
|
|
1421
|
+
detail: "refresh stopped"
|
|
1422
|
+
}
|
|
1423
|
+
};
|
|
1424
|
+
const target = this.currentTargets().find((candidate) => candidate.key === key);
|
|
1425
|
+
if (target === void 0) return {
|
|
1426
|
+
sourceId: key.split(":")[0] ?? "",
|
|
1427
|
+
mode: key.endsWith(":coding-plan") ? "coding-plan" : "api",
|
|
1428
|
+
balances: [],
|
|
1429
|
+
windows: [],
|
|
1430
|
+
fetchedAt: this.deps.clock.now(),
|
|
1431
|
+
error: {
|
|
1432
|
+
kind: "config",
|
|
1433
|
+
detail: `unknown target ${key}`
|
|
1434
|
+
}
|
|
1435
|
+
};
|
|
1436
|
+
const signature = targetSignature(target);
|
|
1437
|
+
const generationAtLookup = this.generations.get(key) ?? 0;
|
|
1296
1438
|
const source = this.deps.findSource(target.sourceId);
|
|
1297
1439
|
if (source === void 0) return this.fail(target, {
|
|
1298
1440
|
kind: "config",
|
|
1299
1441
|
detail: `unknown source ${target.sourceId}`
|
|
1300
|
-
},
|
|
1442
|
+
}, this.readings.get(key));
|
|
1301
1443
|
let credential;
|
|
1302
1444
|
try {
|
|
1303
1445
|
credential = await this.deps.credentials.resolve(target);
|
|
1304
1446
|
} catch (error) {
|
|
1305
|
-
return this.
|
|
1447
|
+
if (this.stopped) return this.refresh(key, options);
|
|
1448
|
+
if (signature !== targetSignature(this.currentTargets().find((candidate) => candidate.key === key)) || (this.generations.get(key) ?? 0) !== generationAtLookup) return this.refresh(key, options);
|
|
1449
|
+
return this.fail(target, toSnapshotError(error), this.readings.get(key));
|
|
1450
|
+
}
|
|
1451
|
+
if (this.stopped) return this.refresh(key, options);
|
|
1452
|
+
if (signature !== targetSignature(this.currentTargets().find((candidate) => candidate.key === key))) return this.refresh(key, options);
|
|
1453
|
+
const identity = createHash("sha256").update(JSON.stringify([
|
|
1454
|
+
target.sourceId,
|
|
1455
|
+
target.mode,
|
|
1456
|
+
credential?.baseUrl ?? target.baseUrl ?? source.defaultBaseUrl(target.mode),
|
|
1457
|
+
credential?.baseUrlPinned === true || target.baseUrlPinned === true,
|
|
1458
|
+
credential?.ref ?? target.apiKeyRef,
|
|
1459
|
+
credential?.apiKey
|
|
1460
|
+
])).digest("hex");
|
|
1461
|
+
if ((this.generations.get(key) ?? 0) !== generationAtLookup && this.identities.get(key) !== identity) return this.refresh(key, options);
|
|
1462
|
+
if (this.identities.get(key) !== identity) {
|
|
1463
|
+
this.identities.set(key, identity);
|
|
1464
|
+
this.generations.set(key, (this.generations.get(key) ?? 0) + 1);
|
|
1465
|
+
this.readings.delete(key);
|
|
1466
|
+
this.succeededAt.delete(key);
|
|
1467
|
+
this.inflight.delete(key);
|
|
1306
1468
|
}
|
|
1307
1469
|
if (credential === void 0) return this.fail(target, {
|
|
1308
1470
|
kind: "config",
|
|
1309
1471
|
detail: "no credential configured"
|
|
1310
|
-
},
|
|
1472
|
+
}, this.readings.get(key));
|
|
1473
|
+
const cached = this.readings.get(key);
|
|
1474
|
+
const lastSuccess = this.succeededAt.get(key);
|
|
1475
|
+
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;
|
|
1476
|
+
const running = this.inflight.get(key);
|
|
1477
|
+
if (running !== void 0) return running;
|
|
1478
|
+
const generation = this.generations.get(key) ?? 0;
|
|
1479
|
+
const promise = this.run(target, credential, source, generation).finally(() => {
|
|
1480
|
+
if (this.inflight.get(key) === promise) this.inflight.delete(key);
|
|
1481
|
+
});
|
|
1482
|
+
this.inflight.set(key, promise);
|
|
1483
|
+
return promise;
|
|
1484
|
+
}
|
|
1485
|
+
async run(target, credential, source, generation) {
|
|
1486
|
+
const previous = this.readings.get(target.key);
|
|
1311
1487
|
try {
|
|
1312
1488
|
const reading = await this.deps.read(target, credential, source);
|
|
1489
|
+
if (this.generations.get(target.key) !== generation) return this.refresh(target.key);
|
|
1313
1490
|
const fetchedAt = this.deps.clock.now();
|
|
1314
1491
|
const snapshot = {
|
|
1315
1492
|
sourceId: target.sourceId,
|
|
@@ -1322,7 +1499,8 @@ var UsageStateStore = class {
|
|
|
1322
1499
|
this.succeededAt.set(target.key, fetchedAt);
|
|
1323
1500
|
return snapshot;
|
|
1324
1501
|
} catch (error) {
|
|
1325
|
-
|
|
1502
|
+
if (this.generations.get(target.key) !== generation) return this.refresh(target.key);
|
|
1503
|
+
return this.fail(target, toSnapshotError(error, credential.apiKey), previous);
|
|
1326
1504
|
}
|
|
1327
1505
|
}
|
|
1328
1506
|
fail(target, error, previous) {
|
|
@@ -1353,10 +1531,22 @@ var UsageStateStore = class {
|
|
|
1353
1531
|
});
|
|
1354
1532
|
this.turnEndCancel = cancel;
|
|
1355
1533
|
}
|
|
1534
|
+
/** Apply a live policy edit without leaving the previous idle timer running. */
|
|
1535
|
+
reconfigure() {
|
|
1536
|
+
if (this.stopped) return;
|
|
1537
|
+
this.currentTargets();
|
|
1538
|
+
if (this.idleCancel === void 0) return;
|
|
1539
|
+
if (this.idleIntervalMs === this.deps.policy().intervalMinutes * 6e4) return;
|
|
1540
|
+
this.idleCancel();
|
|
1541
|
+
this.idleCancel = void 0;
|
|
1542
|
+
this.start();
|
|
1543
|
+
}
|
|
1356
1544
|
/** Start the idle fallback timer; returns a stop function. */
|
|
1357
1545
|
start() {
|
|
1546
|
+
this.stopped = false;
|
|
1358
1547
|
if (this.idleCancel === void 0) {
|
|
1359
1548
|
const intervalMs = this.deps.policy().intervalMinutes * 6e4;
|
|
1549
|
+
this.idleIntervalMs = intervalMs;
|
|
1360
1550
|
this.idleCancel = this.deps.clock.every(intervalMs, () => {
|
|
1361
1551
|
this.refreshAll();
|
|
1362
1552
|
});
|
|
@@ -1364,6 +1554,8 @@ var UsageStateStore = class {
|
|
|
1364
1554
|
return () => this.stop();
|
|
1365
1555
|
}
|
|
1366
1556
|
stop() {
|
|
1557
|
+
this.stopped = true;
|
|
1558
|
+
for (const key of this.targetSignatures.keys()) this.invalidate(key);
|
|
1367
1559
|
this.turnEndCancel?.();
|
|
1368
1560
|
this.turnEndCancel = void 0;
|
|
1369
1561
|
this.idleCancel?.();
|
|
@@ -1387,18 +1579,20 @@ var UsageStateService = class {
|
|
|
1387
1579
|
this.deps = deps;
|
|
1388
1580
|
}
|
|
1389
1581
|
/**
|
|
1390
|
-
*
|
|
1391
|
-
*
|
|
1582
|
+
* Initialize a missing account identity, otherwise report cached readings.
|
|
1583
|
+
* The host owns periodic/turn refreshes; polling this method cannot shorten
|
|
1584
|
+
* that cadence. Forcing is reserved for an explicit refresh.
|
|
1392
1585
|
*
|
|
1393
1586
|
* Settled rather than all: one unreachable provider must not blank the whole
|
|
1394
1587
|
* status line for the others.
|
|
1395
1588
|
*/
|
|
1396
1589
|
async getState(force) {
|
|
1397
|
-
const options = force === true ? { force: true } : {};
|
|
1590
|
+
const options = force === true ? { force: true } : { onlyIfMissing: true };
|
|
1398
1591
|
await Promise.allSettled(this.deps.targets().map((target) => this.deps.store.refresh(target.key, options)));
|
|
1399
1592
|
return {
|
|
1400
1593
|
sources: this.deps.catalog(),
|
|
1401
1594
|
snapshots: this.deps.store.snapshots(),
|
|
1595
|
+
...this.deps.endpointHints === void 0 ? {} : { endpointHints: this.deps.endpointHints() },
|
|
1402
1596
|
checkedAt: this.deps.now()
|
|
1403
1597
|
};
|
|
1404
1598
|
}
|
|
@@ -1450,98 +1644,6 @@ function installUsageStateSettings(ctx, reference, onConfig) {
|
|
|
1450
1644
|
});
|
|
1451
1645
|
}
|
|
1452
1646
|
//#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
1647
|
//#region src/host/targets.ts
|
|
1546
1648
|
function targetKey(sourceId, mode) {
|
|
1547
1649
|
return `${sourceId}:${mode}`;
|
|
@@ -1554,7 +1656,8 @@ function targetKey(sourceId, mode) {
|
|
|
1554
1656
|
* line never shows the same account twice. Providers that are hidden, map to no
|
|
1555
1657
|
* known source, or ask for a mode the source cannot serve are skipped silently.
|
|
1556
1658
|
*/
|
|
1557
|
-
function resolveTargets(
|
|
1659
|
+
function resolveTargets(rawConfig, options = {}) {
|
|
1660
|
+
const config = normalizeConfig(rawConfig);
|
|
1558
1661
|
const sources = options.sources ?? ALL_SOURCES;
|
|
1559
1662
|
const catalog = toSourceCatalog(sources);
|
|
1560
1663
|
const targets = [];
|
|
@@ -1570,26 +1673,21 @@ function resolveTargets(config, options = {}) {
|
|
|
1570
1673
|
sourceId,
|
|
1571
1674
|
mode,
|
|
1572
1675
|
...endpoint?.baseUrl === void 0 ? {} : { baseUrl: endpoint.baseUrl },
|
|
1573
|
-
...endpoint?.pinned === true ? { baseUrlPinned: true } : {}
|
|
1676
|
+
...endpoint?.pinned === true ? { baseUrlPinned: true } : {},
|
|
1677
|
+
...endpoint?.apiKeyRef === void 0 ? {} : { apiKeyRef: endpoint.apiKeyRef }
|
|
1574
1678
|
});
|
|
1575
1679
|
};
|
|
1576
1680
|
/**
|
|
1577
|
-
* The live runtime's provider ids, when
|
|
1578
|
-
* not
|
|
1579
|
-
* it must not suppress the configured entries.
|
|
1681
|
+
* The live runtime's provider ids, when known. `undefined` means the host
|
|
1682
|
+
* could not answer; an empty list is authoritative and stops stale targets.
|
|
1580
1683
|
*/
|
|
1581
|
-
const live = options.providers
|
|
1684
|
+
const live = options.providers === void 0 ? void 0 : new Set(options.providers);
|
|
1582
1685
|
/**
|
|
1583
1686
|
* Whether a stored entry may still produce requests. The entry itself is kept — a
|
|
1584
1687
|
* provider that comes back keeps the mode the user chose for it — but a provider
|
|
1585
1688
|
* DSH no longer has must not keep an account being polled behind an invisible row.
|
|
1586
1689
|
*/
|
|
1587
1690
|
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
1691
|
const providers = /* @__PURE__ */ new Set();
|
|
1594
1692
|
for (const provider of [...config.order, ...Object.keys(config.providers)]) if (pollable(provider)) providers.add(provider);
|
|
1595
1693
|
for (const provider of options.providers ?? []) providers.add(provider);
|
|
@@ -1602,7 +1700,8 @@ function resolveTargets(config, options = {}) {
|
|
|
1602
1700
|
});
|
|
1603
1701
|
if (resolution.sourceId !== null && resolution.mode !== null) push(resolution.sourceId, resolution.mode, {
|
|
1604
1702
|
...resolution.baseUrl === void 0 ? {} : { baseUrl: resolution.baseUrl },
|
|
1605
|
-
...resolution.baseUrlPinned === true ? { pinned: true } : {}
|
|
1703
|
+
...resolution.baseUrlPinned === true ? { pinned: true } : {},
|
|
1704
|
+
...resolution.apiKeyRef === void 0 ? {} : { apiKeyRef: resolution.apiKeyRef }
|
|
1606
1705
|
});
|
|
1607
1706
|
}
|
|
1608
1707
|
return targets;
|
|
@@ -1716,8 +1815,10 @@ function createUsageState(ctx, deps = {}) {
|
|
|
1716
1815
|
const catalog = toSourceCatalog(ALL_SOURCES);
|
|
1717
1816
|
const lookup = createCredentialLookup(ctx, deps.credentialFallback ?? {});
|
|
1718
1817
|
let config = DEFAULT_CONFIG;
|
|
1818
|
+
let reconfigure = () => {};
|
|
1719
1819
|
installUsageStateSettings(ctx, deps.config, (next) => {
|
|
1720
1820
|
config = next;
|
|
1821
|
+
reconfigure();
|
|
1721
1822
|
});
|
|
1722
1823
|
/**
|
|
1723
1824
|
* The plugin's own configuration says nothing about which providers exist, so
|
|
@@ -1725,11 +1826,16 @@ function createUsageState(ctx, deps = {}) {
|
|
|
1725
1826
|
* provider nobody configured still resolves to its suggested source.
|
|
1726
1827
|
*/
|
|
1727
1828
|
const providerFacts = () => {
|
|
1728
|
-
|
|
1729
|
-
const ids = [];
|
|
1829
|
+
let ids;
|
|
1730
1830
|
try {
|
|
1731
|
-
|
|
1732
|
-
|
|
1831
|
+
const llm = ctx.get("llm");
|
|
1832
|
+
if (typeof llm?.listProviders === "function") {
|
|
1833
|
+
ids = [];
|
|
1834
|
+
for (const provider of llm.listProviders()) if (typeof provider?.id === "string" && provider.id !== "") ids.push(provider.id);
|
|
1835
|
+
}
|
|
1836
|
+
} catch {
|
|
1837
|
+
ids = void 0;
|
|
1838
|
+
}
|
|
1733
1839
|
return {
|
|
1734
1840
|
ids,
|
|
1735
1841
|
hints: providerEndpointHints(readNamespace(ctx, "llm-pi-ai"))
|
|
@@ -1740,17 +1846,20 @@ function createUsageState(ctx, deps = {}) {
|
|
|
1740
1846
|
return resolveTargets(config, {
|
|
1741
1847
|
providers: ids,
|
|
1742
1848
|
endpointHints: hints
|
|
1743
|
-
})
|
|
1744
|
-
|
|
1745
|
-
const optionsFor = (target) => {
|
|
1746
|
-
const overrideRef = config.sources[target.sourceId]?.apiKeyRef;
|
|
1747
|
-
return {
|
|
1748
|
-
...overrideRef === void 0 ? {} : { overrideRef },
|
|
1849
|
+
}).map((target) => ({
|
|
1850
|
+
...target,
|
|
1749
1851
|
preferredRefs: providerCredentialRefs({
|
|
1750
1852
|
sourceId: target.sourceId,
|
|
1751
1853
|
deepseekSettings: readNamespace(ctx, "llm-deepseek"),
|
|
1752
1854
|
piAiSettings: readNamespace(ctx, "llm-pi-ai")
|
|
1753
1855
|
})
|
|
1856
|
+
}));
|
|
1857
|
+
};
|
|
1858
|
+
const optionsFor = (target) => {
|
|
1859
|
+
const overrideRef = target.apiKeyRef;
|
|
1860
|
+
return {
|
|
1861
|
+
...overrideRef === void 0 ? {} : { overrideRef },
|
|
1862
|
+
preferredRefs: target.preferredRefs ?? []
|
|
1754
1863
|
};
|
|
1755
1864
|
};
|
|
1756
1865
|
const store = new UsageStateStore({
|
|
@@ -1767,21 +1876,24 @@ function createUsageState(ctx, deps = {}) {
|
|
|
1767
1876
|
if (source === void 0) return void 0;
|
|
1768
1877
|
const resolved = await resolveApiKey(source, target.mode, optionsFor(target), lookup);
|
|
1769
1878
|
if (resolved === void 0) return void 0;
|
|
1770
|
-
|
|
1771
|
-
const baseUrl = pinnedBaseUrl ?? target.baseUrl;
|
|
1772
|
-
if (baseUrl === void 0) return resolved;
|
|
1879
|
+
if (target.baseUrl === void 0) return resolved;
|
|
1773
1880
|
return {
|
|
1774
1881
|
...resolved,
|
|
1775
|
-
baseUrl,
|
|
1776
|
-
...
|
|
1882
|
+
baseUrl: target.baseUrl,
|
|
1883
|
+
...target.baseUrlPinned === true ? { baseUrlPinned: true } : {}
|
|
1777
1884
|
};
|
|
1778
1885
|
} },
|
|
1779
1886
|
read: createTargetReader({ fetch: fetchImpl })
|
|
1780
1887
|
});
|
|
1888
|
+
reconfigure = () => store.reconfigure();
|
|
1781
1889
|
const service = new UsageStateService({
|
|
1782
1890
|
store,
|
|
1783
1891
|
targets,
|
|
1784
1892
|
catalog: () => catalog,
|
|
1893
|
+
endpointHints: () => Object.fromEntries(Object.entries(providerFacts().hints).flatMap(([provider, endpoint]) => {
|
|
1894
|
+
const origin = originOf(endpoint);
|
|
1895
|
+
return origin === void 0 ? [] : [[provider, origin]];
|
|
1896
|
+
})),
|
|
1785
1897
|
describe: async (target) => {
|
|
1786
1898
|
const source = findSource(target.sourceId);
|
|
1787
1899
|
if (source === void 0) return {
|
|
@@ -1795,7 +1907,7 @@ function createUsageState(ctx, deps = {}) {
|
|
|
1795
1907
|
ctx.on("session/event", (...args) => {
|
|
1796
1908
|
if (isTurnEnd(args[1])) store.onTurnEnd();
|
|
1797
1909
|
});
|
|
1798
|
-
store.start();
|
|
1910
|
+
ctx.effect(() => store.start(), "dsh-usage-state: refresh scheduling");
|
|
1799
1911
|
return provideUsageState(ctx, service);
|
|
1800
1912
|
}
|
|
1801
1913
|
/**
|
package/lib/typert.js
CHANGED
|
@@ -80,6 +80,7 @@ const credentialDescriptionSchema = z.object({
|
|
|
80
80
|
const usageStateViewSchema = z.object({
|
|
81
81
|
sources: z.array(catalogEntrySchema),
|
|
82
82
|
snapshots: z.record(z.string(), snapshotSchema),
|
|
83
|
+
endpointHints: z.record(z.string(), z.string()).optional(),
|
|
83
84
|
checkedAt: z.number()
|
|
84
85
|
});
|
|
85
86
|
const credentialReportSchema = z.object({ credentials: z.record(z.string(), credentialDescriptionSchema) });
|