@tokenbuddy/tb-admin 1.0.13 → 1.0.15

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