aistatus 0.0.2
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/LICENSE +21 -0
- package/README.md +341 -0
- package/dist/index.cjs +1101 -0
- package/dist/index.d.cts +202 -0
- package/dist/index.d.ts +202 -0
- package/dist/index.js +1057 -0
- package/package.json +62 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1057 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var AIStatusError = class extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = new.target.name;
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
var AllProvidersDown = class extends AIStatusError {
|
|
9
|
+
tried;
|
|
10
|
+
constructor(tried) {
|
|
11
|
+
super(
|
|
12
|
+
`All providers unavailable. Tried: ${tried.join(", ")}. Check https://aistatus.cc for current status.`
|
|
13
|
+
);
|
|
14
|
+
this.tried = tried;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var ProviderCallFailed = class extends AIStatusError {
|
|
18
|
+
provider;
|
|
19
|
+
model;
|
|
20
|
+
cause;
|
|
21
|
+
constructor(provider, model, cause) {
|
|
22
|
+
super(`${provider} (${model}) call failed: ${String(cause)}`);
|
|
23
|
+
this.provider = provider;
|
|
24
|
+
this.model = model;
|
|
25
|
+
this.cause = cause;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var NoBudgetMatch = class extends AIStatusError {
|
|
29
|
+
maxCost;
|
|
30
|
+
tier;
|
|
31
|
+
constructor(maxCost, tier) {
|
|
32
|
+
super(`No operational model in tier '${tier}' under $${maxCost}/M tokens.`);
|
|
33
|
+
this.maxCost = maxCost;
|
|
34
|
+
this.tier = tier;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var ProviderNotConfigured = class extends AIStatusError {
|
|
38
|
+
provider;
|
|
39
|
+
envName;
|
|
40
|
+
constructor(provider, envName) {
|
|
41
|
+
super(
|
|
42
|
+
envName ? `Provider '${provider}' is not configured. Set ${envName} or pass apiKey explicitly.` : `Provider '${provider}' is not configured. Pass apiKey explicitly.`
|
|
43
|
+
);
|
|
44
|
+
this.provider = provider;
|
|
45
|
+
this.envName = envName;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var ProviderNotInstalled = class extends AIStatusError {
|
|
49
|
+
provider;
|
|
50
|
+
packageName;
|
|
51
|
+
constructor(provider, packageName) {
|
|
52
|
+
super(
|
|
53
|
+
packageName ? `Provider '${provider}' requires package '${packageName}'.` : `Provider '${provider}' is not available in this runtime.`
|
|
54
|
+
);
|
|
55
|
+
this.provider = provider;
|
|
56
|
+
this.packageName = packageName;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var CheckAPIUnreachable = class extends AIStatusError {
|
|
60
|
+
constructor() {
|
|
61
|
+
super(
|
|
62
|
+
"Could not reach aistatus.cc API. Proceeding with provider inference only."
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// src/http.ts
|
|
68
|
+
var HTTPStatusError = class extends Error {
|
|
69
|
+
status;
|
|
70
|
+
body;
|
|
71
|
+
constructor(status, body) {
|
|
72
|
+
super(`HTTP ${status}`);
|
|
73
|
+
this.name = "HTTPStatusError";
|
|
74
|
+
this.status = status;
|
|
75
|
+
this.body = body;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
function readEnv(name) {
|
|
79
|
+
if (!name) {
|
|
80
|
+
return void 0;
|
|
81
|
+
}
|
|
82
|
+
if (typeof process === "undefined" || !process.env) {
|
|
83
|
+
return void 0;
|
|
84
|
+
}
|
|
85
|
+
return process.env[name];
|
|
86
|
+
}
|
|
87
|
+
function requireApiKey(provider, apiKey, envName) {
|
|
88
|
+
if (!apiKey) {
|
|
89
|
+
throw new ProviderNotConfigured(provider, envName);
|
|
90
|
+
}
|
|
91
|
+
return apiKey;
|
|
92
|
+
}
|
|
93
|
+
function joinUrl(baseUrl, path) {
|
|
94
|
+
return `${baseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
95
|
+
}
|
|
96
|
+
function extractText(value) {
|
|
97
|
+
if (typeof value === "string") {
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
if (Array.isArray(value)) {
|
|
101
|
+
return value.map((item) => extractText(item)).filter(Boolean).join("\n");
|
|
102
|
+
}
|
|
103
|
+
if (isRecord(value)) {
|
|
104
|
+
if (typeof value.text === "string") {
|
|
105
|
+
return value.text;
|
|
106
|
+
}
|
|
107
|
+
if ("content" in value) {
|
|
108
|
+
return extractText(value.content);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return "";
|
|
112
|
+
}
|
|
113
|
+
function isRecord(value) {
|
|
114
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
115
|
+
}
|
|
116
|
+
async function fetchJson(url, init = {}) {
|
|
117
|
+
const controller = new AbortController();
|
|
118
|
+
const timeoutMs = init.timeoutMs ?? 3e3;
|
|
119
|
+
const timeoutId = setTimeout(() => {
|
|
120
|
+
controller.abort(new Error(`Request timed out after ${timeoutMs}ms`));
|
|
121
|
+
}, timeoutMs);
|
|
122
|
+
const externalSignal = init.signal;
|
|
123
|
+
const abortFromExternal = () => {
|
|
124
|
+
controller.abort(externalSignal?.reason);
|
|
125
|
+
};
|
|
126
|
+
if (externalSignal) {
|
|
127
|
+
if (externalSignal.aborted) {
|
|
128
|
+
controller.abort(externalSignal.reason);
|
|
129
|
+
} else {
|
|
130
|
+
externalSignal.addEventListener("abort", abortFromExternal, {
|
|
131
|
+
once: true
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const headers = new Headers(init.headers ?? {});
|
|
137
|
+
let body = init.body;
|
|
138
|
+
if (body !== void 0 && body !== null && typeof body !== "string" && !(body instanceof URLSearchParams) && !(body instanceof FormData) && !(body instanceof Blob) && !(body instanceof ArrayBuffer)) {
|
|
139
|
+
body = JSON.stringify(body);
|
|
140
|
+
}
|
|
141
|
+
if (typeof body === "string" && !headers.has("content-type")) {
|
|
142
|
+
headers.set("content-type", "application/json");
|
|
143
|
+
}
|
|
144
|
+
const response = await fetch(url, {
|
|
145
|
+
...init,
|
|
146
|
+
headers,
|
|
147
|
+
body,
|
|
148
|
+
signal: controller.signal
|
|
149
|
+
});
|
|
150
|
+
const text = await response.text();
|
|
151
|
+
const payload = text ? tryParseJson(text) : null;
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
throw new HTTPStatusError(response.status, payload);
|
|
154
|
+
}
|
|
155
|
+
return payload;
|
|
156
|
+
} finally {
|
|
157
|
+
clearTimeout(timeoutId);
|
|
158
|
+
externalSignal?.removeEventListener("abort", abortFromExternal);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function tryParseJson(text) {
|
|
162
|
+
try {
|
|
163
|
+
return JSON.parse(text);
|
|
164
|
+
} catch {
|
|
165
|
+
return text;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/models.ts
|
|
170
|
+
var Status = /* @__PURE__ */ ((Status2) => {
|
|
171
|
+
Status2["OPERATIONAL"] = "operational";
|
|
172
|
+
Status2["DEGRADED"] = "degraded";
|
|
173
|
+
Status2["DOWN"] = "down";
|
|
174
|
+
Status2["UNKNOWN"] = "unknown";
|
|
175
|
+
return Status2;
|
|
176
|
+
})(Status || {});
|
|
177
|
+
var CheckResult = class {
|
|
178
|
+
provider;
|
|
179
|
+
status;
|
|
180
|
+
statusDetail;
|
|
181
|
+
model;
|
|
182
|
+
alternatives;
|
|
183
|
+
constructor(init) {
|
|
184
|
+
this.provider = init.provider;
|
|
185
|
+
this.status = init.status;
|
|
186
|
+
this.statusDetail = init.statusDetail ?? null;
|
|
187
|
+
this.model = init.model ?? null;
|
|
188
|
+
this.alternatives = init.alternatives ?? [];
|
|
189
|
+
}
|
|
190
|
+
get isAvailable() {
|
|
191
|
+
return this.status === "operational" /* OPERATIONAL */;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
var RouteResponse = class {
|
|
195
|
+
content;
|
|
196
|
+
modelUsed;
|
|
197
|
+
providerUsed;
|
|
198
|
+
wasFallback;
|
|
199
|
+
fallbackReason;
|
|
200
|
+
inputTokens;
|
|
201
|
+
outputTokens;
|
|
202
|
+
costUsd;
|
|
203
|
+
raw;
|
|
204
|
+
constructor(init) {
|
|
205
|
+
this.content = init.content;
|
|
206
|
+
this.modelUsed = init.modelUsed;
|
|
207
|
+
this.providerUsed = init.providerUsed;
|
|
208
|
+
this.wasFallback = init.wasFallback;
|
|
209
|
+
this.fallbackReason = init.fallbackReason ?? null;
|
|
210
|
+
this.inputTokens = init.inputTokens ?? 0;
|
|
211
|
+
this.outputTokens = init.outputTokens ?? 0;
|
|
212
|
+
this.costUsd = init.costUsd ?? 0;
|
|
213
|
+
this.raw = init.raw ?? null;
|
|
214
|
+
}
|
|
215
|
+
toString() {
|
|
216
|
+
return this.content;
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// src/defaults.ts
|
|
221
|
+
var AUTO_PROVIDERS = {
|
|
222
|
+
anthropic: {
|
|
223
|
+
envVar: "ANTHROPIC_API_KEY",
|
|
224
|
+
adapterType: "anthropic"
|
|
225
|
+
},
|
|
226
|
+
openai: {
|
|
227
|
+
envVar: "OPENAI_API_KEY",
|
|
228
|
+
adapterType: "openai"
|
|
229
|
+
},
|
|
230
|
+
google: {
|
|
231
|
+
envVar: "GEMINI_API_KEY",
|
|
232
|
+
adapterType: "google"
|
|
233
|
+
},
|
|
234
|
+
openrouter: {
|
|
235
|
+
envVar: "OPENROUTER_API_KEY",
|
|
236
|
+
adapterType: "openrouter"
|
|
237
|
+
},
|
|
238
|
+
deepseek: {
|
|
239
|
+
envVar: "DEEPSEEK_API_KEY",
|
|
240
|
+
adapterType: "deepseek"
|
|
241
|
+
},
|
|
242
|
+
mistral: {
|
|
243
|
+
envVar: "MISTRAL_API_KEY",
|
|
244
|
+
adapterType: "mistral",
|
|
245
|
+
aliases: ["mistralai"]
|
|
246
|
+
},
|
|
247
|
+
xai: {
|
|
248
|
+
envVar: "XAI_API_KEY",
|
|
249
|
+
adapterType: "xai",
|
|
250
|
+
aliases: ["x-ai"]
|
|
251
|
+
},
|
|
252
|
+
groq: {
|
|
253
|
+
envVar: "GROQ_API_KEY",
|
|
254
|
+
adapterType: "groq"
|
|
255
|
+
},
|
|
256
|
+
together: {
|
|
257
|
+
envVar: "TOGETHER_API_KEY",
|
|
258
|
+
adapterType: "together"
|
|
259
|
+
},
|
|
260
|
+
moonshot: {
|
|
261
|
+
envVar: "MOONSHOT_API_KEY",
|
|
262
|
+
adapterType: "moonshot",
|
|
263
|
+
aliases: ["moonshotai"]
|
|
264
|
+
},
|
|
265
|
+
qwen: {
|
|
266
|
+
envVar: "DASHSCOPE_API_KEY",
|
|
267
|
+
adapterType: "qwen"
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
var PROVIDER_ALIASES = {
|
|
271
|
+
anthropic: "anthropic",
|
|
272
|
+
openai: "openai",
|
|
273
|
+
google: "google",
|
|
274
|
+
openrouter: "openrouter",
|
|
275
|
+
deepseek: "deepseek",
|
|
276
|
+
mistral: "mistral",
|
|
277
|
+
mistralai: "mistral",
|
|
278
|
+
xai: "xai",
|
|
279
|
+
"x-ai": "xai",
|
|
280
|
+
groq: "groq",
|
|
281
|
+
together: "together",
|
|
282
|
+
moonshot: "moonshot",
|
|
283
|
+
moonshotai: "moonshot",
|
|
284
|
+
qwen: "qwen"
|
|
285
|
+
};
|
|
286
|
+
var MODEL_PREFIX_MAP = {
|
|
287
|
+
claude: "anthropic",
|
|
288
|
+
gpt: "openai",
|
|
289
|
+
o1: "openai",
|
|
290
|
+
o3: "openai",
|
|
291
|
+
o4: "openai",
|
|
292
|
+
chatgpt: "openai",
|
|
293
|
+
gemini: "google",
|
|
294
|
+
deepseek: "deepseek",
|
|
295
|
+
mistral: "mistral",
|
|
296
|
+
codestral: "mistral",
|
|
297
|
+
pixtral: "mistral",
|
|
298
|
+
grok: "xai",
|
|
299
|
+
llama: "groq",
|
|
300
|
+
qwen: "qwen",
|
|
301
|
+
moonshot: "moonshot"
|
|
302
|
+
};
|
|
303
|
+
function normalizeProviderSlug(slug) {
|
|
304
|
+
const value = (slug ?? "").trim().toLowerCase();
|
|
305
|
+
return PROVIDER_ALIASES[value] ?? value;
|
|
306
|
+
}
|
|
307
|
+
function extractProviderSlug(modelId) {
|
|
308
|
+
const value = (modelId ?? "").trim();
|
|
309
|
+
if (!value.includes("/")) {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
return normalizeProviderSlug(value.split("/", 1)[0]);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/providers/base.ts
|
|
316
|
+
var ProviderAdapter = class {
|
|
317
|
+
config;
|
|
318
|
+
constructor(config) {
|
|
319
|
+
this.config = {
|
|
320
|
+
...config,
|
|
321
|
+
slug: normalizeProviderSlug(config.slug),
|
|
322
|
+
aliases: (config.aliases ?? []).map((alias) => normalizeProviderSlug(alias))
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
get slug() {
|
|
326
|
+
return this.config.slug;
|
|
327
|
+
}
|
|
328
|
+
get aliases() {
|
|
329
|
+
return this.config.aliases ?? [];
|
|
330
|
+
}
|
|
331
|
+
supportsProvider(slug) {
|
|
332
|
+
const normalized = normalizeProviderSlug(slug);
|
|
333
|
+
return normalized === this.slug || this.aliases.includes(normalized);
|
|
334
|
+
}
|
|
335
|
+
stripProvider(modelId) {
|
|
336
|
+
if (!modelId.includes("/")) {
|
|
337
|
+
return modelId;
|
|
338
|
+
}
|
|
339
|
+
return modelId.slice(modelId.indexOf("/") + 1);
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
var ADAPTER_TYPES = /* @__PURE__ */ new Map();
|
|
343
|
+
function registerAdapterType(typeName, ctor) {
|
|
344
|
+
ADAPTER_TYPES.set(typeName.toLowerCase(), ctor);
|
|
345
|
+
}
|
|
346
|
+
function createAdapter(config) {
|
|
347
|
+
const ctor = ADAPTER_TYPES.get(config.adapterType.toLowerCase());
|
|
348
|
+
if (!ctor) {
|
|
349
|
+
throw new Error(`Unknown adapter type: ${config.adapterType}`);
|
|
350
|
+
}
|
|
351
|
+
return new ctor(config);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// src/providers/openai.ts
|
|
355
|
+
var OpenAIAdapter = class extends ProviderAdapter {
|
|
356
|
+
defaultBaseUrl = "https://api.openai.com/v1";
|
|
357
|
+
defaultEnvVar = "OPENAI_API_KEY";
|
|
358
|
+
getBaseUrl() {
|
|
359
|
+
return this.config.baseUrl ?? this.defaultBaseUrl;
|
|
360
|
+
}
|
|
361
|
+
getApiKey() {
|
|
362
|
+
return requireApiKey(
|
|
363
|
+
this.slug,
|
|
364
|
+
this.config.apiKey ?? readEnv(this.config.env ?? this.defaultEnvVar),
|
|
365
|
+
this.config.env ?? this.defaultEnvVar
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
getDefaultHeaders() {
|
|
369
|
+
return {};
|
|
370
|
+
}
|
|
371
|
+
buildPayload(modelId, messages, options) {
|
|
372
|
+
const payload = {
|
|
373
|
+
...options.providerOptions ?? {},
|
|
374
|
+
model: this.stripProvider(modelId),
|
|
375
|
+
messages
|
|
376
|
+
};
|
|
377
|
+
if (options.maxTokens !== void 0 && payload.max_tokens === void 0 && payload.max_completion_tokens === void 0) {
|
|
378
|
+
payload.max_tokens = options.maxTokens;
|
|
379
|
+
}
|
|
380
|
+
if (options.temperature !== void 0 && payload.temperature === void 0) {
|
|
381
|
+
payload.temperature = options.temperature;
|
|
382
|
+
}
|
|
383
|
+
if (options.topP !== void 0 && payload.top_p === void 0) {
|
|
384
|
+
payload.top_p = options.topP;
|
|
385
|
+
}
|
|
386
|
+
return payload;
|
|
387
|
+
}
|
|
388
|
+
async call(modelId, messages, timeoutSeconds, options) {
|
|
389
|
+
const response = await fetchJson(
|
|
390
|
+
joinUrl(this.getBaseUrl(), "chat/completions"),
|
|
391
|
+
{
|
|
392
|
+
method: "POST",
|
|
393
|
+
headers: {
|
|
394
|
+
authorization: `Bearer ${this.getApiKey()}`,
|
|
395
|
+
...this.getDefaultHeaders(),
|
|
396
|
+
...this.config.headers ?? {},
|
|
397
|
+
...options.headers ?? {}
|
|
398
|
+
},
|
|
399
|
+
body: this.buildPayload(modelId, messages, options),
|
|
400
|
+
timeoutMs: timeoutSeconds * 1e3,
|
|
401
|
+
signal: options.signal
|
|
402
|
+
}
|
|
403
|
+
);
|
|
404
|
+
return this.toResponse(response, modelId);
|
|
405
|
+
}
|
|
406
|
+
toResponse(response, modelId) {
|
|
407
|
+
return new RouteResponse({
|
|
408
|
+
content: extractText(response.choices?.[0]?.message?.content),
|
|
409
|
+
modelUsed: modelId,
|
|
410
|
+
providerUsed: this.slug,
|
|
411
|
+
wasFallback: false,
|
|
412
|
+
inputTokens: response.usage?.prompt_tokens ?? 0,
|
|
413
|
+
outputTokens: response.usage?.completion_tokens ?? 0,
|
|
414
|
+
raw: response
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
registerAdapterType("openai", OpenAIAdapter);
|
|
419
|
+
|
|
420
|
+
// src/providers/openrouter.ts
|
|
421
|
+
var OpenRouterAdapter = class extends OpenAIAdapter {
|
|
422
|
+
defaultBaseUrl = "https://openrouter.ai/api/v1";
|
|
423
|
+
defaultEnvVar = "OPENROUTER_API_KEY";
|
|
424
|
+
getDefaultHeaders() {
|
|
425
|
+
return {
|
|
426
|
+
"HTTP-Referer": "https://aistatus.cc",
|
|
427
|
+
"X-Title": "aistatus"
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
registerAdapterType("openrouter", OpenRouterAdapter);
|
|
432
|
+
|
|
433
|
+
// src/providers/anthropic.ts
|
|
434
|
+
var AnthropicAdapter = class extends ProviderAdapter {
|
|
435
|
+
defaultBaseUrl = "https://api.anthropic.com/v1";
|
|
436
|
+
defaultEnvVar = "ANTHROPIC_API_KEY";
|
|
437
|
+
async call(modelId, messages, timeoutSeconds, options) {
|
|
438
|
+
const systemParts = [];
|
|
439
|
+
const anthropicMessages = messages.filter((message) => {
|
|
440
|
+
if (message.role === "system") {
|
|
441
|
+
systemParts.push(message.content);
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
return true;
|
|
445
|
+
}).map((message) => ({
|
|
446
|
+
role: message.role === "assistant" ? "assistant" : "user",
|
|
447
|
+
content: message.content
|
|
448
|
+
}));
|
|
449
|
+
const payload = {
|
|
450
|
+
...options.providerOptions ?? {},
|
|
451
|
+
model: this.stripProvider(modelId),
|
|
452
|
+
messages: anthropicMessages
|
|
453
|
+
};
|
|
454
|
+
if (systemParts.length > 0 && payload.system === void 0) {
|
|
455
|
+
payload.system = systemParts.join("\n\n");
|
|
456
|
+
}
|
|
457
|
+
if (payload.max_tokens === void 0) {
|
|
458
|
+
payload.max_tokens = options.maxTokens ?? 4096;
|
|
459
|
+
}
|
|
460
|
+
if (options.temperature !== void 0 && payload.temperature === void 0) {
|
|
461
|
+
payload.temperature = options.temperature;
|
|
462
|
+
}
|
|
463
|
+
if (options.topP !== void 0 && payload.top_p === void 0) {
|
|
464
|
+
payload.top_p = options.topP;
|
|
465
|
+
}
|
|
466
|
+
const response = await fetchJson(
|
|
467
|
+
joinUrl(this.config.baseUrl ?? this.defaultBaseUrl, "messages"),
|
|
468
|
+
{
|
|
469
|
+
method: "POST",
|
|
470
|
+
headers: {
|
|
471
|
+
"x-api-key": requireApiKey(
|
|
472
|
+
this.slug,
|
|
473
|
+
this.config.apiKey ?? readEnv(this.config.env ?? this.defaultEnvVar),
|
|
474
|
+
this.config.env ?? this.defaultEnvVar
|
|
475
|
+
),
|
|
476
|
+
"anthropic-version": "2023-06-01",
|
|
477
|
+
...this.config.headers ?? {},
|
|
478
|
+
...options.headers ?? {}
|
|
479
|
+
},
|
|
480
|
+
body: payload,
|
|
481
|
+
timeoutMs: timeoutSeconds * 1e3,
|
|
482
|
+
signal: options.signal
|
|
483
|
+
}
|
|
484
|
+
);
|
|
485
|
+
return new RouteResponse({
|
|
486
|
+
content: (response.content ?? []).filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n"),
|
|
487
|
+
modelUsed: modelId,
|
|
488
|
+
providerUsed: this.slug,
|
|
489
|
+
wasFallback: false,
|
|
490
|
+
inputTokens: response.usage?.input_tokens ?? 0,
|
|
491
|
+
outputTokens: response.usage?.output_tokens ?? 0,
|
|
492
|
+
raw: response
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
registerAdapterType("anthropic", AnthropicAdapter);
|
|
497
|
+
|
|
498
|
+
// src/providers/google.ts
|
|
499
|
+
var GoogleAdapter = class extends ProviderAdapter {
|
|
500
|
+
defaultBaseUrl = "https://generativelanguage.googleapis.com/v1beta";
|
|
501
|
+
defaultEnvVar = "GEMINI_API_KEY";
|
|
502
|
+
async call(modelId, messages, timeoutSeconds, options) {
|
|
503
|
+
const providerOptions = { ...options.providerOptions ?? {} };
|
|
504
|
+
const generationConfig = isRecord(providerOptions.generationConfig) ? { ...providerOptions.generationConfig } : {};
|
|
505
|
+
const systemParts = [];
|
|
506
|
+
const contents = messages.filter((message) => {
|
|
507
|
+
if (message.role === "system") {
|
|
508
|
+
systemParts.push(message.content);
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
return true;
|
|
512
|
+
}).map((message) => ({
|
|
513
|
+
role: message.role === "assistant" ? "model" : "user",
|
|
514
|
+
parts: [{ text: message.content }]
|
|
515
|
+
}));
|
|
516
|
+
if (options.maxTokens !== void 0 && generationConfig.maxOutputTokens === void 0) {
|
|
517
|
+
generationConfig.maxOutputTokens = options.maxTokens;
|
|
518
|
+
}
|
|
519
|
+
if (options.temperature !== void 0 && generationConfig.temperature === void 0) {
|
|
520
|
+
generationConfig.temperature = options.temperature;
|
|
521
|
+
}
|
|
522
|
+
if (options.topP !== void 0 && generationConfig.topP === void 0) {
|
|
523
|
+
generationConfig.topP = options.topP;
|
|
524
|
+
}
|
|
525
|
+
const body = {
|
|
526
|
+
...providerOptions,
|
|
527
|
+
contents
|
|
528
|
+
};
|
|
529
|
+
if (Object.keys(generationConfig).length > 0) {
|
|
530
|
+
body.generationConfig = generationConfig;
|
|
531
|
+
}
|
|
532
|
+
if (systemParts.length > 0 && body.systemInstruction === void 0) {
|
|
533
|
+
body.systemInstruction = {
|
|
534
|
+
parts: [{ text: systemParts.join("\n\n") }]
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
const modelName = normalizeGoogleModelName(this.stripProvider(modelId));
|
|
538
|
+
const apiKey = requireApiKey(
|
|
539
|
+
this.slug,
|
|
540
|
+
this.config.apiKey ?? readEnv(this.config.env ?? this.defaultEnvVar),
|
|
541
|
+
this.config.env ?? this.defaultEnvVar
|
|
542
|
+
);
|
|
543
|
+
const url = new URL(
|
|
544
|
+
`${this.config.baseUrl ?? this.defaultBaseUrl}/models/${modelName}:generateContent`
|
|
545
|
+
);
|
|
546
|
+
url.searchParams.set("key", apiKey);
|
|
547
|
+
const response = await fetchJson(url.toString(), {
|
|
548
|
+
method: "POST",
|
|
549
|
+
headers: {
|
|
550
|
+
...this.config.headers ?? {},
|
|
551
|
+
...options.headers ?? {}
|
|
552
|
+
},
|
|
553
|
+
body,
|
|
554
|
+
timeoutMs: timeoutSeconds * 1e3,
|
|
555
|
+
signal: options.signal
|
|
556
|
+
});
|
|
557
|
+
return new RouteResponse({
|
|
558
|
+
content: (response.candidates?.[0]?.content?.parts ?? []).map((part) => part.text ?? "").filter(Boolean).join("\n"),
|
|
559
|
+
modelUsed: modelId,
|
|
560
|
+
providerUsed: this.slug,
|
|
561
|
+
wasFallback: false,
|
|
562
|
+
inputTokens: response.usageMetadata?.promptTokenCount ?? 0,
|
|
563
|
+
outputTokens: response.usageMetadata?.candidatesTokenCount ?? 0,
|
|
564
|
+
raw: response
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
function normalizeGoogleModelName(modelName) {
|
|
569
|
+
return modelName.startsWith("models/") ? modelName.slice(7) : modelName;
|
|
570
|
+
}
|
|
571
|
+
registerAdapterType("google", GoogleAdapter);
|
|
572
|
+
|
|
573
|
+
// src/providers/compatible.ts
|
|
574
|
+
var DeepSeekAdapter = class extends OpenAIAdapter {
|
|
575
|
+
defaultBaseUrl = "https://api.deepseek.com";
|
|
576
|
+
defaultEnvVar = "DEEPSEEK_API_KEY";
|
|
577
|
+
};
|
|
578
|
+
var MistralAdapter = class extends OpenAIAdapter {
|
|
579
|
+
defaultBaseUrl = "https://api.mistral.ai/v1";
|
|
580
|
+
defaultEnvVar = "MISTRAL_API_KEY";
|
|
581
|
+
};
|
|
582
|
+
var XAIAdapter = class extends OpenAIAdapter {
|
|
583
|
+
defaultBaseUrl = "https://api.x.ai/v1";
|
|
584
|
+
defaultEnvVar = "XAI_API_KEY";
|
|
585
|
+
};
|
|
586
|
+
var GroqAdapter = class extends OpenAIAdapter {
|
|
587
|
+
defaultBaseUrl = "https://api.groq.com/openai/v1";
|
|
588
|
+
defaultEnvVar = "GROQ_API_KEY";
|
|
589
|
+
};
|
|
590
|
+
var TogetherAdapter = class extends OpenAIAdapter {
|
|
591
|
+
defaultBaseUrl = "https://api.together.xyz/v1";
|
|
592
|
+
defaultEnvVar = "TOGETHER_API_KEY";
|
|
593
|
+
};
|
|
594
|
+
var MoonshotAdapter = class extends OpenAIAdapter {
|
|
595
|
+
defaultBaseUrl = "https://api.moonshot.cn/v1";
|
|
596
|
+
defaultEnvVar = "MOONSHOT_API_KEY";
|
|
597
|
+
};
|
|
598
|
+
var QwenAdapter = class extends OpenAIAdapter {
|
|
599
|
+
defaultBaseUrl = "https://dashscope.aliyuncs.com/compatible-mode/v1";
|
|
600
|
+
defaultEnvVar = "DASHSCOPE_API_KEY";
|
|
601
|
+
};
|
|
602
|
+
registerAdapterType("deepseek", DeepSeekAdapter);
|
|
603
|
+
registerAdapterType("mistral", MistralAdapter);
|
|
604
|
+
registerAdapterType("xai", XAIAdapter);
|
|
605
|
+
registerAdapterType("groq", GroqAdapter);
|
|
606
|
+
registerAdapterType("together", TogetherAdapter);
|
|
607
|
+
registerAdapterType("moonshot", MoonshotAdapter);
|
|
608
|
+
registerAdapterType("qwen", QwenAdapter);
|
|
609
|
+
|
|
610
|
+
// src/api.ts
|
|
611
|
+
var BASE_URL = "https://aistatus.cc";
|
|
612
|
+
var StatusAPI = class {
|
|
613
|
+
baseUrl;
|
|
614
|
+
timeoutMs;
|
|
615
|
+
constructor(baseUrl = BASE_URL, timeoutSeconds = 3) {
|
|
616
|
+
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
617
|
+
this.timeoutMs = timeoutSeconds * 1e3;
|
|
618
|
+
}
|
|
619
|
+
async checkProvider(slug) {
|
|
620
|
+
const data = await this.get("/api/check", {
|
|
621
|
+
provider: slug
|
|
622
|
+
});
|
|
623
|
+
return this.parseCheck(data);
|
|
624
|
+
}
|
|
625
|
+
async checkModel(modelId) {
|
|
626
|
+
const data = await this.get("/api/check", {
|
|
627
|
+
model: modelId
|
|
628
|
+
});
|
|
629
|
+
return this.parseCheck(data);
|
|
630
|
+
}
|
|
631
|
+
async acheckProvider(slug) {
|
|
632
|
+
return this.checkProvider(slug);
|
|
633
|
+
}
|
|
634
|
+
async acheckModel(modelId) {
|
|
635
|
+
return this.checkModel(modelId);
|
|
636
|
+
}
|
|
637
|
+
async providers() {
|
|
638
|
+
const data = await this.get("/api/providers");
|
|
639
|
+
const providers = Array.isArray(data.providers) ? data.providers : [];
|
|
640
|
+
return providers.map((provider) => this.parseProviderStatus(provider)).filter((provider) => provider !== null);
|
|
641
|
+
}
|
|
642
|
+
async model(modelId) {
|
|
643
|
+
try {
|
|
644
|
+
const data = await this.get(`/api/models/${encodeURI(modelId)}`);
|
|
645
|
+
return this.parseModel(data);
|
|
646
|
+
} catch (error) {
|
|
647
|
+
if (error instanceof HTTPStatusError && error.status === 404) {
|
|
648
|
+
return null;
|
|
649
|
+
}
|
|
650
|
+
throw error;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
async searchModels(query) {
|
|
654
|
+
const data = await this.get("/api/models", {
|
|
655
|
+
q: query
|
|
656
|
+
});
|
|
657
|
+
const models = Array.isArray(data.models) ? data.models : [];
|
|
658
|
+
return models.map((model) => this.parseModel(model)).filter((model) => model !== null);
|
|
659
|
+
}
|
|
660
|
+
async get(path, params) {
|
|
661
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
662
|
+
if (params) {
|
|
663
|
+
for (const [key, value] of Object.entries(params)) {
|
|
664
|
+
if (value !== void 0) {
|
|
665
|
+
url.searchParams.set(key, value);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return fetchJson(url.toString(), {
|
|
670
|
+
timeoutMs: this.timeoutMs
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
parseCheck(data) {
|
|
674
|
+
const model = asString(data.model) ?? null;
|
|
675
|
+
const provider = normalizeProviderSlug(
|
|
676
|
+
asString(data.provider) ?? asString(data.slug) ?? extractProviderSlug(model) ?? ""
|
|
677
|
+
) || "";
|
|
678
|
+
const status = parseStatus(
|
|
679
|
+
asString(data.status) ?? asString(data.providerStatus) ?? availableToStatus(data.available)
|
|
680
|
+
);
|
|
681
|
+
const alternatives = Array.isArray(data.alternatives) ? data.alternatives.map((alternative) => parseAlternative(alternative)).filter((alternative) => alternative !== null) : [];
|
|
682
|
+
return new CheckResult({
|
|
683
|
+
provider,
|
|
684
|
+
status,
|
|
685
|
+
statusDetail: asString(data.statusDetail) ?? asString(data.providerStatusDetail) ?? null,
|
|
686
|
+
model,
|
|
687
|
+
alternatives
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
parseProviderStatus(value) {
|
|
691
|
+
if (!isRecord(value)) {
|
|
692
|
+
return null;
|
|
693
|
+
}
|
|
694
|
+
return {
|
|
695
|
+
slug: normalizeProviderSlug(asString(value.slug) ?? ""),
|
|
696
|
+
name: asString(value.name) ?? asString(value.slug) ?? "",
|
|
697
|
+
status: parseStatus(asString(value.status)),
|
|
698
|
+
statusDetail: asString(value.statusDetail) ?? null,
|
|
699
|
+
modelCount: asNumber(value.modelCount)
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
parseModel(value) {
|
|
703
|
+
if (!isRecord(value)) {
|
|
704
|
+
return null;
|
|
705
|
+
}
|
|
706
|
+
const pricing = isRecord(value.pricing) ? value.pricing : {};
|
|
707
|
+
const provider = isRecord(value.provider) ? value.provider : {};
|
|
708
|
+
return {
|
|
709
|
+
id: asString(value.id) ?? "",
|
|
710
|
+
name: asString(value.name) ?? "",
|
|
711
|
+
providerSlug: normalizeProviderSlug(
|
|
712
|
+
asString(provider.slug) ?? extractProviderSlug(asString(value.id) ?? "") ?? ""
|
|
713
|
+
),
|
|
714
|
+
contextLength: asNumber(value.context_length),
|
|
715
|
+
modality: asString(value.modality) ?? "text->text",
|
|
716
|
+
promptPrice: asFloat(pricing.prompt),
|
|
717
|
+
completionPrice: asFloat(pricing.completion)
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
function parseAlternative(value) {
|
|
722
|
+
if (!isRecord(value)) {
|
|
723
|
+
return null;
|
|
724
|
+
}
|
|
725
|
+
const suggestedModel = asString(value.suggestedModel) ?? asString(value.model) ?? asString(value.id) ?? "";
|
|
726
|
+
const slug = normalizeProviderSlug(
|
|
727
|
+
asString(value.slug) ?? asString(value.provider) ?? extractProviderSlug(suggestedModel) ?? ""
|
|
728
|
+
);
|
|
729
|
+
return {
|
|
730
|
+
slug,
|
|
731
|
+
name: asString(value.name) ?? slug,
|
|
732
|
+
status: parseStatus(
|
|
733
|
+
asString(value.status) ?? asString(value.providerStatus) ?? availableToStatus(value.available)
|
|
734
|
+
),
|
|
735
|
+
suggestedModel
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function availableToStatus(value) {
|
|
739
|
+
if (value === true) {
|
|
740
|
+
return "operational" /* OPERATIONAL */;
|
|
741
|
+
}
|
|
742
|
+
if (value === false) {
|
|
743
|
+
return "down" /* DOWN */;
|
|
744
|
+
}
|
|
745
|
+
return void 0;
|
|
746
|
+
}
|
|
747
|
+
function parseStatus(value) {
|
|
748
|
+
switch (value) {
|
|
749
|
+
case "operational" /* OPERATIONAL */:
|
|
750
|
+
return "operational" /* OPERATIONAL */;
|
|
751
|
+
case "degraded" /* DEGRADED */:
|
|
752
|
+
return "degraded" /* DEGRADED */;
|
|
753
|
+
case "down" /* DOWN */:
|
|
754
|
+
return "down" /* DOWN */;
|
|
755
|
+
default:
|
|
756
|
+
return "unknown" /* UNKNOWN */;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
function asString(value) {
|
|
760
|
+
return typeof value === "string" ? value : void 0;
|
|
761
|
+
}
|
|
762
|
+
function asNumber(value) {
|
|
763
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
764
|
+
return value;
|
|
765
|
+
}
|
|
766
|
+
if (typeof value === "string") {
|
|
767
|
+
const parsed = Number(value);
|
|
768
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
769
|
+
}
|
|
770
|
+
return 0;
|
|
771
|
+
}
|
|
772
|
+
function asFloat(value) {
|
|
773
|
+
return asNumber(value);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// src/router.ts
|
|
777
|
+
var Router = class {
|
|
778
|
+
api;
|
|
779
|
+
adapters = /* @__PURE__ */ new Map();
|
|
780
|
+
adapterIndex = /* @__PURE__ */ new Map();
|
|
781
|
+
tiers = /* @__PURE__ */ new Map();
|
|
782
|
+
constructor(options = {}) {
|
|
783
|
+
this.api = new StatusAPI(
|
|
784
|
+
options.baseUrl,
|
|
785
|
+
options.checkTimeout ?? 3
|
|
786
|
+
);
|
|
787
|
+
if (options.autoDiscover !== false) {
|
|
788
|
+
this.autoDiscover(options.providers);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
registerProvider(config) {
|
|
792
|
+
const adapter = createAdapter(config);
|
|
793
|
+
this.adapters.set(adapter.slug, adapter);
|
|
794
|
+
this.indexAdapter(adapter);
|
|
795
|
+
}
|
|
796
|
+
addTier(name, models) {
|
|
797
|
+
this.tiers.set(name, [...models]);
|
|
798
|
+
}
|
|
799
|
+
async route(messages, options = {}) {
|
|
800
|
+
const normalizedMessages = this.normalizeMessages(messages, options.system);
|
|
801
|
+
const callOptions = this.extractCallOptions(options);
|
|
802
|
+
if (!options.model && !options.tier) {
|
|
803
|
+
throw new Error("Either 'model' or 'tier' must be specified");
|
|
804
|
+
}
|
|
805
|
+
if (options.tier) {
|
|
806
|
+
return this.routeTier(normalizedMessages, options.tier, options, callOptions);
|
|
807
|
+
}
|
|
808
|
+
return this.routeModel(
|
|
809
|
+
normalizedMessages,
|
|
810
|
+
options.model,
|
|
811
|
+
options,
|
|
812
|
+
callOptions
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
async aroute(messages, options = {}) {
|
|
816
|
+
return this.route(messages, options);
|
|
817
|
+
}
|
|
818
|
+
autoDiscover(only) {
|
|
819
|
+
for (const [slug, spec] of Object.entries(AUTO_PROVIDERS)) {
|
|
820
|
+
if (only && !only.includes(slug)) {
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
if (!readEnv(spec.envVar)) {
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
this.registerProvider({
|
|
827
|
+
slug,
|
|
828
|
+
adapterType: spec.adapterType,
|
|
829
|
+
env: spec.envVar,
|
|
830
|
+
aliases: spec.aliases
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
indexAdapter(adapter) {
|
|
835
|
+
this.adapterIndex.set(adapter.slug, adapter.slug);
|
|
836
|
+
for (const alias of adapter.aliases) {
|
|
837
|
+
this.adapterIndex.set(alias, adapter.slug);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
normalizeMessages(messages, system) {
|
|
841
|
+
const normalized = typeof messages === "string" ? [{ role: "user", content: messages }] : [...messages];
|
|
842
|
+
if (system) {
|
|
843
|
+
normalized.unshift({ role: "system", content: system });
|
|
844
|
+
}
|
|
845
|
+
return normalized;
|
|
846
|
+
}
|
|
847
|
+
extractCallOptions(options) {
|
|
848
|
+
const {
|
|
849
|
+
model: _model,
|
|
850
|
+
tier: _tier,
|
|
851
|
+
system: _system,
|
|
852
|
+
allowFallback: _allowFallback,
|
|
853
|
+
timeout: _timeout,
|
|
854
|
+
prefer: _prefer,
|
|
855
|
+
maxTokens,
|
|
856
|
+
temperature,
|
|
857
|
+
topP,
|
|
858
|
+
providerOptions,
|
|
859
|
+
headers,
|
|
860
|
+
signal,
|
|
861
|
+
...extraProviderOptions
|
|
862
|
+
} = options;
|
|
863
|
+
return {
|
|
864
|
+
maxTokens,
|
|
865
|
+
temperature,
|
|
866
|
+
topP,
|
|
867
|
+
providerOptions: {
|
|
868
|
+
...extraProviderOptions,
|
|
869
|
+
...providerOptions ?? {}
|
|
870
|
+
},
|
|
871
|
+
headers,
|
|
872
|
+
signal
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
async resolveModel(model, prefer) {
|
|
876
|
+
try {
|
|
877
|
+
const check = await this.api.checkModel(model);
|
|
878
|
+
const primaryProvider = check.provider || this.guessProvider(model)[0]?.providerSlug;
|
|
879
|
+
const primary = {
|
|
880
|
+
providerSlug: primaryProvider,
|
|
881
|
+
modelId: check.model ?? model
|
|
882
|
+
};
|
|
883
|
+
const alternatives = check.alternatives.filter((alternative) => alternative.status === "operational").map((alternative) => ({
|
|
884
|
+
providerSlug: alternative.slug,
|
|
885
|
+
modelId: alternative.suggestedModel || model
|
|
886
|
+
}));
|
|
887
|
+
const ordered = check.isAvailable ? [primary, ...alternatives] : [...alternatives, primary];
|
|
888
|
+
return this.sortAndBindCandidates(ordered, prefer);
|
|
889
|
+
} catch {
|
|
890
|
+
return this.sortAndBindCandidates(this.guessProvider(model), prefer);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
guessProvider(model) {
|
|
894
|
+
const normalizedModel = model.toLowerCase();
|
|
895
|
+
const directProvider = normalizeProviderSlug(model.split("/", 1)[0]);
|
|
896
|
+
if (model.includes("/") && this.adapterIndex.has(directProvider)) {
|
|
897
|
+
return [{ providerSlug: directProvider, modelId: model }];
|
|
898
|
+
}
|
|
899
|
+
for (const [prefix, providerSlug] of Object.entries(MODEL_PREFIX_MAP)) {
|
|
900
|
+
if (normalizedModel.startsWith(prefix)) {
|
|
901
|
+
return [{ providerSlug, modelId: model }];
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
return Array.from(this.adapters.keys()).map((adapterKey) => ({
|
|
905
|
+
providerSlug: adapterKey,
|
|
906
|
+
modelId: model
|
|
907
|
+
}));
|
|
908
|
+
}
|
|
909
|
+
sortAndBindCandidates(candidates, prefer) {
|
|
910
|
+
const preferOrder = (prefer ?? []).map((item) => normalizeProviderSlug(item));
|
|
911
|
+
const resolved = candidates.map((candidate) => {
|
|
912
|
+
const providerSlug = normalizeProviderSlug(candidate.providerSlug);
|
|
913
|
+
const adapterKey = this.adapterIndex.get(providerSlug);
|
|
914
|
+
if (!adapterKey) {
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
return {
|
|
918
|
+
providerSlug,
|
|
919
|
+
adapterKey,
|
|
920
|
+
modelId: candidate.modelId
|
|
921
|
+
};
|
|
922
|
+
}).filter((candidate) => candidate !== null);
|
|
923
|
+
resolved.sort((left, right) => {
|
|
924
|
+
if (preferOrder.length === 0) {
|
|
925
|
+
return 0;
|
|
926
|
+
}
|
|
927
|
+
return preferenceScore(left, preferOrder) - preferenceScore(right, preferOrder);
|
|
928
|
+
});
|
|
929
|
+
return dedupeCandidates(resolved);
|
|
930
|
+
}
|
|
931
|
+
async routeModel(messages, model, options, callOptions) {
|
|
932
|
+
const candidates = await this.resolveModel(model, options.prefer);
|
|
933
|
+
if (candidates.length === 0) {
|
|
934
|
+
throw new AllProvidersDown([`no adapter for model '${model}'`]);
|
|
935
|
+
}
|
|
936
|
+
const tried = [];
|
|
937
|
+
const first = candidates[0];
|
|
938
|
+
for (const candidate of candidates) {
|
|
939
|
+
const adapter = this.adapters.get(candidate.adapterKey);
|
|
940
|
+
if (!adapter) {
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
943
|
+
try {
|
|
944
|
+
const response = await adapter.call(
|
|
945
|
+
candidate.modelId,
|
|
946
|
+
messages,
|
|
947
|
+
options.timeout ?? 30,
|
|
948
|
+
callOptions
|
|
949
|
+
);
|
|
950
|
+
const usedModel = response.modelUsed.includes("/") ? response.modelUsed : `${candidate.providerSlug}/${response.modelUsed}`;
|
|
951
|
+
const isFallback = candidate.providerSlug !== first.providerSlug || candidate.modelId !== first.modelId;
|
|
952
|
+
return new RouteResponse({
|
|
953
|
+
content: response.content,
|
|
954
|
+
modelUsed: usedModel,
|
|
955
|
+
providerUsed: candidate.providerSlug,
|
|
956
|
+
wasFallback: isFallback,
|
|
957
|
+
fallbackReason: isFallback ? `${first.providerSlug} unavailable` : null,
|
|
958
|
+
inputTokens: response.inputTokens,
|
|
959
|
+
outputTokens: response.outputTokens,
|
|
960
|
+
costUsd: response.costUsd,
|
|
961
|
+
raw: response.raw
|
|
962
|
+
});
|
|
963
|
+
} catch (error) {
|
|
964
|
+
tried.push(`${candidate.providerSlug}[error]`);
|
|
965
|
+
if (options.allowFallback === false) {
|
|
966
|
+
throw new ProviderCallFailed(
|
|
967
|
+
candidate.providerSlug,
|
|
968
|
+
candidate.modelId,
|
|
969
|
+
error
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
throw new AllProvidersDown(tried);
|
|
975
|
+
}
|
|
976
|
+
async routeTier(messages, tier, options, callOptions) {
|
|
977
|
+
const models = this.tiers.get(tier);
|
|
978
|
+
if (!models) {
|
|
979
|
+
throw new Error(
|
|
980
|
+
`Tier '${tier}' not configured. Use router.addTier('${tier}', ['model-a']) first.`
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
const tried = [];
|
|
984
|
+
for (const model of models) {
|
|
985
|
+
try {
|
|
986
|
+
return await this.routeModel(messages, model, options, callOptions);
|
|
987
|
+
} catch (error) {
|
|
988
|
+
if (error instanceof AllProvidersDown) {
|
|
989
|
+
tried.push(...error.tried);
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
if (error instanceof ProviderCallFailed && options.allowFallback === false) {
|
|
993
|
+
throw error;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
throw new AllProvidersDown(tried);
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
function preferenceScore(candidate, preferOrder) {
|
|
1001
|
+
const providerIndex = preferOrder.indexOf(candidate.providerSlug);
|
|
1002
|
+
if (providerIndex !== -1) {
|
|
1003
|
+
return providerIndex;
|
|
1004
|
+
}
|
|
1005
|
+
const adapterIndex = preferOrder.indexOf(candidate.adapterKey);
|
|
1006
|
+
if (adapterIndex !== -1) {
|
|
1007
|
+
return adapterIndex;
|
|
1008
|
+
}
|
|
1009
|
+
return preferOrder.length;
|
|
1010
|
+
}
|
|
1011
|
+
function dedupeCandidates(candidates) {
|
|
1012
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1013
|
+
const result = [];
|
|
1014
|
+
for (const candidate of candidates) {
|
|
1015
|
+
const key = `${candidate.adapterKey}:${candidate.modelId}`;
|
|
1016
|
+
if (seen.has(key)) {
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
seen.add(key);
|
|
1020
|
+
result.push(candidate);
|
|
1021
|
+
}
|
|
1022
|
+
return result;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// src/index.ts
|
|
1026
|
+
var VERSION = "0.0.2";
|
|
1027
|
+
var defaultRouter = null;
|
|
1028
|
+
function getDefaultRouter() {
|
|
1029
|
+
defaultRouter ??= new Router();
|
|
1030
|
+
return defaultRouter;
|
|
1031
|
+
}
|
|
1032
|
+
async function route(messages, options = {}) {
|
|
1033
|
+
return getDefaultRouter().route(messages, options);
|
|
1034
|
+
}
|
|
1035
|
+
async function aroute(messages, options = {}) {
|
|
1036
|
+
return route(messages, options);
|
|
1037
|
+
}
|
|
1038
|
+
export {
|
|
1039
|
+
AIStatusError,
|
|
1040
|
+
AllProvidersDown,
|
|
1041
|
+
CheckAPIUnreachable,
|
|
1042
|
+
CheckResult,
|
|
1043
|
+
NoBudgetMatch,
|
|
1044
|
+
ProviderAdapter,
|
|
1045
|
+
ProviderCallFailed,
|
|
1046
|
+
ProviderNotConfigured,
|
|
1047
|
+
ProviderNotInstalled,
|
|
1048
|
+
RouteResponse,
|
|
1049
|
+
Router,
|
|
1050
|
+
Status,
|
|
1051
|
+
StatusAPI,
|
|
1052
|
+
VERSION,
|
|
1053
|
+
aroute,
|
|
1054
|
+
createAdapter,
|
|
1055
|
+
registerAdapterType,
|
|
1056
|
+
route
|
|
1057
|
+
};
|