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