@tokenbuddy/tokenbuddy 1.0.36 → 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,410 +0,0 @@
1
- import type { SellerRoutingScorer } from "./seller-routing-strategy.js";
2
-
3
- export const PROVIDER_MODE_CONFIG_KEY = "provider-mode";
4
- export const MANUAL_PROVIDER_CONFIG_KEY = "manual-providers";
5
- export const AUTO_PROVIDER_CONFIG_KEY = "auto-provider";
6
- export const MANUAL_PROVIDER_OBSERVATIONS_CONFIG_KEY = "manual-provider-observations";
7
-
8
- export type ProviderMode = "manual" | "auto";
9
- export type ManualProviderKind = "openai-compatible";
10
- export type ProviderProtocol = "chat_completions" | "responses" | "messages" | "images_generations";
11
- export type AutoProviderRange = "recommended" | "custom";
12
- export type ManualProviderRoutingPolicy = "fallback" | "locked";
13
-
14
- export interface ProviderModeConfig {
15
- mode: ProviderMode;
16
- updatedAt: string;
17
- }
18
-
19
- export interface ManualProviderConfig {
20
- id: string;
21
- name: string;
22
- kind: ManualProviderKind;
23
- baseUrl: string;
24
- apiKeyEnv?: string;
25
- secretRef?: string;
26
- models: string[];
27
- supportedProtocols: ProviderProtocol[];
28
- enabled: boolean;
29
- notes?: string;
30
- createdAt: string;
31
- updatedAt: string;
32
- }
33
-
34
- export interface ManualProvidersConfig {
35
- version: 1;
36
- providers: ManualProviderConfig[];
37
- routing: ManualProviderRoutingConfig;
38
- updatedAt: string;
39
- }
40
-
41
- export interface ManualProviderRoutingConfig {
42
- policy: ManualProviderRoutingPolicy;
43
- lockedProviderId?: string;
44
- }
45
-
46
- export interface AutoProviderConfig {
47
- enabled: boolean;
48
- range: AutoProviderRange;
49
- scorer: SellerRoutingScorer;
50
- modelIds: string[];
51
- sellerIds: string[];
52
- maxConcurrentProviders: 10;
53
- updatedAt: string;
54
- }
55
-
56
- export interface PublicManualProviderConfig extends Omit<ManualProviderConfig, "apiKeyEnv" | "secretRef"> {
57
- keyRef?: {
58
- kind: "env" | "secret";
59
- name: string;
60
- configured: boolean;
61
- };
62
- current?: boolean;
63
- lastAccess?: string;
64
- status?: "healthy" | "degraded" | "unhealthy" | "unknown";
65
- errorClass?: string;
66
- errorMessage?: string;
67
- ttftMs?: number;
68
- avgTokensPerSecond?: number;
69
- }
70
-
71
- export interface ManualProviderObservation {
72
- providerId: string;
73
- current: boolean;
74
- lastAccess: string;
75
- status: "healthy" | "degraded" | "unhealthy" | "unknown";
76
- errorClass?: string;
77
- errorMessage?: string;
78
- ttftMs?: number;
79
- avgTokensPerSecond?: number;
80
- }
81
-
82
- export interface ManualProviderObservationsConfig {
83
- version: 1;
84
- observations: ManualProviderObservation[];
85
- updatedAt: string;
86
- }
87
-
88
- const VALID_PROTOCOLS = new Set<ProviderProtocol>(["chat_completions", "responses", "messages", "images_generations"]);
89
- const VALID_SCORERS = new Set<SellerRoutingScorer>(["balanced", "speed", "discount"]);
90
-
91
- export function defaultProviderModeConfig(now = new Date().toISOString()): ProviderModeConfig {
92
- return {
93
- mode: "manual",
94
- updatedAt: now
95
- };
96
- }
97
-
98
- export function normalizeProviderModeConfig(value: unknown, now = new Date().toISOString()): ProviderModeConfig {
99
- if (!isRecord(value)) {
100
- return defaultProviderModeConfig(now);
101
- }
102
- return {
103
- mode: readProviderMode(value.mode),
104
- updatedAt: readOptionalString(value.updatedAt) ?? now
105
- };
106
- }
107
-
108
- export function normalizeManualProvidersConfig(value: unknown, now = new Date().toISOString()): ManualProvidersConfig {
109
- if (!isRecord(value)) {
110
- return {
111
- version: 1,
112
- providers: [],
113
- routing: defaultManualProviderRoutingConfig(),
114
- updatedAt: now
115
- };
116
- }
117
- const providersValue = Array.isArray(value.providers) ? value.providers : [];
118
- const seen = new Set<string>();
119
- const providers = providersValue.map((providerValue) => {
120
- const provider = normalizeManualProviderConfig(providerValue, { now });
121
- if (seen.has(provider.id)) {
122
- throw new Error(`manual provider id is duplicated: ${provider.id}`);
123
- }
124
- seen.add(provider.id);
125
- return provider;
126
- });
127
- return {
128
- version: 1,
129
- providers,
130
- routing: normalizeManualProviderRoutingConfig(value.routing),
131
- updatedAt: readOptionalString(value.updatedAt) ?? now
132
- };
133
- }
134
-
135
- export function defaultManualProviderRoutingConfig(): ManualProviderRoutingConfig {
136
- return {
137
- policy: "fallback"
138
- };
139
- }
140
-
141
- export function normalizeManualProviderRoutingConfig(value: unknown): ManualProviderRoutingConfig {
142
- if (!isRecord(value)) {
143
- return defaultManualProviderRoutingConfig();
144
- }
145
- const policy = readManualProviderRoutingPolicy(value.policy);
146
- const lockedProviderId = readOptionalString(value.lockedProviderId);
147
- if (policy === "locked" && !lockedProviderId) {
148
- throw new Error("manual provider locked routing requires lockedProviderId");
149
- }
150
- return {
151
- policy,
152
- lockedProviderId: policy === "locked" ? readProviderId(lockedProviderId) : undefined
153
- };
154
- }
155
-
156
- export function normalizeManualProviderConfig(
157
- value: unknown,
158
- options: { now?: string; id?: string; existingIds?: ReadonlySet<string> } = {}
159
- ): ManualProviderConfig {
160
- if (!isRecord(value)) {
161
- throw new Error("manual provider config must be an object");
162
- }
163
- if ("apiKey" in value) {
164
- throw new Error("manual provider config must not contain a raw apiKey; use apiKeyEnv or secretRef");
165
- }
166
- const now = options.now ?? new Date().toISOString();
167
- const id = readProviderId(options.id ?? value.id);
168
- if (options.existingIds?.has(id)) {
169
- throw new Error(`manual provider id is duplicated: ${id}`);
170
- }
171
- const apiKeyEnv = readOptionalString(value.apiKeyEnv);
172
- const secretRef = readOptionalString(value.secretRef);
173
- if (!apiKeyEnv && !secretRef) {
174
- throw new Error("manual provider requires apiKeyEnv or secretRef");
175
- }
176
- if (apiKeyEnv && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(apiKeyEnv)) {
177
- throw new Error("manual provider apiKeyEnv must be a valid environment variable name");
178
- }
179
-
180
- return {
181
- id,
182
- name: readRequiredString(value.name, "manual provider name"),
183
- kind: readManualProviderKind(value.kind),
184
- baseUrl: normalizeProviderBaseUrl(value.baseUrl),
185
- apiKeyEnv,
186
- secretRef,
187
- models: readNonEmptyStringList(value.models, "manual provider models"),
188
- supportedProtocols: readProtocols(value.supportedProtocols),
189
- enabled: value.enabled === undefined ? true : value.enabled === true,
190
- notes: readOptionalString(value.notes),
191
- createdAt: readOptionalString(value.createdAt) ?? now,
192
- updatedAt: now
193
- };
194
- }
195
-
196
- export function normalizeAutoProviderConfig(value: unknown, now = new Date().toISOString()): AutoProviderConfig {
197
- if (!isRecord(value)) {
198
- return defaultAutoProviderConfig(now);
199
- }
200
- return {
201
- enabled: value.enabled === true,
202
- range: readAutoProviderRange(value.range),
203
- scorer: readScorer(value.scorer),
204
- modelIds: readStringList(value.modelIds),
205
- sellerIds: readStringList(value.sellerIds),
206
- maxConcurrentProviders: 10,
207
- updatedAt: readOptionalString(value.updatedAt) ?? now
208
- };
209
- }
210
-
211
- export function defaultAutoProviderConfig(now = new Date().toISOString()): AutoProviderConfig {
212
- return {
213
- enabled: false,
214
- range: "recommended",
215
- scorer: "balanced",
216
- modelIds: [],
217
- sellerIds: [],
218
- maxConcurrentProviders: 10,
219
- updatedAt: now
220
- };
221
- }
222
-
223
- export function normalizeManualProviderObservationsConfig(
224
- value: unknown,
225
- now = new Date().toISOString()
226
- ): ManualProviderObservationsConfig {
227
- if (!isRecord(value)) {
228
- return {
229
- version: 1,
230
- observations: [],
231
- updatedAt: now
232
- };
233
- }
234
- const observationsValue = Array.isArray(value.observations) ? value.observations : [];
235
- const seen = new Set<string>();
236
- const observations = observationsValue
237
- .filter(isRecord)
238
- .map((entry) => normalizeManualProviderObservation(entry, now))
239
- .filter((entry) => {
240
- if (seen.has(entry.providerId)) {
241
- return false;
242
- }
243
- seen.add(entry.providerId);
244
- return true;
245
- });
246
- return {
247
- version: 1,
248
- observations,
249
- updatedAt: readOptionalString(value.updatedAt) ?? now
250
- };
251
- }
252
-
253
- export function publicManualProviderConfig(
254
- provider: ManualProviderConfig,
255
- observation?: ManualProviderObservation,
256
- env: NodeJS.ProcessEnv = process.env
257
- ): PublicManualProviderConfig {
258
- const keyRef = provider.apiKeyEnv
259
- ? { kind: "env" as const, name: provider.apiKeyEnv, configured: Boolean(env[provider.apiKeyEnv]) }
260
- : provider.secretRef
261
- ? { kind: "secret" as const, name: provider.secretRef, configured: true }
262
- : undefined;
263
- const { apiKeyEnv: _apiKeyEnv, secretRef: _secretRef, ...publicProvider } = provider;
264
- return {
265
- ...publicProvider,
266
- keyRef,
267
- current: observation?.current,
268
- lastAccess: observation?.lastAccess,
269
- status: observation?.status,
270
- errorClass: observation?.errorClass,
271
- errorMessage: observation?.errorMessage,
272
- ttftMs: observation?.ttftMs,
273
- avgTokensPerSecond: observation?.avgTokensPerSecond
274
- };
275
- }
276
-
277
- function normalizeManualProviderObservation(value: Record<string, unknown>, now: string): ManualProviderObservation {
278
- return {
279
- providerId: readRequiredString(value.providerId, "manual provider observation providerId"),
280
- current: value.current === true,
281
- lastAccess: readOptionalString(value.lastAccess) ?? now,
282
- status: readObservationStatus(value.status),
283
- errorClass: readOptionalString(value.errorClass),
284
- errorMessage: readOptionalString(value.errorMessage),
285
- ttftMs: readOptionalNumber(value.ttftMs),
286
- avgTokensPerSecond: readOptionalNumber(value.avgTokensPerSecond)
287
- };
288
- }
289
-
290
- function readObservationStatus(value: unknown): ManualProviderObservation["status"] {
291
- if (value === "healthy" || value === "degraded" || value === "unhealthy" || value === "unknown") {
292
- return value;
293
- }
294
- return "unknown";
295
- }
296
-
297
- function readProviderMode(value: unknown): ProviderMode {
298
- if (value === "manual" || value === "auto") {
299
- return value;
300
- }
301
- throw new Error("provider mode must be manual or auto");
302
- }
303
-
304
- function readManualProviderRoutingPolicy(value: unknown): ManualProviderRoutingPolicy {
305
- if (value === undefined || value === null || value === "" || value === "fallback") {
306
- return "fallback";
307
- }
308
- if (value === "locked") {
309
- return "locked";
310
- }
311
- throw new Error("manual provider routing policy must be fallback or locked");
312
- }
313
-
314
- function readManualProviderKind(value: unknown): ManualProviderKind {
315
- if (value === undefined || value === null || value === "" || value === "openai-compatible") {
316
- return "openai-compatible";
317
- }
318
- throw new Error("manual provider kind must be openai-compatible");
319
- }
320
-
321
- function readAutoProviderRange(value: unknown): AutoProviderRange {
322
- if (value === undefined || value === null || value === "" || value === "recommended") {
323
- return "recommended";
324
- }
325
- if (value === "custom") {
326
- return "custom";
327
- }
328
- throw new Error("auto provider range must be recommended or custom");
329
- }
330
-
331
- function readScorer(value: unknown): SellerRoutingScorer {
332
- if (value === undefined || value === null || value === "") {
333
- return "balanced";
334
- }
335
- if (typeof value === "string" && VALID_SCORERS.has(value as SellerRoutingScorer)) {
336
- return value as SellerRoutingScorer;
337
- }
338
- throw new Error("auto provider scorer must be balanced, speed, or discount");
339
- }
340
-
341
- function readProtocols(value: unknown): ProviderProtocol[] {
342
- const protocols = readStringList(value);
343
- if (protocols.length === 0) {
344
- return ["chat_completions"];
345
- }
346
- const invalid = protocols.find((protocol) => !VALID_PROTOCOLS.has(protocol as ProviderProtocol));
347
- if (invalid) {
348
- throw new Error(`manual provider protocol is invalid: ${invalid}`);
349
- }
350
- return protocols as ProviderProtocol[];
351
- }
352
-
353
- function normalizeProviderBaseUrl(value: unknown): string {
354
- const raw = readRequiredString(value, "manual provider baseUrl");
355
- let parsed: URL;
356
- try {
357
- parsed = new URL(raw);
358
- } catch {
359
- throw new Error("manual provider baseUrl must be a valid URL");
360
- }
361
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
362
- throw new Error("manual provider baseUrl must use http or https");
363
- }
364
- return parsed.toString().replace(/\/+$/, "");
365
- }
366
-
367
- function readProviderId(value: unknown): string {
368
- const id = readRequiredString(value, "manual provider id");
369
- if (!/^[A-Za-z0-9][A-Za-z0-9_-]{1,63}$/.test(id)) {
370
- throw new Error("manual provider id must be 2-64 characters of letters, numbers, underscore, or dash");
371
- }
372
- return id;
373
- }
374
-
375
- function readRequiredString(value: unknown, label: string): string {
376
- if (typeof value !== "string" || value.trim().length === 0) {
377
- throw new Error(`${label} is required`);
378
- }
379
- return value.trim();
380
- }
381
-
382
- function readOptionalString(value: unknown): string | undefined {
383
- return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
384
- }
385
-
386
- function readOptionalNumber(value: unknown): number | undefined {
387
- return typeof value === "number" && Number.isFinite(value) ? value : undefined;
388
- }
389
-
390
- function readNonEmptyStringList(value: unknown, label: string): string[] {
391
- const entries = readStringList(value);
392
- if (entries.length === 0) {
393
- throw new Error(`${label} must contain at least one value`);
394
- }
395
- return entries;
396
- }
397
-
398
- function readStringList(value: unknown): string[] {
399
- if (!Array.isArray(value)) {
400
- return [];
401
- }
402
- return value
403
- .filter((entry): entry is string => typeof entry === "string")
404
- .map((entry) => entry.trim())
405
- .filter((entry, index, all) => entry.length > 0 && all.indexOf(entry) === index);
406
- }
407
-
408
- function isRecord(value: unknown): value is Record<string, unknown> {
409
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
410
- }
@@ -1,51 +0,0 @@
1
- import * as crypto from "crypto";
2
-
3
- export const DEFAULT_SELLER_REGISTRY_URL = "https://registry.tokenbuddy.ai/v1/registry.json";
4
- export const DEFAULT_SELLER_REGISTRY_SIGNATURE_URL = "https://registry.tokenbuddy.ai/v1/registry.sig";
5
-
6
- const TRUSTED_REGISTRY_KEYS: Record<string, string> = {
7
- "registry-ed25519-2026-06": "MCowBQYDK2VwAyEAcPWdwqqycIHmhSBWmt+HgFgQEMNFJv2uEEcpxPzwgb0="
8
- };
9
-
10
- export function signatureUrlForRegistryUrl(registryUrl: string): string {
11
- if (registryUrl === DEFAULT_SELLER_REGISTRY_URL) {
12
- return DEFAULT_SELLER_REGISTRY_SIGNATURE_URL;
13
- }
14
- return registryUrl.replace(/\.json(?:\?.*)?$/, ".sig");
15
- }
16
-
17
- export function shouldVerifyRegistry(registryUrl: string): boolean {
18
- if (process.env.TB_PROXYD_ALLOW_UNSIGNED_REGISTRY === "1") {
19
- return false;
20
- }
21
- return registryUrl === DEFAULT_SELLER_REGISTRY_URL || process.env.TB_PROXYD_REQUIRE_SIGNED_REGISTRY === "1";
22
- }
23
-
24
- export function verifyTrustedRegistrySignature(registryBytes: string, signatureText: string): string {
25
- return verifyRegistrySignatureWithKeys(registryBytes, signatureText, TRUSTED_REGISTRY_KEYS);
26
- }
27
-
28
- export function verifyRegistrySignatureWithKeys(
29
- registryBytes: string,
30
- signatureText: string,
31
- trustedKeys: Record<string, string>
32
- ): string {
33
- const signature = signatureText.trim();
34
- for (const [keyId, publicKeyDerBase64] of Object.entries(trustedKeys)) {
35
- const publicKey = crypto.createPublicKey({
36
- key: Buffer.from(publicKeyDerBase64, "base64"),
37
- format: "der",
38
- type: "spki"
39
- });
40
- const ok = crypto.verify(
41
- null,
42
- Buffer.from(registryBytes, "utf8"),
43
- publicKey,
44
- Buffer.from(signature, "base64url")
45
- );
46
- if (ok) {
47
- return keyId;
48
- }
49
- }
50
- throw new Error("registry signature verification failed");
51
- }