@tokenbuddy/tb-admin 1.0.14 → 1.0.27

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.
Files changed (55) hide show
  1. package/dist/src/bootstrap-registry.d.ts +1 -0
  2. package/dist/src/bootstrap-registry.d.ts.map +1 -1
  3. package/dist/src/bootstrap-registry.js.map +1 -1
  4. package/dist/src/cli.d.ts.map +1 -1
  5. package/dist/src/cli.js +294 -13
  6. package/dist/src/cli.js.map +1 -1
  7. package/dist/src/client.d.ts +12 -3
  8. package/dist/src/client.d.ts.map +1 -1
  9. package/dist/src/client.js +12 -8
  10. package/dist/src/client.js.map +1 -1
  11. package/dist/src/display-format.d.ts +39 -0
  12. package/dist/src/display-format.d.ts.map +1 -0
  13. package/dist/src/display-format.js +354 -0
  14. package/dist/src/display-format.js.map +1 -0
  15. package/dist/src/server-cmd.d.ts +25 -1
  16. package/dist/src/server-cmd.d.ts.map +1 -1
  17. package/dist/src/server-cmd.js +116 -16
  18. package/dist/src/server-cmd.js.map +1 -1
  19. package/dist/src/ui-actions.d.ts +90 -0
  20. package/dist/src/ui-actions.d.ts.map +1 -0
  21. package/dist/src/ui-actions.js +823 -0
  22. package/dist/src/ui-actions.js.map +1 -0
  23. package/dist/src/ui-command.d.ts +4 -0
  24. package/dist/src/ui-command.d.ts.map +1 -0
  25. package/dist/src/ui-command.js +37 -0
  26. package/dist/src/ui-command.js.map +1 -0
  27. package/dist/src/ui-server.d.ts +22 -0
  28. package/dist/src/ui-server.d.ts.map +1 -0
  29. package/dist/src/ui-server.js +261 -0
  30. package/dist/src/ui-server.js.map +1 -0
  31. package/dist/src/ui-state.d.ts +140 -0
  32. package/dist/src/ui-state.d.ts.map +1 -0
  33. package/dist/src/ui-state.js +438 -0
  34. package/dist/src/ui-state.js.map +1 -0
  35. package/dist/src/ui-static.d.ts +2 -0
  36. package/dist/src/ui-static.d.ts.map +1 -0
  37. package/dist/src/ui-static.js +469 -0
  38. package/dist/src/ui-static.js.map +1 -0
  39. package/dist/src/upstream-balance-probe.d.ts +41 -0
  40. package/dist/src/upstream-balance-probe.d.ts.map +1 -0
  41. package/dist/src/upstream-balance-probe.js +379 -0
  42. package/dist/src/upstream-balance-probe.js.map +1 -0
  43. package/package.json +1 -1
  44. package/src/bootstrap-registry.ts +1 -0
  45. package/src/cli.ts +335 -13
  46. package/src/client.ts +13 -8
  47. package/src/display-format.ts +398 -0
  48. package/src/server-cmd.ts +145 -20
  49. package/src/ui-actions.ts +958 -0
  50. package/src/ui-command.ts +39 -0
  51. package/src/ui-server.ts +322 -0
  52. package/src/ui-state.ts +614 -0
  53. package/src/ui-static.ts +472 -0
  54. package/src/upstream-balance-probe.ts +505 -0
  55. package/tests/admin.test.ts +1404 -2
