@tokenbuddy/tokenbuddy 1.0.36 → 1.0.38

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 (146) hide show
  1. package/dist/src/buyer-store.d.ts +7 -2
  2. package/dist/src/buyer-store.js +46 -7
  3. package/dist/src/cli.d.ts +1 -0
  4. package/dist/src/cli.js +15 -7
  5. package/dist/src/daemon.d.ts +12 -0
  6. package/dist/src/daemon.js +791 -61
  7. package/dist/src/doctor-diagnostics.js +1 -6
  8. package/dist/src/provider-install.d.ts +2 -2
  9. package/dist/src/provider-install.js +248 -2
  10. package/dist/src/seller-catalog.d.ts +21 -0
  11. package/dist/src/seller-catalog.js +17 -0
  12. package/dist/src/seller-route-planner.d.ts +4 -1
  13. package/dist/src/seller-route-planner.js +3 -0
  14. package/dist/src/seller-routing-strategy.d.ts +3 -0
  15. package/dist/src/terminal-detect.d.ts +1 -1
  16. package/dist/src/terminal-detect.js +3 -2
  17. package/dist/src/workdir.d.ts +10 -0
  18. package/dist/src/workdir.js +26 -0
  19. package/package.json +15 -2
  20. package/static/ui/assets/index-Djfl9tw5.js +271 -0
  21. package/static/ui/assets/index-DkfztCkn.css +1 -0
  22. package/static/ui/index.html +2 -2
  23. package/dist/src/buyer-store.d.ts.map +0 -1
  24. package/dist/src/buyer-store.js.map +0 -1
  25. package/dist/src/clawtip-bootstrap.d.ts.map +0 -1
  26. package/dist/src/clawtip-bootstrap.js.map +0 -1
  27. package/dist/src/cli.d.ts.map +0 -1
  28. package/dist/src/cli.js.map +0 -1
  29. package/dist/src/credit-tracker.d.ts.map +0 -1
  30. package/dist/src/credit-tracker.js.map +0 -1
  31. package/dist/src/daemon.d.ts.map +0 -1
  32. package/dist/src/daemon.js.map +0 -1
  33. package/dist/src/doctor-clawtip-wallet.d.ts.map +0 -1
  34. package/dist/src/doctor-clawtip-wallet.js.map +0 -1
  35. package/dist/src/doctor-diagnostics.d.ts.map +0 -1
  36. package/dist/src/doctor-diagnostics.js.map +0 -1
  37. package/dist/src/index.d.ts.map +0 -1
  38. package/dist/src/index.js.map +0 -1
  39. package/dist/src/init-clawtip-activation.d.ts.map +0 -1
  40. package/dist/src/init-clawtip-activation.js.map +0 -1
  41. package/dist/src/init-payment-options.d.ts.map +0 -1
  42. package/dist/src/init-payment-options.js.map +0 -1
  43. package/dist/src/init-setup.d.ts.map +0 -1
  44. package/dist/src/init-setup.js.map +0 -1
  45. package/dist/src/model-index.d.ts.map +0 -1
  46. package/dist/src/model-index.js.map +0 -1
  47. package/dist/src/package-update.d.ts.map +0 -1
  48. package/dist/src/package-update.js.map +0 -1
  49. package/dist/src/prewarm-cache.d.ts.map +0 -1
  50. package/dist/src/prewarm-cache.js.map +0 -1
  51. package/dist/src/prewarm-scheduler.d.ts.map +0 -1
  52. package/dist/src/prewarm-scheduler.js.map +0 -1
  53. package/dist/src/provider-install.d.ts.map +0 -1
  54. package/dist/src/provider-install.js.map +0 -1
  55. package/dist/src/provider-routing-config.d.ts.map +0 -1
  56. package/dist/src/provider-routing-config.js.map +0 -1
  57. package/dist/src/registry-trust.d.ts.map +0 -1
  58. package/dist/src/registry-trust.js.map +0 -1
  59. package/dist/src/route-failover.d.ts.map +0 -1
  60. package/dist/src/route-failover.js.map +0 -1
  61. package/dist/src/seller-catalog.d.ts.map +0 -1
  62. package/dist/src/seller-catalog.js.map +0 -1
  63. package/dist/src/seller-concurrency-limiter.d.ts.map +0 -1
  64. package/dist/src/seller-concurrency-limiter.js.map +0 -1
  65. package/dist/src/seller-metadata-cache.d.ts.map +0 -1
  66. package/dist/src/seller-metadata-cache.js.map +0 -1
  67. package/dist/src/seller-pool.d.ts.map +0 -1
  68. package/dist/src/seller-pool.js.map +0 -1
  69. package/dist/src/seller-route-planner.d.ts.map +0 -1
  70. package/dist/src/seller-route-planner.js.map +0 -1
  71. package/dist/src/seller-routing-config.d.ts.map +0 -1
  72. package/dist/src/seller-routing-config.js.map +0 -1
  73. package/dist/src/seller-routing-strategy.d.ts.map +0 -1
  74. package/dist/src/seller-routing-strategy.js.map +0 -1
  75. package/dist/src/stream-failover.d.ts.map +0 -1
  76. package/dist/src/stream-failover.js.map +0 -1
  77. package/dist/src/tb-clawtip-proof.d.ts.map +0 -1
  78. package/dist/src/tb-clawtip-proof.js.map +0 -1
  79. package/dist/src/tb-proxyd.d.ts.map +0 -1
  80. package/dist/src/tb-proxyd.js.map +0 -1
  81. package/dist/src/terminal-detect.d.ts.map +0 -1
  82. package/dist/src/terminal-detect.js.map +0 -1
  83. package/dist/src/terminal-image.d.ts.map +0 -1
  84. package/dist/src/terminal-image.js.map +0 -1
  85. package/src/buyer-store.ts +0 -1090
  86. package/src/clawtip-bootstrap.ts +0 -65
  87. package/src/cli.ts +0 -2243
  88. package/src/credit-tracker.ts +0 -295
  89. package/src/daemon.ts +0 -5475
  90. package/src/doctor-clawtip-wallet.ts +0 -95
  91. package/src/doctor-diagnostics.ts +0 -1026
  92. package/src/index.ts +0 -16
  93. package/src/init-clawtip-activation.ts +0 -695
  94. package/src/init-payment-options.ts +0 -373
  95. package/src/init-setup.ts +0 -165
  96. package/src/model-index.ts +0 -278
  97. package/src/package-update.ts +0 -311
  98. package/src/prewarm-cache.ts +0 -485
  99. package/src/prewarm-scheduler.ts +0 -675
  100. package/src/provider-install.ts +0 -1006
  101. package/src/provider-routing-config.ts +0 -410
  102. package/src/registry-trust.ts +0 -51
  103. package/src/route-failover.ts +0 -304
  104. package/src/seller-catalog.ts +0 -505
  105. package/src/seller-concurrency-limiter.ts +0 -161
  106. package/src/seller-metadata-cache.ts +0 -91
  107. package/src/seller-pool.ts +0 -557
  108. package/src/seller-route-planner.ts +0 -513
  109. package/src/seller-routing-config.ts +0 -211
  110. package/src/seller-routing-strategy.ts +0 -362
  111. package/src/stream-failover.ts +0 -152
  112. package/src/tb-clawtip-proof.ts +0 -28
  113. package/src/tb-proxyd.ts +0 -101
  114. package/src/terminal-detect.ts +0 -333
  115. package/src/terminal-image.ts +0 -228
  116. package/static/ui/assets/index-0MVXD7bH.css +0 -1
  117. package/static/ui/assets/index-BVbeDEwq.js +0 -271
  118. package/static/ui/assets/index-BVbeDEwq.js.map +0 -1
  119. package/tests/cli-routing.test.ts +0 -363
  120. package/tests/control-plane-ui-endpoints.test.ts +0 -1630
  121. package/tests/credit-tracker.test.ts +0 -165
  122. package/tests/daemon-413-fallback.test.ts +0 -92
  123. package/tests/daemon-classify.test.ts +0 -452
  124. package/tests/daemon-roles.test.ts +0 -92
  125. package/tests/daemon-trusted-registry-cache.test.ts +0 -132
  126. package/tests/e2e.test.ts +0 -366
  127. package/tests/image-generation-e2e.test.ts +0 -230
  128. package/tests/model-index.test.ts +0 -198
  129. package/tests/package-update.test.ts +0 -147
  130. package/tests/prewarm-cache.test.ts +0 -296
  131. package/tests/prewarm-scheduler.test.ts +0 -367
  132. package/tests/provider-routing-config.test.ts +0 -150
  133. package/tests/registry-trust.test.ts +0 -28
  134. package/tests/route-failover.test.ts +0 -222
  135. package/tests/seller-catalog-413.test.ts +0 -120
  136. package/tests/seller-catalog-utilities.test.ts +0 -124
  137. package/tests/seller-concurrency-limiter.test.ts +0 -83
  138. package/tests/seller-metadata-cache.test.ts +0 -89
  139. package/tests/seller-pool.test.ts +0 -365
  140. package/tests/seller-route-planner.test.ts +0 -312
  141. package/tests/seller-routing-config.test.ts +0 -124
  142. package/tests/seller-routing-strategy.test.ts +0 -167
  143. package/tests/stream-failover.test.ts +0 -52
  144. package/tests/thousand-seller.test.ts +0 -151
  145. package/tests/tokenbuddy.test.ts +0 -4043
  146. package/tsconfig.json +0 -8
