@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.
- package/dist/src/buyer-store.d.ts +7 -2
- package/dist/src/buyer-store.js +46 -7
- package/dist/src/cli.d.ts +1 -0
- package/dist/src/cli.js +15 -7
- package/dist/src/daemon.d.ts +12 -0
- package/dist/src/daemon.js +791 -61
- package/dist/src/doctor-diagnostics.js +1 -6
- package/dist/src/provider-install.d.ts +2 -2
- package/dist/src/provider-install.js +248 -2
- package/dist/src/seller-catalog.d.ts +21 -0
- package/dist/src/seller-catalog.js +17 -0
- package/dist/src/seller-route-planner.d.ts +4 -1
- package/dist/src/seller-route-planner.js +3 -0
- package/dist/src/seller-routing-strategy.d.ts +3 -0
- package/dist/src/terminal-detect.d.ts +1 -1
- package/dist/src/terminal-detect.js +3 -2
- package/dist/src/workdir.d.ts +10 -0
- package/dist/src/workdir.js +26 -0
- package/package.json +15 -2
- package/static/ui/assets/index-Djfl9tw5.js +271 -0
- package/static/ui/assets/index-DkfztCkn.css +1 -0
- package/static/ui/index.html +2 -2
- package/dist/src/buyer-store.d.ts.map +0 -1
- package/dist/src/buyer-store.js.map +0 -1
- package/dist/src/clawtip-bootstrap.d.ts.map +0 -1
- package/dist/src/clawtip-bootstrap.js.map +0 -1
- package/dist/src/cli.d.ts.map +0 -1
- package/dist/src/cli.js.map +0 -1
- package/dist/src/credit-tracker.d.ts.map +0 -1
- package/dist/src/credit-tracker.js.map +0 -1
- package/dist/src/daemon.d.ts.map +0 -1
- package/dist/src/daemon.js.map +0 -1
- package/dist/src/doctor-clawtip-wallet.d.ts.map +0 -1
- package/dist/src/doctor-clawtip-wallet.js.map +0 -1
- package/dist/src/doctor-diagnostics.d.ts.map +0 -1
- package/dist/src/doctor-diagnostics.js.map +0 -1
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js.map +0 -1
- package/dist/src/init-clawtip-activation.d.ts.map +0 -1
- package/dist/src/init-clawtip-activation.js.map +0 -1
- package/dist/src/init-payment-options.d.ts.map +0 -1
- package/dist/src/init-payment-options.js.map +0 -1
- package/dist/src/init-setup.d.ts.map +0 -1
- package/dist/src/init-setup.js.map +0 -1
- package/dist/src/model-index.d.ts.map +0 -1
- package/dist/src/model-index.js.map +0 -1
- package/dist/src/package-update.d.ts.map +0 -1
- package/dist/src/package-update.js.map +0 -1
- package/dist/src/prewarm-cache.d.ts.map +0 -1
- package/dist/src/prewarm-cache.js.map +0 -1
- package/dist/src/prewarm-scheduler.d.ts.map +0 -1
- package/dist/src/prewarm-scheduler.js.map +0 -1
- package/dist/src/provider-install.d.ts.map +0 -1
- package/dist/src/provider-install.js.map +0 -1
- package/dist/src/provider-routing-config.d.ts.map +0 -1
- package/dist/src/provider-routing-config.js.map +0 -1
- package/dist/src/registry-trust.d.ts.map +0 -1
- package/dist/src/registry-trust.js.map +0 -1
- package/dist/src/route-failover.d.ts.map +0 -1
- package/dist/src/route-failover.js.map +0 -1
- package/dist/src/seller-catalog.d.ts.map +0 -1
- package/dist/src/seller-catalog.js.map +0 -1
- package/dist/src/seller-concurrency-limiter.d.ts.map +0 -1
- package/dist/src/seller-concurrency-limiter.js.map +0 -1
- package/dist/src/seller-metadata-cache.d.ts.map +0 -1
- package/dist/src/seller-metadata-cache.js.map +0 -1
- package/dist/src/seller-pool.d.ts.map +0 -1
- package/dist/src/seller-pool.js.map +0 -1
- package/dist/src/seller-route-planner.d.ts.map +0 -1
- package/dist/src/seller-route-planner.js.map +0 -1
- package/dist/src/seller-routing-config.d.ts.map +0 -1
- package/dist/src/seller-routing-config.js.map +0 -1
- package/dist/src/seller-routing-strategy.d.ts.map +0 -1
- package/dist/src/seller-routing-strategy.js.map +0 -1
- package/dist/src/stream-failover.d.ts.map +0 -1
- package/dist/src/stream-failover.js.map +0 -1
- package/dist/src/tb-clawtip-proof.d.ts.map +0 -1
- package/dist/src/tb-clawtip-proof.js.map +0 -1
- package/dist/src/tb-proxyd.d.ts.map +0 -1
- package/dist/src/tb-proxyd.js.map +0 -1
- package/dist/src/terminal-detect.d.ts.map +0 -1
- package/dist/src/terminal-detect.js.map +0 -1
- package/dist/src/terminal-image.d.ts.map +0 -1
- package/dist/src/terminal-image.js.map +0 -1
- package/src/buyer-store.ts +0 -1090
- package/src/clawtip-bootstrap.ts +0 -65
- package/src/cli.ts +0 -2243
- package/src/credit-tracker.ts +0 -295
- package/src/daemon.ts +0 -5475
- package/src/doctor-clawtip-wallet.ts +0 -95
- package/src/doctor-diagnostics.ts +0 -1026
- package/src/index.ts +0 -16
- package/src/init-clawtip-activation.ts +0 -695
- package/src/init-payment-options.ts +0 -373
- package/src/init-setup.ts +0 -165
- package/src/model-index.ts +0 -278
- package/src/package-update.ts +0 -311
- package/src/prewarm-cache.ts +0 -485
- package/src/prewarm-scheduler.ts +0 -675
- package/src/provider-install.ts +0 -1006
- package/src/provider-routing-config.ts +0 -410
- package/src/registry-trust.ts +0 -51
- package/src/route-failover.ts +0 -304
- package/src/seller-catalog.ts +0 -505
- package/src/seller-concurrency-limiter.ts +0 -161
- package/src/seller-metadata-cache.ts +0 -91
- package/src/seller-pool.ts +0 -557
- package/src/seller-route-planner.ts +0 -513
- package/src/seller-routing-config.ts +0 -211
- package/src/seller-routing-strategy.ts +0 -362
- package/src/stream-failover.ts +0 -152
- package/src/tb-clawtip-proof.ts +0 -28
- package/src/tb-proxyd.ts +0 -101
- package/src/terminal-detect.ts +0 -333
- package/src/terminal-image.ts +0 -228
- package/static/ui/assets/index-0MVXD7bH.css +0 -1
- package/static/ui/assets/index-BVbeDEwq.js +0 -271
- package/static/ui/assets/index-BVbeDEwq.js.map +0 -1
- package/tests/cli-routing.test.ts +0 -363
- package/tests/control-plane-ui-endpoints.test.ts +0 -1630
- package/tests/credit-tracker.test.ts +0 -165
- package/tests/daemon-413-fallback.test.ts +0 -92
- package/tests/daemon-classify.test.ts +0 -452
- package/tests/daemon-roles.test.ts +0 -92
- package/tests/daemon-trusted-registry-cache.test.ts +0 -132
- package/tests/e2e.test.ts +0 -366
- package/tests/image-generation-e2e.test.ts +0 -230
- package/tests/model-index.test.ts +0 -198
- package/tests/package-update.test.ts +0 -147
- package/tests/prewarm-cache.test.ts +0 -296
- package/tests/prewarm-scheduler.test.ts +0 -367
- package/tests/provider-routing-config.test.ts +0 -150
- package/tests/registry-trust.test.ts +0 -28
- package/tests/route-failover.test.ts +0 -222
- package/tests/seller-catalog-413.test.ts +0 -120
- package/tests/seller-catalog-utilities.test.ts +0 -124
- package/tests/seller-concurrency-limiter.test.ts +0 -83
- package/tests/seller-metadata-cache.test.ts +0 -89
- package/tests/seller-pool.test.ts +0 -365
- package/tests/seller-route-planner.test.ts +0 -312
- package/tests/seller-routing-config.test.ts +0 -124
- package/tests/seller-routing-strategy.test.ts +0 -167
- package/tests/stream-failover.test.ts +0 -52
- package/tests/thousand-seller.test.ts +0 -151
- package/tests/tokenbuddy.test.ts +0 -4043
- 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
|
-
}
|
package/src/registry-trust.ts
DELETED
|
@@ -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
|
-
}
|