@@ -0,0 +1,614 @@
1
+ import { AdminClient } from "./client.js";
2
+ import { ConfigManager, type AdminProfile } from "./config.js";
3
+ import type { SellerRegistryDocument, SellerRegistryEntry } from "./bootstrap-registry.js";
4
+ import {
5
+ BalanceProbeCache,
6
+ type BalanceSnapshot,
7
+ probeUpstreamBalance
8
+ } from "./upstream-balance-probe.js";
9
+
10
+ export type RegistryStatus = "active" | "draining" | "offline" | "pending" | "unknown";
11
+ export type NodeStatus = RegistryStatus | "busy_capacity" | "auth_unknown";
12
+ export type UpstreamStatus = "healthy" | "degraded" | "unhealthy" | "unknown";
13
+
14
+ export interface SellerRow {
15
+ id: string;
16
+ name: string;
17
+ description?: string;
18
+ app?: string;
19
+ profile?: string;
20
+ url: string;
21
+ registryStatus: RegistryStatus;
22
+ nodeStatus: NodeStatus;
23
+ region?: string;
24
+ upstreamDomain: string;
25
+ upstreamStatus: UpstreamStatus;
26
+ upstreamBalanceUsdMicros?: number;
27
+ upstreamBalanceCurrency?: string;
28
+ upstreamBalanceSource?: string;
29
+ upstreamBalanceFetchedAt?: string;
30
+ upstreamBalanceError?: string;
31
+ upstreamRechargeUrl?: string;
32
+ discountRatio?: number;
33
+ capacityUsed?: number;
34
+ capacityLimit?: number;
35
+ ttftMs?: number;
36
+ avgInferenceMs?: number;
37
+ lastInferenceMs?: number;
38
+ avgTokensPerSecond?: number;
39
+ lastTokensPerSecond?: number;
40
+ latencySamples?: number;
41
+ lastSwitchAt?: string;
42
+ modelsCount?: number;
43
+ specs?: {
44
+ memoryGb?: number;
45
+ machines?: number;
46
+ region?: string;
47
+ modelsCount?: number;
48
+ };
49
+ error?: string;
50
+ }
51
+
52
+ export interface SellerModelRow {
53
+ upstreamModel: string;
54
+ billingModel: string;
55
+ inputPrice?: string;
56
+ outputPrice?: string;
57
+ ttftMs?: number;
58
+ avgInferenceMs?: number;
59
+ avgTokensPerSecond?: number;
60
+ latencySamples?: number;
61
+ }
62
+
63
+ export interface SellerDetail {
64
+ row: SellerRow;
65
+ configuration: {
66
+ registryStatus: string;
67
+ region?: string;
68
+ upstreamUrl?: string;
69
+ upstreamApiKeyMasked?: string;
70
+ upstreamStatus: UpstreamStatus;
71
+ upstreamBalance?: string;
72
+ upstreamBalanceSource?: string;
73
+ upstreamBalanceFetchedAt?: string;
74
+ upstreamBalanceError?: string;
75
+ upstreamBalanceProbeTemplate?: string;
76
+ upstreamBalanceProbeUrl?: string;
77
+ upstreamBalanceProbeUserId?: string;
78
+ upstreamBalanceProbeRechargeUrl?: string;
79
+ upstreamBalanceUrl?: string;
80
+ upstreamUserId?: string;
81
+ upstreamRechargeUrl?: string;
82
+ markupRatio?: number;
83
+ discountRatio?: number;
84
+ maxConnections?: number;
85
+ maxQueueDepth?: number;
86
+ };
87
+ models: SellerModelRow[];
88
+ }
89
+
90
+ export interface BootstrapSummary {
91
+ status: "available" | "unavailable";
92
+ url?: string;
93
+ profile?: string;
94
+ registryVersion?: number;
95
+ registryUpdatedAt?: string;
96
+ sellerEntries: number;
97
+ defaultSeller?: string;
98
+ regions: string[];
99
+ error?: string;
100
+ }
101
+
102
+ export interface BootstrapConfigSummary {
103
+ status: "available" | "unavailable";
104
+ sellerRegistryPath?: string;
105
+ bindHost?: string;
106
+ bindPort?: number;
107
+ allowLocalSellerUrls?: boolean;
108
+ clawtip?: {
109
+ payToMasked?: string;
110
+ sm4KeyMasked?: string;
111
+ skillSlug?: string;
112
+ skillId?: string;
113
+ description?: string;
114
+ resourceUrl?: string;
115
+ activationFeeFen?: number;
116
+ microsPerFen?: number;
117
+ };
118
+ error?: string;
119
+ }
120
+
121
+ export interface AdminUiStateOptions {
122
+ configManager: ConfigManager;
123
+ configPath?: string;
124
+ profile?: string;
125
+ url?: string;
126
+ token?: string;
127
+ fetchJson?: (url: string, init?: RequestInit) => Promise<unknown>;
128
+ balanceFetch?: typeof fetch;
129
+ }
130
+
131
+ interface ProfileMatch {
132
+ name?: string;
133
+ profile?: AdminProfile;
134
+ localProfile?: boolean;
135
+ }
136
+
137
+ interface SellerSnapshot {
138
+ row: SellerRow;
139
+ status?: any;
140
+ service?: any;
141
+ upstreams?: any;
142
+ config?: any;
143
+ balance?: BalanceSnapshot;
144
+ }
145
+
146
+ export class AdminUiState {
147
+ private readonly configManager: ConfigManager;
148
+ private readonly options: AdminUiStateOptions;
149
+ private readonly fetchJson: (url: string, init?: RequestInit) => Promise<unknown>;
150
+ private readonly balanceCache = new BalanceProbeCache();
151
+
152
+ constructor(options: AdminUiStateOptions) {
153
+ this.options = options;
154
+ this.configManager = options.configPath ? new ConfigManager(options.configPath) : options.configManager;
155
+ this.fetchJson = options.fetchJson || defaultFetchJson;
156
+ }
157
+
158
+ public async bootstrap(): Promise<BootstrapSummary> {
159
+ try {
160
+ const document = await this.fetchRegistry();
161
+ const profile = this.activeBootstrapProfile();
162
+ return {
163
+ status: "available",
164
+ url: profile.profile?.url || this.options.url,
165
+ profile: profile.name,
166
+ registryVersion: document.version,
167
+ registryUpdatedAt: document.updatedAt,
168
+ sellerEntries: document.sellers.length,
169
+ defaultSeller: document.defaultSeller,
170
+ regions: Array.from(new Set(document.sellers.map((seller) => seller.region).filter((region): region is string => Boolean(region)))).sort()
171
+ };
172
+ } catch (err: any) {
173
+ const profile = this.activeBootstrapProfile();
174
+ return {
175
+ status: "unavailable",
176
+ url: profile.profile?.url || this.options.url,
177
+ profile: profile.name,
178
+ sellerEntries: 0,
179
+ regions: [],
180
+ error: err.message
181
+ };
182
+ }
183
+ }
184
+
185
+ public async bootstrapConfig(): Promise<BootstrapConfigSummary> {
186
+ try {
187
+ const profile = this.activeBootstrapProfile();
188
+ if (!profile.profile) {
189
+ throw new Error("No bootstrap profile found. Configure an admin profile or pass --url and --token.");
190
+ }
191
+ const client = new AdminClient(profile.profile.url, profile.profile.token);
192
+ const config = await client.get("/operator/config");
193
+ return {
194
+ status: "available",
195
+ sellerRegistryPath: stringValue(config.sellerRegistryPath),
196
+ bindHost: stringValue(config.bind?.host),
197
+ bindPort: numberValue(config.bind?.port),
198
+ allowLocalSellerUrls: Boolean(config.allowLocalSellerUrls),
199
+ clawtip: {
200
+ payToMasked: maskSecret(config.clawtip?.payTo),
201
+ sm4KeyMasked: maskSecret(config.clawtip?.sm4KeyBase64),
202
+ skillSlug: stringValue(config.clawtip?.skillSlug),
203
+ skillId: stringValue(config.clawtip?.skillId),
204
+ description: stringValue(config.clawtip?.description),
205
+ resourceUrl: stringValue(config.clawtip?.resourceUrl),
206
+ activationFeeFen: numberValue(config.clawtip?.activationFeeFen),
207
+ microsPerFen: numberValue(config.clawtip?.microsPerFen)
208
+ }
209
+ };
210
+ } catch (err: any) {
211
+ return {
212
+ status: "unavailable",
213
+ error: err.message
214
+ };
215
+ }
216
+ }
217
+
218
+ public async sellers(): Promise<SellerRow[]> {
219
+ const document = await this.fetchRegistry();
220
+ const snapshots = await Promise.all(document.sellers.map((seller) => this.sellerSnapshot(seller)));
221
+ return snapshots.map((snapshot) => snapshot.row);
222
+ }
223
+
224
+ public async sellerDetail(id: string): Promise<SellerDetail> {
225
+ const document = await this.fetchRegistry();
226
+ const entry = document.sellers.find((seller) => seller.id === id || seller.name === id || seller.app === id);
227
+ if (!entry) {
228
+ throw new Error(`seller \`${id}\` not found in bootstrap registry`);
229
+ }
230
+ const snapshot = await this.sellerSnapshot(entry);
231
+ const config = snapshot.config?.config || snapshot.config || {};
232
+ const upstreams = snapshot.upstreams || {};
233
+ return {
234
+ row: snapshot.row,
235
+ configuration: {
236
+ registryStatus: snapshot.row.registryStatus,
237
+ region: entry.region,
238
+ upstreamUrl: stringValue(config.upstreamUrl || upstreams.upstreamUrl),
239
+ upstreamApiKeyMasked: maskApiKey(config.upstreamApiKey || upstreams.upstreamApiKey),
240
+ upstreamStatus: snapshot.row.upstreamStatus,
241
+ upstreamBalance: balanceString(snapshot.balance),
242
+ upstreamBalanceSource: snapshot.row.upstreamBalanceSource,
243
+ upstreamBalanceFetchedAt: snapshot.row.upstreamBalanceFetchedAt,
244
+ upstreamBalanceError: snapshot.row.upstreamBalanceError,
245
+ upstreamBalanceProbeTemplate: stringValue(config.upstreamBalanceProbe?.template),
246
+ upstreamBalanceProbeUrl: stringValue(config.upstreamBalanceProbe?.url || config.upstreamBalanceUrl),
247
+ upstreamBalanceProbeUserId: stringValue(config.upstreamBalanceProbe?.userId || config.upstreamUserId),
248
+ upstreamBalanceProbeRechargeUrl: stringValue(config.upstreamBalanceProbe?.rechargeUrl || config.upstreamRechargeUrl),
249
+ upstreamBalanceUrl: stringValue(config.upstreamBalanceUrl),
250
+ upstreamUserId: stringValue(config.upstreamUserId),
251
+ upstreamRechargeUrl: snapshot.row.upstreamRechargeUrl,
252
+ markupRatio: numberValue(config.markupRatio ?? upstreams.markupRatio),
253
+ discountRatio: numberValue(config.discountRatio ?? upstreams.discountRatio),
254
+ maxConnections: numberValue(config.maxConnections ?? snapshot.service?.capacity?.maxConnections ?? snapshot.status?.capacity?.maxConnections),
255
+ maxQueueDepth: numberValue(config.maxQueueDepth ?? snapshot.service?.capacity?.maxQueueDepth ?? snapshot.status?.capacity?.maxQueueDepth)
256
+ },
257
+ models: modelRows(upstreams, config, snapshot.status)
258
+ };
259
+ }
260
+
261
+ public async rawSellerConfig(id: string): Promise<{ entry: SellerRegistryEntry; profileName?: string; config: any }> {
262
+ const document = await this.fetchRegistry();
263
+ const entry = document.sellers.find((seller) => seller.id === id || seller.name === id || seller.app === id);
264
+ if (!entry) {
265
+ throw new Error(`seller \`${id}\` not found in bootstrap registry`);
266
+ }
267
+ const match = this.matchSellerProfile(entry);
268
+ if (!match.profile) {
269
+ throw new Error(`seller \`${entry.id}\` has no matching local admin profile`);
270
+ }
271
+ const response = await this.fetchSellerAdminJson(match.profile, "/operator/admin/config");
272
+ return { entry, profileName: match.localProfile ? match.name : undefined, config: response.config || response };
273
+ }
274
+
275
+ public async fetchRegistry(): Promise<SellerRegistryDocument> {
276
+ const profile = this.activeBootstrapProfile();
277
+ const baseUrl = this.options.url || profile.profile?.url;
278
+ if (!baseUrl) {
279
+ throw new Error("No bootstrap profile found. Configure an admin profile or pass --url.");
280
+ }
281
+ const document = await this.fetchJson(`${trimSlash(baseUrl)}/registry/sellers`) as SellerRegistryDocument;
282
+ if (!document || !Array.isArray(document.sellers)) {
283
+ throw new Error("bootstrap registry response did not include sellers");
284
+ }
285
+ return document;
286
+ }
287
+
288
+ private activeBootstrapProfile(): ProfileMatch {
289
+ const envProfile = process.env.TOKENBUDDY_ADMIN_PROFILE;
290
+ const target = this.options.profile || envProfile;
291
+ if (this.options.url) {
292
+ return {
293
+ name: target,
294
+ profile: this.options.token ? { url: this.options.url, token: this.options.token } : undefined,
295
+ localProfile: false
296
+ };
297
+ }
298
+ const config = this.configManager.load();
299
+ const name = target || config.default_profile || Object.keys(config.profiles)[0];
300
+ return {
301
+ name,
302
+ profile: name ? config.profiles[name] : undefined,
303
+ localProfile: Boolean(name && config.profiles[name])
304
+ };
305
+ }
306
+
307
+ private matchSellerProfile(entry: SellerRegistryEntry): ProfileMatch {
308
+ const config = this.configManager.load();
309
+ if (entry.profile && config.profiles[entry.profile]) {
310
+ return { name: entry.profile, profile: config.profiles[entry.profile], localProfile: true };
311
+ }
312
+ const byUrl = Object.entries(config.profiles).find(([, profile]) => trimSlash(profile.url) === trimSlash(entry.url));
313
+ if (byUrl) {
314
+ return { name: byUrl[0], profile: byUrl[1], localProfile: true };
315
+ }
316
+ const entryHost = hostName(entry.url);
317
+ const near = Object.entries(config.profiles).find(([name, profile]) => {
318
+ const haystack = `${name} ${profile.url} ${hostName(profile.url)}`.toLowerCase();
319
+ return [entry.id, entry.app, entryHost].some((value) => value && haystack.includes(value.toLowerCase()));
320
+ });
321
+ if (near) {
322
+ return { name: near[0], profile: near[1], localProfile: true };
323
+ }
324
+ const provider = this.configManager.getSellerProvider("fly");
325
+ if (provider?.operator_secret) {
326
+ return {
327
+ name: entry.app || entry.id,
328
+ profile: {
329
+ url: entry.url,
330
+ token: provider.operator_secret
331
+ },
332
+ localProfile: false
333
+ };
334
+ }
335
+ return {};
336
+ }
337
+
338
+ private async sellerSnapshot(entry: SellerRegistryEntry): Promise<SellerSnapshot> {
339
+ const match = this.matchSellerProfile(entry);
340
+ const baseRow = baseSellerRow(entry, match.name);
341
+ if (!match.profile) {
342
+ return {
343
+ row: {
344
+ ...baseRow,
345
+ nodeStatus: "auth_unknown",
346
+ error: "No matching local admin profile"
347
+ }
348
+ };
349
+ }
350
+
351
+ try {
352
+ const [status, service, upstreams, config] = await Promise.all([
353
+ this.fetchSellerAdminJson(match.profile, "/operator/status").catch((err: any) => ({ error: err.message })),
354
+ this.fetchSellerAdminJson(match.profile, "/operator/admin/service").catch((err: any) => ({ error: err.message })),
355
+ this.fetchSellerAdminJson(match.profile, "/operator/admin/upstreams").catch((err: any) => ({ error: err.message })),
356
+ this.fetchSellerAdminJson(match.profile, "/operator/admin/config").catch((err: any) => ({ error: err.message }))
357
+ ]);
358
+ const configDocument = config?.config || config || {};
359
+ const balance = await this.balanceSnapshot(configDocument, upstreams);
360
+ return {
361
+ status,
362
+ service,
363
+ upstreams,
364
+ config,
365
+ balance,
366
+ row: mergeSellerRow(baseRow, entry, status, service, upstreams, configDocument, balance)
367
+ };
368
+ } catch (err: any) {
369
+ return {
370
+ row: {
371
+ ...baseRow,
372
+ nodeStatus: "unknown",
373
+ error: err.message
374
+ }
375
+ };
376
+ }
377
+ }
378
+
379
+ private async fetchSellerAdminJson(profile: AdminProfile, pathName: string): Promise<any> {
380
+ return this.fetchJson(`${trimSlash(profile.url)}${pathName}`, {
381
+ headers: {
382
+ "Content-Type": "application/json",
383
+ Authorization: `Bearer ${profile.token}`
384
+ }
385
+ });
386
+ }
387
+
388
+ private async balanceSnapshot(config: any, upstreams: any): Promise<BalanceSnapshot | undefined> {
389
+ if (config?.error) {
390
+ return undefined;
391
+ }
392
+ if (stringValue(config.upstreamBalanceProbe?.template) === "none") {
393
+ return undefined;
394
+ }
395
+ return probeUpstreamBalance({
396
+ upstreamUrl: stringValue(config.upstreamUrl || upstreams?.upstreamUrl),
397
+ upstreamBalanceUrl: stringValue(config.upstreamBalanceUrl || upstreams?.upstreamBalanceUrl),
398
+ upstreamApiKey: stringValue(config.upstreamApiKey),
399
+ upstreamUserId: stringValue(config.upstreamUserId),
400
+ upstreamBalanceProbe: objectValue(config.upstreamBalanceProbe)
401
+ }, {
402
+ fetch: this.options.balanceFetch,
403
+ cache: this.balanceCache
404
+ });
405
+ }
406
+ }
407
+
408
+ async function defaultFetchJson(url: string, init?: RequestInit): Promise<unknown> {
409
+ const response = await fetch(url, init);
410
+ if (!response.ok) {
411
+ const text = await response.text();
412
+ throw new Error(`HTTP Error ${response.status}: ${text || response.statusText}`);
413
+ }
414
+ const text = await response.text();
415
+ return text ? JSON.parse(text) : {};
416
+ }
417
+
418
+ function baseSellerRow(entry: SellerRegistryEntry, profile?: string): SellerRow {
419
+ return {
420
+ id: entry.id,
421
+ name: entry.id,
422
+ description: entry.name && entry.name !== entry.id ? entry.name : undefined,
423
+ app: entry.app,
424
+ profile: profile || entry.profile,
425
+ url: entry.url,
426
+ registryStatus: registryStatus(entry.status),
427
+ nodeStatus: registryStatus(entry.status),
428
+ region: entry.region,
429
+ upstreamDomain: hostName(entry.url) || "unknown",
430
+ upstreamStatus: "unknown",
431
+ modelsCount: entry.modelsCount ?? entry.models?.length ?? entry.sampleModels?.length,
432
+ specs: {
433
+ region: entry.region,
434
+ modelsCount: entry.modelsCount ?? entry.models?.length ?? entry.sampleModels?.length
435
+ }
436
+ };
437
+ }
438
+
439
+ function mergeSellerRow(
440
+ base: SellerRow,
441
+ entry: SellerRegistryEntry,
442
+ status: any,
443
+ service: any,
444
+ upstreams: any,
445
+ config: any,
446
+ balance: BalanceSnapshot | undefined
447
+ ): SellerRow {
448
+ const normalizedUpstreams = upstreamDocument(upstreams);
449
+ const capacity = status?.capacity || service?.capacity || {};
450
+ const upstreamUrl = stringValue(config?.upstreamUrl || normalizedUpstreams?.upstreamUrl || service?.upstreamUrl) || entry.url;
451
+ const error = firstError(status, service, upstreams);
452
+ return {
453
+ ...base,
454
+ nodeStatus: error ? "unknown" : nodeStatus(status?.status || entry.status),
455
+ upstreamDomain: hostName(upstreamUrl) || base.upstreamDomain,
456
+ upstreamStatus: upstreamStatus(status?.upstream?.status || normalizedUpstreams?.status),
457
+ discountRatio: numberValue(config?.discountRatio ?? normalizedUpstreams?.discountRatio),
458
+ capacityUsed: numberValue(capacity.activeConnections),
459
+ capacityLimit: numberValue(capacity.maxConnections),
460
+ ttftMs: numberValue(status?.latency?.ttftMs),
461
+ avgInferenceMs: numberValue(status?.latency?.avgInferenceMs),
462
+ lastInferenceMs: numberValue(status?.latency?.lastInferenceMs),
463
+ avgTokensPerSecond: numberValue(status?.latency?.avgTokensPerSecond),
464
+ lastTokensPerSecond: numberValue(status?.latency?.lastTokensPerSecond),
465
+ latencySamples: numberValue(status?.latency?.sampleCount),
466
+ upstreamBalanceUsdMicros: balance && Number.isFinite(balance.amountUsdMicros ?? NaN) ? (balance.amountUsdMicros as number) : undefined,
467
+ upstreamBalanceCurrency: typeof balance?.currency === "string" ? balance.currency : undefined,
468
+ upstreamBalanceSource: balance?.source,
469
+ upstreamBalanceFetchedAt: balance ? new Date(balance.fetchedAt).toISOString() : undefined,
470
+ upstreamBalanceError: balance?.error?.message,
471
+ upstreamRechargeUrl: stringValue(config?.upstreamBalanceProbe?.rechargeUrl || config?.upstreamRechargeUrl || normalizedUpstreams?.upstreamRechargeUrl),
472
+ modelsCount: numberValue(service?.modelsCount ?? normalizedUpstreams?.models?.length ?? base.modelsCount),
473
+ specs: {
474
+ ...base.specs,
475
+ region: entry.region,
476
+ modelsCount: numberValue(service?.modelsCount ?? normalizedUpstreams?.models?.length ?? base.modelsCount)
477
+ },
478
+ error
479
+ };
480
+ }
481
+
482
+ function modelRows(upstreams: any, config: any, status: any): SellerModelRow[] {
483
+ const normalizedUpstreams = upstreamDocument(upstreams);
484
+ const aliases = config.modelAliases || normalizedUpstreams.modelAliases || {};
485
+ const models = Array.isArray(normalizedUpstreams.models)
486
+ ? normalizedUpstreams.models
487
+ : Array.isArray(config.models)
488
+ ? config.models
489
+ : [];
490
+ return models.map((model: any) => {
491
+ const id = stringValue(model.id || model.name) || "unknown";
492
+ return {
493
+ upstreamModel: id,
494
+ billingModel: stringValue(aliases[id]) || id,
495
+ inputPrice: priceString(model.inputPriceMicrosPer1m),
496
+ outputPrice: priceString(model.outputPriceMicrosPer1m),
497
+ ttftMs: numberValue(model.ttftMs ?? status?.latency?.ttftMs),
498
+ avgInferenceMs: numberValue(model.avgInferenceMs ?? status?.latency?.avgInferenceMs),
499
+ avgTokensPerSecond: numberValue(model.avgTokensPerSecond ?? status?.latency?.avgTokensPerSecond),
500
+ latencySamples: numberValue(model.latencySamples ?? status?.latency?.sampleCount)
501
+ };
502
+ });
503
+ }
504
+
505
+ function upstreamDocument(value: any): any {
506
+ if (Array.isArray(value?.upstreams)) {
507
+ return objectValue(value.upstreams[0]) || {};
508
+ }
509
+ return objectValue(value?.upstreams) || value || {};
510
+ }
511
+
512
+ function registryStatus(value: unknown): RegistryStatus {
513
+ const normalized = String(value || "unknown").toLowerCase();
514
+ if (normalized === "active" || normalized === "draining" || normalized === "offline" || normalized === "pending") {
515
+ return normalized;
516
+ }
517
+ return "unknown";
518
+ }
519
+
520
+ function nodeStatus(value: unknown): NodeStatus {
521
+ const normalized = String(value || "unknown").toLowerCase();
522
+ if (normalized === "active" || normalized === "busy_capacity" || normalized === "draining" || normalized === "pending" || normalized === "offline") {
523
+ return normalized;
524
+ }
525
+ if (normalized === "healthy") {
526
+ return "active";
527
+ }
528
+ return "unknown";
529
+ }
530
+
531
+ function upstreamStatus(value: unknown): UpstreamStatus {
532
+ const normalized = String(value || "unknown").toLowerCase();
533
+ if (normalized === "healthy" || normalized === "degraded" || normalized === "unhealthy") {
534
+ return normalized;
535
+ }
536
+ return "unknown";
537
+ }
538
+
539
+ export function maskApiKey(value: unknown): string | undefined {
540
+ const normalized = String(value || "").trim();
541
+ if (!normalized) {
542
+ return undefined;
543
+ }
544
+ const tail = normalized.replace(/\s+/g, "").slice(-4);
545
+ return tail ? `**** **** **** ${tail}` : "****";
546
+ }
547
+
548
+ function maskSecret(value: unknown): string | undefined {
549
+ const normalized = String(value || "").trim();
550
+ if (!normalized) {
551
+ return undefined;
552
+ }
553
+ const compact = normalized.replace(/\s+/g, "");
554
+ if (compact.length <= 8) {
555
+ return "********";
556
+ }
557
+ return `${compact.slice(0, 4)}...${compact.slice(-4)}`;
558
+ }
559
+
560
+ function trimSlash(value: string): string {
561
+ return value.replace(/\/+$/, "");
562
+ }
563
+
564
+ function hostName(value: string | undefined): string {
565
+ if (!value) {
566
+ return "";
567
+ }
568
+ try {
569
+ return new URL(value).hostname.replace(/^www\./, "");
570
+ } catch {
571
+ return "";
572
+ }
573
+ }
574
+
575
+ function numberValue(value: unknown): number | undefined {
576
+ const parsed = Number(value);
577
+ return Number.isFinite(parsed) ? parsed : undefined;
578
+ }
579
+
580
+ function stringValue(value: unknown): string | undefined {
581
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
582
+ }
583
+
584
+ function objectValue(value: unknown): Record<string, unknown> | undefined {
585
+ return value && typeof value === "object" && !Array.isArray(value)
586
+ ? value as Record<string, unknown>
587
+ : undefined;
588
+ }
589
+
590
+ function priceString(value: unknown): string | undefined {
591
+ const parsed = numberValue(value);
592
+ if (parsed === undefined) {
593
+ return undefined;
594
+ }
595
+ return `$${(parsed / 1000000).toFixed(4)}/1M`;
596
+ }
597
+
598
+ function balanceString(balance: BalanceSnapshot | undefined): string | undefined {
599
+ if (!balance) {
600
+ return undefined;
601
+ }
602
+ if (balance.rawAmount === null) {
603
+ return balance.error?.message;
604
+ }
605
+ const amount = Math.abs(balance.rawAmount) >= 100
606
+ ? balance.rawAmount.toFixed(0)
607
+ : balance.rawAmount.toFixed(2);
608
+ return `${balance.currency || "USD"} ${amount}`;
609
+ }
610
+
611
+ function firstError(...values: any[]): string | undefined {
612
+ const hit = values.find((value) => value?.error);
613
+ return hit?.error;
614
+ }