@@ -1,1026 +0,0 @@
1
- import { BuyerStore } from "./buyer-store.js";
2
- import Table from "cli-table3";
3
- import {
4
- detectProviders,
5
- PROXY_ACCESS_TOKEN_PLACEHOLDER,
6
- SUPPORTED_PROVIDER_IDS,
7
- type ProviderCandidate,
8
- type ProviderId,
9
- type ProviderRuntimeConfig,
10
- } from "./provider-install.js";
11
- import {
12
- discoverSellerBackedModels,
13
- type ModelCatalogEntry,
14
- type SellerCatalogEntry,
15
- } from "./seller-catalog.js";
16
- import {
17
- printDoctorClawtipWallet,
18
- readDoctorClawtipWallet,
19
- type DoctorClawtipWalletSummary,
20
- } from "./doctor-clawtip-wallet.js";
21
- import type { PublicManualProviderConfig } from "./provider-routing-config.js";
22
-
23
- /**
24
- * `tb doctor` 输出里 provider 行的形状。
25
- * 在 `ProviderCandidate` 之上叠加 buyer 端持久化的 runtime config(如果存在)。
26
- */
27
- export interface DoctorProviderView extends ProviderCandidate {
28
- /** 持久化的 provider runtime config(opencode 的 `defaultModel` 等) */
29
- runtimeConfig?: ProviderRuntimeConfig;
30
- /** runtime config 的最近更新时间 */
31
- runtimeConfigUpdatedAt?: string;
32
- }
33
-
34
- /**
35
- * `tb doctor` 输出里 seller 行的形状。
36
- * 字段命名与 `RegistrySeller` 对齐,但额外包含状态、错误信息、manifest sellerId 等。
37
- */
38
- export interface DoctorSellerEntry {
39
- id: string;
40
- name?: string;
41
- url: string;
42
- status: string;
43
- discountRatio?: number;
44
- supportedProtocols?: string[];
45
- paymentMethods?: string[];
46
- manifestSellerId?: string;
47
- modelCount?: number;
48
- errorMessage?: string;
49
- }
50
-
51
- /**
52
- * `tb doctor` 完整诊断结果,由 `collectDoctorDiagnostics` 返回。
53
- */
54
- export interface DoctorDiagnostics {
55
- /** 控制面 / 代理端点可达性 */
56
- access: DoctorAccessSummary;
57
- /** Clawtip 钱包信息 */
58
- clawtipWallet: DoctorClawtipWalletSummary;
59
- /** buyer 本地 Manual provider 状态 */
60
- manualProviders: DoctorManualProvidersSummary;
61
- /** 模型目录汇总 */
62
- models: DoctorModelsSummary;
63
- /** provider 安装 / 配置状态 */
64
- providers: DoctorProviderView[];
65
- /** seller 注册表 + 探测结果 */
66
- sellers: DoctorSellersSummary;
67
- }
68
-
69
- interface RemoteJsonResult<T> {
70
- url: string;
71
- available: boolean;
72
- statusCode?: number;
73
- data?: T;
74
- error?: string;
75
- }
76
-
77
- interface DoctorEndpointStatus {
78
- id: string;
79
- name: string;
80
- url: string;
81
- probeUrl?: string;
82
- available: boolean;
83
- requiresToken: boolean;
84
- token?: string;
85
- error?: string;
86
- }
87
-
88
- interface DoctorSellerResponse {
89
- registryUrl?: string;
90
- version?: number;
91
- defaultSeller?: string;
92
- sellers: DoctorSellerEntry[];
93
- }
94
-
95
- interface DoctorRegistryDocument {
96
- version?: number;
97
- defaultSeller?: string;
98
- sellers: Array<{
99
- id: string;
100
- name?: string;
101
- url: string;
102
- supportedProtocols?: string[];
103
- paymentMethods?: string[];
104
- }>;
105
- }
106
-
107
- interface DoctorModelsResponse {
108
- object?: string;
109
- registryUrl?: string;
110
- data: ModelCatalogEntry[];
111
- sellers?: DoctorSellerEntry[];
112
- }
113
-
114
- interface DoctorManualProvidersResponse {
115
- providers: PublicManualProviderConfig[];
116
- }
117
-
118
- /**
119
- * `tb doctor` 模型表的单行:按 model id 聚合 seller 的折扣 / 价格区间。
120
- */
121
- export interface DoctorModelSummaryEntry {
122
- id: string;
123
- sellerCount: number;
124
- localProviderCount: number;
125
- sourceRange: string;
126
- discountMin?: number;
127
- discountMax?: number;
128
- discountRange: string;
129
- inputPriceMinMicrosPer1m?: number;
130
- inputPriceMaxMicrosPer1m?: number;
131
- outputPriceMinMicrosPer1m?: number;
132
- outputPriceMaxMicrosPer1m?: number;
133
- priceRange: string;
134
- }
135
-
136
- interface DoctorFetchResults {
137
- healthResult: RemoteJsonResult<Record<string, unknown>>;
138
- sellersResult: RemoteJsonResult<DoctorSellerResponse>;
139
- modelsResult: RemoteJsonResult<DoctorModelsResponse>;
140
- manualProvidersResult: RemoteJsonResult<DoctorManualProvidersResponse>;
141
- proxyModelsResult: RemoteJsonResult<{ object?: string; data?: Array<{ id?: string }> }>;
142
- registryResult: RemoteJsonResult<DoctorRegistryDocument>;
143
- }
144
-
145
- interface DoctorFetchPromises {
146
- healthResult: Promise<RemoteJsonResult<Record<string, unknown>>>;
147
- sellersResult: Promise<RemoteJsonResult<DoctorSellerResponse>>;
148
- modelsResult: Promise<RemoteJsonResult<DoctorModelsResponse>>;
149
- manualProvidersResult: Promise<RemoteJsonResult<DoctorManualProvidersResponse>>;
150
- proxyModelsResult: Promise<RemoteJsonResult<{ object?: string; data?: Array<{ id?: string }> }>>;
151
- registryResult: Promise<RemoteJsonResult<DoctorRegistryDocument>>;
152
- }
153
-
154
- interface DoctorAccessAvailability {
155
- sellersAvailable?: boolean;
156
- sellersError?: string;
157
- modelsAvailable?: boolean;
158
- modelsError?: string;
159
- }
160
-
161
- interface DoctorRenderOptions {
162
- controlPort: number;
163
- proxyPort: number;
164
- daemonRunning: boolean;
165
- daemonError?: string;
166
- sellerRegistryUrl?: string;
167
- providers: DoctorProviderView[];
168
- writeLine?: (line: string) => void;
169
- }
170
-
171
- interface DoctorCollectOptions {
172
- controlPort: number;
173
- proxyPort: number;
174
- daemonRunning: boolean;
175
- daemonError?: string;
176
- sellerRegistryUrl?: string;
177
- providers: DoctorProviderView[];
178
- }
179
-
180
- type DoctorAccessSummary = {
181
- controlBaseUrl: string;
182
- proxyBaseUrl: string;
183
- openAiBaseUrl: string;
184
- anthropicBaseUrl: string;
185
- token: string;
186
- endpoints: DoctorEndpointStatus[];
187
- };
188
-
189
- type DoctorSellersSummary = {
190
- available: boolean;
191
- registryUrl?: string;
192
- version?: number;
193
- defaultSeller?: string;
194
- sellers: DoctorSellerEntry[];
195
- error?: string;
196
- };
197
-
198
- export type DoctorManualProvidersSummary = {
199
- available: boolean;
200
- count: number;
201
- configuredKeyCount: number;
202
- providers: PublicManualProviderConfig[];
203
- error?: string;
204
- };
205
-
206
- /**
207
- * `tb doctor` 模型目录汇总:原始 `data` + 聚合后的 `grouped`(按 model id 分组)。
208
- */
209
- export type DoctorModelsSummary = {
210
- available: boolean;
211
- count: number;
212
- uniqueCount: number;
213
- registryUrl?: string;
214
- data: ModelCatalogEntry[];
215
- grouped: DoctorModelSummaryEntry[];
216
- sellers: DoctorSellerEntry[];
217
- error?: string;
218
- };
219
-
220
- function parseJsonText(text: string): unknown {
221
- if (!text.trim()) {
222
- return undefined;
223
- }
224
- return JSON.parse(text) as unknown;
225
- }
226
-
227
- function extractRemoteError(payload: unknown, fallback: string): string {
228
- if (payload && typeof payload === "object") {
229
- if ("error" in payload) {
230
- const error = (payload as { error?: unknown }).error;
231
- if (typeof error === "string" && error.trim()) {
232
- return error;
233
- }
234
- if (error && typeof error === "object" && "message" in error && typeof error.message === "string" && error.message.trim()) {
235
- return error.message;
236
- }
237
- }
238
- if ("message" in payload && typeof (payload as { message?: unknown }).message === "string" && (payload as { message: string }).message.trim()) {
239
- return (payload as { message: string }).message;
240
- }
241
- }
242
- return fallback;
243
- }
244
-
245
- async function fetchJsonDocument<T>(url: string): Promise<RemoteJsonResult<T>> {
246
- try {
247
- const response = await fetch(url);
248
- const text = await response.text();
249
- const payload = text ? parseJsonText(text) : undefined;
250
- if (!response.ok) {
251
- return {
252
- url,
253
- available: false,
254
- statusCode: response.status,
255
- error: extractRemoteError(payload, `HTTP ${response.status}`),
256
- };
257
- }
258
- return {
259
- url,
260
- available: true,
261
- statusCode: response.status,
262
- data: payload as T,
263
- };
264
- } catch (error: unknown) {
265
- return {
266
- url,
267
- available: false,
268
- error: error instanceof Error ? error.message : String(error),
269
- };
270
- }
271
- }
272
-
273
- function providerRuntimeSummary(runtimeConfig?: ProviderRuntimeConfig): string | undefined {
274
- if (!runtimeConfig) {
275
- return undefined;
276
- }
277
- if (runtimeConfig.selectionKind === "single-model") {
278
- return [
279
- runtimeConfig.protocolPreference,
280
- runtimeConfig.defaultModel,
281
- ].filter(Boolean).join(" · ");
282
- }
283
-
284
- const bindings = [
285
- runtimeConfig.roles.haiku?.upstreamModel ? `haiku=${runtimeConfig.roles.haiku.upstreamModel}` : undefined,
286
- runtimeConfig.roles.sonnet?.upstreamModel ? `sonnet=${runtimeConfig.roles.sonnet.upstreamModel}` : undefined,
287
- runtimeConfig.roles.opus?.upstreamModel ? `opus=${runtimeConfig.roles.opus.upstreamModel}` : undefined,
288
- runtimeConfig.fallbackModel ? `fallback=${runtimeConfig.fallbackModel}` : undefined,
289
- ].filter(Boolean);
290
- return [runtimeConfig.protocolPreference, ...bindings].join(" · ");
291
- }
292
-
293
- function mergeDoctorSellerEntries(
294
- configuredSellers: DoctorSellerEntry[],
295
- probedSellers: DoctorSellerEntry[],
296
- ): DoctorSellerEntry[] {
297
- const configuredById = new Map(configuredSellers.map((seller) => [seller.id, seller]));
298
- const merged: DoctorSellerEntry[] = [];
299
-
300
- for (const seller of probedSellers) {
301
- const configured = configuredById.get(seller.id);
302
- merged.push({
303
- ...configured,
304
- ...seller,
305
- discountRatio: seller.discountRatio ?? configured?.discountRatio,
306
- supportedProtocols: seller.supportedProtocols || configured?.supportedProtocols,
307
- paymentMethods: seller.paymentMethods || configured?.paymentMethods,
308
- modelCount: seller.modelCount ?? configured?.modelCount,
309
- errorMessage: seller.errorMessage || configured?.errorMessage,
310
- });
311
- configuredById.delete(seller.id);
312
- }
313
-
314
- for (const seller of configuredById.values()) {
315
- merged.push(seller);
316
- }
317
-
318
- return merged;
319
- }
320
-
321
- function registrySellersToDoctorEntries(registry?: DoctorRegistryDocument): DoctorSellerEntry[] {
322
- if (!registry?.sellers) {
323
- return [];
324
- }
325
- return registry.sellers.map((seller) => ({
326
- id: seller.id,
327
- name: seller.name,
328
- url: seller.url,
329
- status: "configured",
330
- supportedProtocols: seller.supportedProtocols || [],
331
- paymentMethods: seller.paymentMethods || [],
332
- }));
333
- }
334
-
335
- function sellerCatalogEntriesToDoctorEntries(sellers: SellerCatalogEntry[]): DoctorSellerEntry[] {
336
- return sellers.map((seller) => ({
337
- id: seller.id,
338
- name: seller.name,
339
- url: seller.url,
340
- status: seller.status,
341
- discountRatio: seller.discountRatio,
342
- supportedProtocols: seller.supportedProtocols,
343
- paymentMethods: seller.paymentMethods,
344
- manifestSellerId: seller.manifestSellerId,
345
- modelCount: seller.modelCount,
346
- errorMessage: seller.errorMessage,
347
- }));
348
- }
349
-
350
- function discountRatioFromSeller(seller: DoctorSellerEntry): number | undefined {
351
- if (typeof seller.discountRatio === "number") {
352
- return seller.discountRatio;
353
- }
354
- const match = seller.name?.match(/([0-9]+(?:\.[0-9]+)?)\s+discount/i);
355
- if (!match) {
356
- return undefined;
357
- }
358
- const parsed = Number(match[1]);
359
- return Number.isFinite(parsed) ? parsed : undefined;
360
- }
361
-
362
- function formatDiscountRatio(value: number): string {
363
- const ratio = Math.max(0, value);
364
- if (ratio === 0) return "免费";
365
- if (Math.abs(ratio - 1) < 0.0001) return "原价";
366
- const folded = Math.round(ratio * 100) / 10;
367
- return `${Number.isInteger(folded) ? String(folded) : folded.toFixed(1)}折`;
368
- }
369
-
370
- function formatUsdPer1m(microsPer1m: number): string {
371
- const usd = microsPer1m / 1_000_000;
372
- return `$${usd.toFixed(2).replace(/\.?0+$/, "")}`;
373
- }
374
-
375
- function formatPriceRange(minMicros?: number, maxMicros?: number): string {
376
- if (minMicros == null || maxMicros == null) {
377
- return "-";
378
- }
379
- if (minMicros === maxMicros) {
380
- return formatUsdPer1m(minMicros);
381
- }
382
- return `${formatUsdPer1m(minMicros)}~${formatUsdPer1m(maxMicros)}`;
383
- }
384
-
385
- function formatModelSourceRange(marketplaceCount: number, localProviderCount: number): string {
386
- const parts = [
387
- marketplaceCount > 0 ? `${marketplaceCount} marketplace` : undefined,
388
- localProviderCount > 0 ? `${localProviderCount} local` : undefined,
389
- ].filter(Boolean);
390
- return parts.length > 0 ? parts.join(" + ") : "-";
391
- }
392
-
393
- function modelsHaveExplicitPriceData(models: ModelCatalogEntry[]): boolean {
394
- return models.some((model) =>
395
- typeof model.inputPriceMicrosPer1m === "number" ||
396
- typeof model.outputPriceMicrosPer1m === "number"
397
- );
398
- }
399
-
400
- function buildDoctorModelSummaryEntries(
401
- models: ModelCatalogEntry[],
402
- sellers: DoctorSellerEntry[],
403
- ): DoctorModelSummaryEntry[] {
404
- const grouped = new Map<string, {
405
- sellerIds: Set<string>;
406
- localProviderIds: Set<string>;
407
- discounts: number[];
408
- inputPrices: number[];
409
- outputPrices: number[];
410
- }>();
411
- const discountBySellerId = new Map(
412
- sellers
413
- .map((seller) => [seller.id, discountRatioFromSeller(seller)] as const)
414
- .filter((entry): entry is readonly [string, number] => typeof entry[1] === "number")
415
- );
416
-
417
- for (const model of models) {
418
- const entry = grouped.get(model.id) || {
419
- sellerIds: new Set<string>(),
420
- localProviderIds: new Set<string>(),
421
- discounts: [],
422
- inputPrices: [],
423
- outputPrices: [],
424
- };
425
- if (!entry.sellerIds.has(model.sellerId)) {
426
- entry.sellerIds.add(model.sellerId);
427
- if (model.paymentMethods.includes("provider_key")) {
428
- entry.localProviderIds.add(model.sellerId);
429
- }
430
- const discount = discountBySellerId.get(model.sellerId);
431
- if (typeof discount === "number") {
432
- entry.discounts.push(discount);
433
- }
434
- if (typeof model.inputPriceMicrosPer1m === "number") {
435
- entry.inputPrices.push(model.inputPriceMicrosPer1m);
436
- }
437
- if (typeof model.outputPriceMicrosPer1m === "number") {
438
- entry.outputPrices.push(model.outputPriceMicrosPer1m);
439
- }
440
- }
441
- grouped.set(model.id, entry);
442
- }
443
-
444
- return Array.from(grouped.entries())
445
- .map(([id, entry]) => {
446
- const marketplaceCount = Math.max(0, entry.sellerIds.size - entry.localProviderIds.size);
447
- const discountMin = entry.discounts.length > 0 ? Math.min(...entry.discounts) : undefined;
448
- const discountMax = entry.discounts.length > 0 ? Math.max(...entry.discounts) : undefined;
449
- const inputPriceMinMicrosPer1m = entry.inputPrices.length > 0 ? Math.min(...entry.inputPrices) : undefined;
450
- const inputPriceMaxMicrosPer1m = entry.inputPrices.length > 0 ? Math.max(...entry.inputPrices) : undefined;
451
- const outputPriceMinMicrosPer1m = entry.outputPrices.length > 0 ? Math.min(...entry.outputPrices) : undefined;
452
- const outputPriceMaxMicrosPer1m = entry.outputPrices.length > 0 ? Math.max(...entry.outputPrices) : undefined;
453
- const discountRange = discountMin == null || discountMax == null
454
- ? "-"
455
- : discountMin === discountMax
456
- ? formatDiscountRatio(discountMin)
457
- : `${formatDiscountRatio(discountMin)}~${formatDiscountRatio(discountMax)}`;
458
- const priceRange = inputPriceMinMicrosPer1m == null || outputPriceMinMicrosPer1m == null
459
- ? "-"
460
- : `in ${formatPriceRange(inputPriceMinMicrosPer1m, inputPriceMaxMicrosPer1m)} / out ${formatPriceRange(outputPriceMinMicrosPer1m, outputPriceMaxMicrosPer1m)}`;
461
- return {
462
- id,
463
- sellerCount: entry.sellerIds.size,
464
- localProviderCount: entry.localProviderIds.size,
465
- sourceRange: formatModelSourceRange(marketplaceCount, entry.localProviderIds.size),
466
- discountMin,
467
- discountMax,
468
- discountRange,
469
- inputPriceMinMicrosPer1m,
470
- inputPriceMaxMicrosPer1m,
471
- outputPriceMinMicrosPer1m,
472
- outputPriceMaxMicrosPer1m,
473
- priceRange,
474
- };
475
- })
476
- .sort((left, right) => left.id.localeCompare(right.id));
477
- }
478
-
479
- function startDoctorFetches(
480
- controlPort: number,
481
- proxyPort: number,
482
- daemonRunning: boolean,
483
- daemonError: string | undefined,
484
- sellerRegistryUrl?: string,
485
- ): DoctorFetchPromises {
486
- const controlBaseUrl = `http://127.0.0.1:${controlPort}`;
487
- const openAiBaseUrl = `http://127.0.0.1:${proxyPort}/v1`;
488
- const unavailableMessage = daemonError || "tb-proxyd is not running";
489
-
490
- if (!daemonRunning) {
491
- return {
492
- healthResult: Promise.resolve({ url: `${controlBaseUrl}/health`, available: false, error: unavailableMessage }),
493
- sellersResult: Promise.resolve({ url: `${controlBaseUrl}/sellers`, available: false, error: unavailableMessage }),
494
- modelsResult: Promise.resolve({ url: `${controlBaseUrl}/models`, available: false, error: unavailableMessage }),
495
- manualProvidersResult: Promise.resolve({ url: `${controlBaseUrl}/routing/manual-providers`, available: false, error: unavailableMessage }),
496
- proxyModelsResult: Promise.resolve({ url: `${openAiBaseUrl}/models`, available: false, error: unavailableMessage }),
497
- registryResult: Promise.resolve({ url: sellerRegistryUrl || "", available: false, error: unavailableMessage }),
498
- };
499
- }
500
-
501
- return {
502
- healthResult: fetchJsonDocument<Record<string, unknown>>(`${controlBaseUrl}/health`),
503
- sellersResult: fetchJsonDocument<DoctorSellerResponse>(`${controlBaseUrl}/sellers`),
504
- modelsResult: fetchJsonDocument<DoctorModelsResponse>(`${controlBaseUrl}/models`),
505
- manualProvidersResult: fetchJsonDocument<DoctorManualProvidersResponse>(`${controlBaseUrl}/routing/manual-providers`),
506
- proxyModelsResult: fetchJsonDocument<{ object?: string; data?: Array<{ id?: string }> }>(`${openAiBaseUrl}/models`),
507
- registryResult: sellerRegistryUrl
508
- ? fetchJsonDocument<DoctorRegistryDocument>(sellerRegistryUrl)
509
- : Promise.resolve({ url: "", available: false, error: "registry url unavailable" }),
510
- };
511
- }
512
-
513
- async function resolveDoctorFetches(fetches: DoctorFetchPromises): Promise<DoctorFetchResults> {
514
- const [
515
- healthResult,
516
- sellersResult,
517
- modelsResult,
518
- manualProvidersResult,
519
- proxyModelsResult,
520
- registryResult,
521
- ] = await Promise.all([
522
- fetches.healthResult,
523
- fetches.sellersResult,
524
- fetches.modelsResult,
525
- fetches.manualProvidersResult,
526
- fetches.proxyModelsResult,
527
- fetches.registryResult,
528
- ]);
529
- return {
530
- healthResult,
531
- sellersResult,
532
- modelsResult,
533
- manualProvidersResult,
534
- proxyModelsResult,
535
- registryResult,
536
- };
537
- }
538
-
539
- function buildDoctorSellerSummary(
540
- sellersResult: RemoteJsonResult<DoctorSellerResponse>,
541
- registryResult: RemoteJsonResult<DoctorRegistryDocument>,
542
- sellerRegistryUrl?: string,
543
- probedSellers: DoctorSellerEntry[] = [],
544
- ): DoctorSellersSummary {
545
- const sellersData = sellersResult.data;
546
- const configuredSellers = mergeDoctorSellerEntries(
547
- registrySellersToDoctorEntries(registryResult.data),
548
- sellersData?.sellers || [],
549
- );
550
- const mergedSellers = probedSellers.length > 0
551
- ? mergeDoctorSellerEntries(configuredSellers, probedSellers)
552
- : configuredSellers;
553
-
554
- return {
555
- available: sellersResult.available || registryResult.available || mergedSellers.length > 0,
556
- registryUrl: sellersData?.registryUrl || sellerRegistryUrl,
557
- version: sellersData?.version || registryResult.data?.version,
558
- defaultSeller: sellersData?.defaultSeller || registryResult.data?.defaultSeller,
559
- sellers: mergedSellers,
560
- error: sellersResult.error || registryResult.error,
561
- };
562
- }
563
-
564
- function buildDoctorManualProvidersSummary(
565
- manualProvidersResult: RemoteJsonResult<DoctorManualProvidersResponse>
566
- ): DoctorManualProvidersSummary {
567
- const providers = manualProvidersResult.data?.providers || [];
568
- return {
569
- available: manualProvidersResult.available,
570
- count: providers.length,
571
- configuredKeyCount: providers.filter((provider) => provider.keyRef?.configured).length,
572
- providers,
573
- error: manualProvidersResult.error,
574
- };
575
- }
576
-
577
- function buildDoctorAccessSummary(
578
- controlPort: number,
579
- proxyPort: number,
580
- healthResult: RemoteJsonResult<Record<string, unknown>>,
581
- proxyModelsResult: RemoteJsonResult<{ object?: string; data?: Array<{ id?: string }> }>,
582
- availability: DoctorAccessAvailability = {},
583
- ): DoctorAccessSummary {
584
- const controlBaseUrl = `http://127.0.0.1:${controlPort}`;
585
- const proxyBaseUrl = `http://127.0.0.1:${proxyPort}`;
586
- const openAiBaseUrl = `${proxyBaseUrl}/v1`;
587
- const anthropicBaseUrl = proxyBaseUrl;
588
-
589
- return {
590
- controlBaseUrl,
591
- proxyBaseUrl,
592
- openAiBaseUrl,
593
- anthropicBaseUrl,
594
- token: PROXY_ACCESS_TOKEN_PLACEHOLDER,
595
- endpoints: [
596
- {
597
- id: "control.health",
598
- name: "Control Plane Health",
599
- url: `${controlBaseUrl}/health`,
600
- available: healthResult.available,
601
- requiresToken: false,
602
- error: healthResult.error,
603
- },
604
- {
605
- id: "control.sellers",
606
- name: "Seller Registry",
607
- url: `${controlBaseUrl}/sellers`,
608
- available: availability.sellersAvailable ?? true,
609
- requiresToken: false,
610
- error: availability.sellersError,
611
- },
612
- {
613
- id: "control.models",
614
- name: "Seller-backed Models",
615
- url: `${controlBaseUrl}/models`,
616
- available: availability.modelsAvailable ?? true,
617
- requiresToken: false,
618
- error: availability.modelsError,
619
- },
620
- {
621
- id: "proxy.openai",
622
- name: "OpenAI-compatible Proxy",
623
- url: openAiBaseUrl,
624
- probeUrl: `${openAiBaseUrl}/models`,
625
- available: proxyModelsResult.available,
626
- requiresToken: true,
627
- token: PROXY_ACCESS_TOKEN_PLACEHOLDER,
628
- error: proxyModelsResult.error,
629
- },
630
- {
631
- id: "proxy.anthropic",
632
- name: "Anthropic-compatible Proxy",
633
- url: anthropicBaseUrl,
634
- probeUrl: `${openAiBaseUrl}/models`,
635
- available: proxyModelsResult.available,
636
- requiresToken: true,
637
- token: PROXY_ACCESS_TOKEN_PLACEHOLDER,
638
- error: proxyModelsResult.error,
639
- },
640
- ],
641
- };
642
- }
643
-
644
- function buildDoctorModelsSummary(
645
- modelsResult: RemoteJsonResult<DoctorModelsResponse>,
646
- sellers: DoctorSellerEntry[],
647
- ): DoctorModelsSummary {
648
- const modelsData = modelsResult.data;
649
- const grouped = buildDoctorModelSummaryEntries(modelsData?.data || [], sellers);
650
- return {
651
- available: modelsResult.available,
652
- count: modelsData?.data?.length || 0,
653
- uniqueCount: grouped.length,
654
- registryUrl: modelsData?.registryUrl,
655
- data: modelsData?.data || [],
656
- grouped,
657
- sellers,
658
- error: modelsResult.error,
659
- };
660
- }
661
-
662
- function providerStatusIcon(status: DoctorProviderView["status"]): string {
663
- if (status === "configured") {
664
- return "✅";
665
- }
666
- if (status === "installed") {
667
- return "🟡";
668
- }
669
- return "🔘";
670
- }
671
-
672
- function remoteStatusIcon(available: boolean): string {
673
- return available ? "✅" : "❌";
674
- }
675
-
676
- function formatList(values?: string[]): string {
677
- return values && values.length > 0 ? values.join(", ") : "-";
678
- }
679
-
680
- function defaultWriter(line: string): void {
681
- console.log(line);
682
- }
683
-
684
- function printDoctorAccess(access: DoctorAccessSummary, writeLine: (line: string) => void): void {
685
- writeLine("Access check complete.");
686
- writeLine(`Proxy token: ${access.token}`);
687
- for (const endpoint of access.endpoints) {
688
- writeLine(`${remoteStatusIcon(endpoint.available)} ${endpoint.name}`);
689
- writeLine(` URL: ${endpoint.url}`);
690
- if (endpoint.probeUrl) {
691
- writeLine(` Probe: ${endpoint.probeUrl}`);
692
- }
693
- if (endpoint.requiresToken && endpoint.token) {
694
- writeLine(` Token: ${endpoint.token}`);
695
- }
696
- if (!endpoint.available && endpoint.error) {
697
- writeLine(` Error: ${endpoint.error}`);
698
- }
699
- }
700
- }
701
-
702
- function printDoctorSellers(sellers: DoctorSellersSummary, writeLine: (line: string) => void): void {
703
- writeLine("Seller registry refresh complete.");
704
- if (!sellers.available) {
705
- writeLine(`❌ Seller registry unavailable: ${sellers.error || "unknown error"}`);
706
- return;
707
- }
708
- writeLine(`Registry: ${sellers.registryUrl || "-"}`);
709
- writeLine(`Default seller: ${sellers.defaultSeller || "-"}`);
710
- for (const seller of sellers.sellers) {
711
- const label = seller.name ? `${seller.name} (${seller.id})` : seller.id;
712
- const icon = seller.status === "ok" || seller.status === "configured"
713
- ? "✅"
714
- : seller.status === "failed"
715
- ? "❌"
716
- : "🔘";
717
- writeLine(`${icon} ${label} [${seller.status}]`);
718
- writeLine(` URL: ${seller.url}`);
719
- writeLine(` Protocols: ${formatList(seller.supportedProtocols)}`);
720
- writeLine(` Payments: ${formatList(seller.paymentMethods)}`);
721
- if (seller.modelCount != null) {
722
- writeLine(` Models: ${seller.modelCount}`);
723
- }
724
- if (seller.errorMessage) {
725
- writeLine(` Error: ${seller.errorMessage}`);
726
- }
727
- }
728
- }
729
-
730
- function printDoctorManualProviders(
731
- manualProviders: DoctorManualProvidersSummary,
732
- writeLine: (line: string) => void
733
- ): void {
734
- writeLine("Manual provider check complete.");
735
- if (!manualProviders.available) {
736
- writeLine(`❌ Manual providers unavailable: ${manualProviders.error || "unknown error"}`);
737
- return;
738
- }
739
- writeLine(`Manual providers: ${manualProviders.count}`);
740
- writeLine(`Configured keys: ${manualProviders.configuredKeyCount}/${manualProviders.count}`);
741
- for (const provider of manualProviders.providers) {
742
- const icon = provider.enabled && provider.keyRef?.configured
743
- ? "✅"
744
- : provider.enabled
745
- ? "⚠️"
746
- : "🔘";
747
- const keyRef = provider.keyRef
748
- ? `${provider.keyRef.kind}:${provider.keyRef.name} ${provider.keyRef.configured ? "configured" : "missing"}`
749
- : "missing";
750
- writeLine(`${icon} ${provider.name} (${provider.id}) [${provider.enabled ? "enabled" : "disabled"}]`);
751
- writeLine(` Key: ${keyRef}`);
752
- writeLine(` Models: ${provider.models.length}`);
753
- writeLine(` Protocols: ${formatList(provider.supportedProtocols)}`);
754
- if (provider.lastAccess) {
755
- writeLine(` Last access: ${provider.lastAccess}`);
756
- }
757
- if (provider.status) {
758
- writeLine(` Status: ${provider.status}`);
759
- }
760
- if (provider.errorMessage) {
761
- writeLine(` Error: ${provider.errorMessage}`);
762
- }
763
- }
764
- }
765
-
766
- /**
767
- * 打印模型目录汇总(写到 `writeLine`,默认 stdout)。
768
- *
769
- * @param models 模型汇总
770
- * @param writeLine 自定义输出函数(测试可注入)
771
- */
772
- export function printDoctorModelsSummary(
773
- models: DoctorModelsSummary,
774
- writeLine: (line: string) => void = defaultWriter,
775
- ): void {
776
- writeLine("Model catalog refresh complete.");
777
- if (!models.available) {
778
- writeLine(`❌ Model catalog unavailable: ${models.error || "unknown error"}`);
779
- return;
780
- }
781
- writeLine(`Unique models: ${models.uniqueCount}`);
782
- writeLine(`Seller offers: ${models.count}`);
783
-
784
- const table = new Table({
785
- head: ["Model ID", "Seller Count", "Sources", "Discount Range", "Price Range"],
786
- style: {
787
- head: []
788
- }
789
- });
790
- for (const entry of models.grouped) {
791
- table.push([
792
- entry.id,
793
- String(entry.sellerCount),
794
- entry.sourceRange,
795
- entry.discountRange,
796
- entry.priceRange
797
- ]);
798
- }
799
- writeLine(table.toString());
800
- }
801
-
802
- /**
803
- * 读取 buyer 端已知的 provider 列表,把 runtime config(如果有)合并到 `DoctorProviderView`。
804
- *
805
- * @returns 每个 provider 的 view(数组)
806
- */
807
- export function readDoctorProviders(): DoctorProviderView[] {
808
- const store = new BuyerStore();
809
- try {
810
- const runtimeConfigByProvider = new Map<ProviderId, { config: ProviderRuntimeConfig; updatedAt: string }>();
811
- for (const providerId of SUPPORTED_PROVIDER_IDS) {
812
- const record = store.getProviderRuntimeConfig<ProviderRuntimeConfig>(providerId);
813
- if (record) {
814
- runtimeConfigByProvider.set(providerId, {
815
- config: record.config,
816
- updatedAt: record.updatedAt,
817
- });
818
- }
819
- }
820
-
821
- return detectProviders().map((provider) => {
822
- const runtime = runtimeConfigByProvider.get(provider.id);
823
- return {
824
- ...provider,
825
- runtimeConfig: runtime?.config,
826
- runtimeConfigUpdatedAt: runtime?.updatedAt,
827
- };
828
- });
829
- } finally {
830
- store.close();
831
- }
832
- }
833
-
834
- /**
835
- * 打印 provider 列表("Programming Terminals" 段)。
836
- *
837
- * @param providers provider view 列表
838
- * @param writeLine 自定义输出函数
839
- */
840
- export function printDoctorProviders(
841
- providers: DoctorProviderView[],
842
- writeLine: (line: string) => void = defaultWriter,
843
- ): void {
844
- writeLine("\n--- Programming Terminals ---");
845
- for (const provider of providers) {
846
- writeLine(`${providerStatusIcon(provider.status)} ${provider.name} [${provider.status}]`);
847
- if (provider.commandName) {
848
- writeLine(` Command: ${provider.commandName}${provider.executablePath ? ` -> ${provider.executablePath}` : " (not found in PATH)"}`);
849
- }
850
- writeLine(` Config: ${provider.configPath}`);
851
- if (provider.observedPaths && provider.observedPaths.length > 0) {
852
- writeLine(` Native hints: ${provider.observedPaths.join(", ")}`);
853
- }
854
- const runtimeSummary = providerRuntimeSummary(provider.runtimeConfig);
855
- if (runtimeSummary) {
856
- writeLine(` Runtime: ${runtimeSummary}`);
857
- }
858
- writeLine(` Notes: ${provider.reason}`);
859
- }
860
- }
861
-
862
- /**
863
- * 收集 `tb doctor` 需要的全部诊断信息。
864
- * 1. 并发拉取 daemon health / sellers / models / proxy models / registry 五个端点。
865
- * 2. 合并 registry 配置 + 探测结果 + catalog 拉取结果。
866
- * 3. 返回完整 `DoctorDiagnostics`(供 JSON 消费者或自定义渲染使用)。
867
- *
868
- * @param options 收集选项(端口 / daemon 状态 / registry URL / providers)
869
- * @returns 完整诊断结果
870
- */
871
- export async function collectDoctorDiagnostics(options: DoctorCollectOptions): Promise<DoctorDiagnostics> {
872
- const fetches = startDoctorFetches(
873
- options.controlPort,
874
- options.proxyPort,
875
- options.daemonRunning,
876
- options.daemonError,
877
- options.sellerRegistryUrl,
878
- );
879
- const results = await resolveDoctorFetches(fetches);
880
- const sellers = buildDoctorSellerSummary(
881
- results.sellersResult,
882
- results.registryResult,
883
- options.sellerRegistryUrl,
884
- results.modelsResult.data?.sellers || [],
885
- );
886
- const models = buildDoctorModelsSummary(results.modelsResult, sellers.sellers);
887
- const manualProviders = buildDoctorManualProvidersSummary(results.manualProvidersResult);
888
-
889
- return {
890
- access: buildDoctorAccessSummary(
891
- options.controlPort,
892
- options.proxyPort,
893
- results.healthResult,
894
- results.proxyModelsResult,
895
- {
896
- sellersAvailable: sellers.available,
897
- sellersError: sellers.error,
898
- modelsAvailable: models.available,
899
- modelsError: models.error,
900
- },
901
- ),
902
- clawtipWallet: readDoctorClawtipWallet(),
903
- manualProviders,
904
- models,
905
- providers: options.providers,
906
- sellers,
907
- };
908
- }
909
-
910
- /**
911
- * 单独收集 `tb models` 需要的模型汇总(不包含 providers)。
912
- * daemon 拉到的数据缺价格时,回退用 `discoverSellerBackedModels` 重新拉 catalog。
913
- *
914
- * @param options 同 `collectDoctorDiagnostics`(去掉 providers)
915
- * @returns 模型汇总
916
- */
917
- export async function collectDoctorModelsSummary(options: Omit<DoctorCollectOptions, "providers">): Promise<DoctorModelsSummary> {
918
- const diagnostics = await collectDoctorDiagnostics({
919
- ...options,
920
- providers: [],
921
- });
922
- if (modelsHaveExplicitPriceData(diagnostics.models.data) || !options.sellerRegistryUrl) {
923
- return diagnostics.models;
924
- }
925
-
926
- try {
927
- const catalog = await discoverSellerBackedModels(options.sellerRegistryUrl);
928
- const sellers = sellerCatalogEntriesToDoctorEntries(catalog.sellers);
929
- return buildDoctorModelsSummary({
930
- url: options.sellerRegistryUrl,
931
- available: true,
932
- data: {
933
- object: "list",
934
- registryUrl: catalog.registryUrl,
935
- data: catalog.models,
936
- },
937
- }, sellers);
938
- } catch {
939
- return diagnostics.models;
940
- }
941
-
942
- }
943
-
944
- /**
945
- * 渐进式渲染 `tb doctor`:分阶段打印(clawtip → access → sellers → models),边等边输出,避免用户长时间看不到输出。
946
- *
947
- * @param options 渲染选项(含写入函数)
948
- * @returns 完整 `DoctorDiagnostics`
949
- */
950
- export async function renderDoctorDiagnosticsProgressively(options: DoctorRenderOptions): Promise<DoctorDiagnostics> {
951
- const writeLine = options.writeLine || defaultWriter;
952
- const clawtipWallet = readDoctorClawtipWallet();
953
- const fetches = startDoctorFetches(
954
- options.controlPort,
955
- options.proxyPort,
956
- options.daemonRunning,
957
- options.daemonError,
958
- options.sellerRegistryUrl,
959
- );
960
-
961
- printDoctorClawtipWallet(clawtipWallet, writeLine);
962
-
963
- writeLine("\n--- Access Interfaces ---");
964
- writeLine("Checking local control plane and proxy endpoints...");
965
- const [healthResult, proxyModelsResult] = await Promise.all([
966
- fetches.healthResult,
967
- fetches.proxyModelsResult,
968
- ]);
969
- const access = buildDoctorAccessSummary(
970
- options.controlPort,
971
- options.proxyPort,
972
- healthResult,
973
- proxyModelsResult,
974
- );
975
- printDoctorAccess(access, writeLine);
976
-
977
- writeLine("\n--- Manual Providers ---");
978
- writeLine("Reading local custom provider status...");
979
- const manualProvidersResult = await fetches.manualProvidersResult;
980
- const manualProviders = buildDoctorManualProvidersSummary(manualProvidersResult);
981
- printDoctorManualProviders(manualProviders, writeLine);
982
-
983
- writeLine("\n--- Sellers ---");
984
- writeLine("Refreshing seller registry...");
985
- const [sellersResult, registryResult] = await Promise.all([
986
- fetches.sellersResult,
987
- fetches.registryResult,
988
- ]);
989
- const partialSellers = buildDoctorSellerSummary(
990
- sellersResult,
991
- registryResult,
992
- options.sellerRegistryUrl,
993
- );
994
- printDoctorSellers(partialSellers, writeLine);
995
-
996
- const modelsResult = await fetches.modelsResult;
997
- const finalSellers = buildDoctorSellerSummary(
998
- sellersResult,
999
- registryResult,
1000
- options.sellerRegistryUrl,
1001
- modelsResult.data?.sellers || [],
1002
- );
1003
- const models = buildDoctorModelsSummary(modelsResult, finalSellers.sellers);
1004
- access.endpoints = buildDoctorAccessSummary(
1005
- options.controlPort,
1006
- options.proxyPort,
1007
- healthResult,
1008
- proxyModelsResult,
1009
- {
1010
- sellersAvailable: finalSellers.available,
1011
- sellersError: finalSellers.error,
1012
- modelsAvailable: models.available,
1013
- modelsError: models.error,
1014
- },
1015
- ).endpoints;
1016
- writeLine("\nModel catalog hidden in `tb doctor`. Run `tb models` for the current model summary.");
1017
-
1018
- return {
1019
- access,
1020
- clawtipWallet,
1021
- manualProviders,
1022
- models,
1023
- providers: options.providers,
1024
- sellers: finalSellers,
1025
- };
1026
- }