@tokenbuddy/tokenbuddy 1.0.35 → 1.0.37

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 (143) hide show
  1. package/dist/src/buyer-store.d.ts +6 -1
  2. package/dist/src/buyer-store.js +43 -4
  3. package/dist/src/cli.js +2 -2
  4. package/dist/src/daemon.d.ts +12 -0
  5. package/dist/src/daemon.js +791 -61
  6. package/dist/src/doctor-diagnostics.js +1 -6
  7. package/dist/src/provider-install.d.ts +2 -2
  8. package/dist/src/provider-install.js +248 -2
  9. package/dist/src/seller-catalog.d.ts +21 -0
  10. package/dist/src/seller-catalog.js +17 -0
  11. package/dist/src/seller-route-planner.d.ts +4 -1
  12. package/dist/src/seller-route-planner.js +3 -0
  13. package/dist/src/seller-routing-strategy.d.ts +3 -0
  14. package/dist/src/terminal-detect.d.ts +1 -1
  15. package/dist/src/terminal-detect.js +3 -2
  16. package/package.json +15 -2
  17. package/static/ui/assets/index-Djfl9tw5.js +271 -0
  18. package/static/ui/assets/index-DkfztCkn.css +1 -0
  19. package/static/ui/index.html +2 -2
  20. package/dist/src/buyer-store.d.ts.map +0 -1
  21. package/dist/src/buyer-store.js.map +0 -1
  22. package/dist/src/clawtip-bootstrap.d.ts.map +0 -1
  23. package/dist/src/clawtip-bootstrap.js.map +0 -1
  24. package/dist/src/cli.d.ts.map +0 -1
  25. package/dist/src/cli.js.map +0 -1
  26. package/dist/src/credit-tracker.d.ts.map +0 -1
  27. package/dist/src/credit-tracker.js.map +0 -1
  28. package/dist/src/daemon.d.ts.map +0 -1
  29. package/dist/src/daemon.js.map +0 -1
  30. package/dist/src/doctor-clawtip-wallet.d.ts.map +0 -1
  31. package/dist/src/doctor-clawtip-wallet.js.map +0 -1
  32. package/dist/src/doctor-diagnostics.d.ts.map +0 -1
  33. package/dist/src/doctor-diagnostics.js.map +0 -1
  34. package/dist/src/index.d.ts.map +0 -1
  35. package/dist/src/index.js.map +0 -1
  36. package/dist/src/init-clawtip-activation.d.ts.map +0 -1
  37. package/dist/src/init-clawtip-activation.js.map +0 -1
  38. package/dist/src/init-payment-options.d.ts.map +0 -1
  39. package/dist/src/init-payment-options.js.map +0 -1
  40. package/dist/src/init-setup.d.ts.map +0 -1
  41. package/dist/src/init-setup.js.map +0 -1
  42. package/dist/src/model-index.d.ts.map +0 -1
  43. package/dist/src/model-index.js.map +0 -1
  44. package/dist/src/package-update.d.ts.map +0 -1
  45. package/dist/src/package-update.js.map +0 -1
  46. package/dist/src/prewarm-cache.d.ts.map +0 -1
  47. package/dist/src/prewarm-cache.js.map +0 -1
  48. package/dist/src/prewarm-scheduler.d.ts.map +0 -1
  49. package/dist/src/prewarm-scheduler.js.map +0 -1
  50. package/dist/src/provider-install.d.ts.map +0 -1
  51. package/dist/src/provider-install.js.map +0 -1
  52. package/dist/src/provider-routing-config.d.ts.map +0 -1
  53. package/dist/src/provider-routing-config.js.map +0 -1
  54. package/dist/src/registry-trust.d.ts.map +0 -1
  55. package/dist/src/registry-trust.js.map +0 -1
  56. package/dist/src/route-failover.d.ts.map +0 -1
  57. package/dist/src/route-failover.js.map +0 -1
  58. package/dist/src/seller-catalog.d.ts.map +0 -1
  59. package/dist/src/seller-catalog.js.map +0 -1
  60. package/dist/src/seller-concurrency-limiter.d.ts.map +0 -1
  61. package/dist/src/seller-concurrency-limiter.js.map +0 -1
  62. package/dist/src/seller-metadata-cache.d.ts.map +0 -1
  63. package/dist/src/seller-metadata-cache.js.map +0 -1
  64. package/dist/src/seller-pool.d.ts.map +0 -1
  65. package/dist/src/seller-pool.js.map +0 -1
  66. package/dist/src/seller-route-planner.d.ts.map +0 -1
  67. package/dist/src/seller-route-planner.js.map +0 -1
  68. package/dist/src/seller-routing-config.d.ts.map +0 -1
  69. package/dist/src/seller-routing-config.js.map +0 -1
  70. package/dist/src/seller-routing-strategy.d.ts.map +0 -1
  71. package/dist/src/seller-routing-strategy.js.map +0 -1
  72. package/dist/src/stream-failover.d.ts.map +0 -1
  73. package/dist/src/stream-failover.js.map +0 -1
  74. package/dist/src/tb-clawtip-proof.d.ts.map +0 -1
  75. package/dist/src/tb-clawtip-proof.js.map +0 -1
  76. package/dist/src/tb-proxyd.d.ts.map +0 -1
  77. package/dist/src/tb-proxyd.js.map +0 -1
  78. package/dist/src/terminal-detect.d.ts.map +0 -1
  79. package/dist/src/terminal-detect.js.map +0 -1
  80. package/dist/src/terminal-image.d.ts.map +0 -1
  81. package/dist/src/terminal-image.js.map +0 -1
  82. package/src/buyer-store.ts +0 -1090
  83. package/src/clawtip-bootstrap.ts +0 -65
  84. package/src/cli.ts +0 -2243
  85. package/src/credit-tracker.ts +0 -295
  86. package/src/daemon.ts +0 -5475
  87. package/src/doctor-clawtip-wallet.ts +0 -95
  88. package/src/doctor-diagnostics.ts +0 -1026
  89. package/src/index.ts +0 -16
  90. package/src/init-clawtip-activation.ts +0 -695
  91. package/src/init-payment-options.ts +0 -373
  92. package/src/init-setup.ts +0 -165
  93. package/src/model-index.ts +0 -278
  94. package/src/package-update.ts +0 -311
  95. package/src/prewarm-cache.ts +0 -485
  96. package/src/prewarm-scheduler.ts +0 -675
  97. package/src/provider-install.ts +0 -1006
  98. package/src/provider-routing-config.ts +0 -410
  99. package/src/registry-trust.ts +0 -51
  100. package/src/route-failover.ts +0 -304
  101. package/src/seller-catalog.ts +0 -505
  102. package/src/seller-concurrency-limiter.ts +0 -161
  103. package/src/seller-metadata-cache.ts +0 -91
  104. package/src/seller-pool.ts +0 -557
  105. package/src/seller-route-planner.ts +0 -513
  106. package/src/seller-routing-config.ts +0 -211
  107. package/src/seller-routing-strategy.ts +0 -362
  108. package/src/stream-failover.ts +0 -152
  109. package/src/tb-clawtip-proof.ts +0 -28
  110. package/src/tb-proxyd.ts +0 -101
  111. package/src/terminal-detect.ts +0 -333
  112. package/src/terminal-image.ts +0 -228
  113. package/static/ui/assets/index-0MVXD7bH.css +0 -1
  114. package/static/ui/assets/index-BVbeDEwq.js +0 -271
  115. package/static/ui/assets/index-BVbeDEwq.js.map +0 -1
  116. package/tests/cli-routing.test.ts +0 -363
  117. package/tests/control-plane-ui-endpoints.test.ts +0 -1630
  118. package/tests/credit-tracker.test.ts +0 -165
  119. package/tests/daemon-413-fallback.test.ts +0 -92
  120. package/tests/daemon-classify.test.ts +0 -452
  121. package/tests/daemon-roles.test.ts +0 -92
  122. package/tests/daemon-trusted-registry-cache.test.ts +0 -132
  123. package/tests/e2e.test.ts +0 -366
  124. package/tests/image-generation-e2e.test.ts +0 -230
  125. package/tests/model-index.test.ts +0 -198
  126. package/tests/package-update.test.ts +0 -147
  127. package/tests/prewarm-cache.test.ts +0 -296
  128. package/tests/prewarm-scheduler.test.ts +0 -367
  129. package/tests/provider-routing-config.test.ts +0 -150
  130. package/tests/registry-trust.test.ts +0 -28
  131. package/tests/route-failover.test.ts +0 -222
  132. package/tests/seller-catalog-413.test.ts +0 -120
  133. package/tests/seller-catalog-utilities.test.ts +0 -124
  134. package/tests/seller-concurrency-limiter.test.ts +0 -83
  135. package/tests/seller-metadata-cache.test.ts +0 -89
  136. package/tests/seller-pool.test.ts +0 -365
  137. package/tests/seller-route-planner.test.ts +0 -312
  138. package/tests/seller-routing-config.test.ts +0 -124
  139. package/tests/seller-routing-strategy.test.ts +0 -167
  140. package/tests/stream-failover.test.ts +0 -52
  141. package/tests/thousand-seller.test.ts +0 -151
  142. package/tests/tokenbuddy.test.ts +0 -4043
  143. 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
- }