@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,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
- }