@broberg/ai-sdk 0.53.0 → 0.54.1
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/{chunk-XDBN6QJ4.js → chunk-PDGFAD66.js} +2 -1
- package/dist/{chunk-ZSLRCOYM.js → chunk-TSNJ5N4F.js} +3 -1
- package/dist/index.d.ts +45 -4
- package/dist/index.js +200 -8
- package/dist/index.js.map +1 -1
- package/dist/pricing.js +1 -1
- package/dist/registry.js +1 -1
- package/package.json +1 -1
- /package/dist/{chunk-XDBN6QJ4.js.map → chunk-PDGFAD66.js.map} +0 -0
- /package/dist/{chunk-ZSLRCOYM.js.map → chunk-TSNJ5N4F.js.map} +0 -0
|
@@ -225,10 +225,11 @@ export {
|
|
|
225
225
|
assertOverrideProvider,
|
|
226
226
|
resolveTier,
|
|
227
227
|
resetRegistry,
|
|
228
|
+
allEntries,
|
|
228
229
|
providerIds,
|
|
229
230
|
setAvailability,
|
|
230
231
|
ModelUnavailableError,
|
|
231
232
|
listModels,
|
|
232
233
|
resolveModel
|
|
233
234
|
};
|
|
234
|
-
//# sourceMappingURL=chunk-
|
|
235
|
+
//# sourceMappingURL=chunk-PDGFAD66.js.map
|
|
@@ -460,8 +460,10 @@ function resetPricingWarningForTests() {
|
|
|
460
460
|
}
|
|
461
461
|
|
|
462
462
|
export {
|
|
463
|
+
PRICING,
|
|
463
464
|
getPrice,
|
|
464
465
|
MEDIA_PRICING_CHECKED_AT,
|
|
466
|
+
MEDIA_PRICING,
|
|
465
467
|
DEFAULT_CLIP_SEC,
|
|
466
468
|
getMediaPrice,
|
|
467
469
|
listMediaPrices,
|
|
@@ -475,4 +477,4 @@ export {
|
|
|
475
477
|
warnIfPricingStale,
|
|
476
478
|
resetPricingWarningForTests
|
|
477
479
|
};
|
|
478
|
-
//# sourceMappingURL=chunk-
|
|
480
|
+
//# sourceMappingURL=chunk-TSNJ5N4F.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -2850,7 +2850,7 @@ interface OpenAICompatibleConfig {
|
|
|
2850
2850
|
apiKey?: string;
|
|
2851
2851
|
/** Extra headers (e.g. OpenRouter's HTTP-Referer / X-Title). */
|
|
2852
2852
|
extraHeaders?: Record<string, string>;
|
|
2853
|
-
/** Injectable fetch
|
|
2853
|
+
/** Injectable fetch — used by BOTH the streaming and the non-streaming path (F073). */
|
|
2854
2854
|
fetch?: typeof fetch;
|
|
2855
2855
|
/** F049 — the provider understands `prefix: true` on a trailing assistant message.
|
|
2856
2856
|
* Mistral only. A prefix sent anywhere else is REFUSED rather than dropped: a
|
|
@@ -2884,6 +2884,43 @@ declare function byokAdapter<C extends KeyedConfig>(factory: (config: C) => Prov
|
|
|
2884
2884
|
/** True when the adapter was built by byokAdapter with a real key. */
|
|
2885
2885
|
declare function isByokKeyed(adapter: ProviderAdapter): boolean;
|
|
2886
2886
|
|
|
2887
|
+
interface PriceDriftRow {
|
|
2888
|
+
/** `${provider}:${model}` — the key in ai-sdk's price table. */
|
|
2889
|
+
key: string;
|
|
2890
|
+
provider: string;
|
|
2891
|
+
model: string;
|
|
2892
|
+
ours: {
|
|
2893
|
+
inputPer1M: number;
|
|
2894
|
+
outputPer1M: number;
|
|
2895
|
+
};
|
|
2896
|
+
upstream: {
|
|
2897
|
+
inputPer1M?: number;
|
|
2898
|
+
outputPer1M?: number;
|
|
2899
|
+
};
|
|
2900
|
+
/** Relative change, upstream vs ours, in percent (+20 = 20 % dearer). Undefined when
|
|
2901
|
+
* upstream omits that side. */
|
|
2902
|
+
inputChangePct?: number;
|
|
2903
|
+
outputChangePct?: number;
|
|
2904
|
+
}
|
|
2905
|
+
type PriceDriftResult = {
|
|
2906
|
+
ok: true;
|
|
2907
|
+
measuredAt: string;
|
|
2908
|
+
source: "openrouter";
|
|
2909
|
+
checked: number;
|
|
2910
|
+
drift: PriceDriftRow[];
|
|
2911
|
+
coverage: string;
|
|
2912
|
+
} | {
|
|
2913
|
+
ok: false;
|
|
2914
|
+
measuredAt: string;
|
|
2915
|
+
error: string;
|
|
2916
|
+
};
|
|
2917
|
+
/** Compare ai-sdk's price table with OpenRouter's live prices. Never throws: an outage
|
|
2918
|
+
* returns `{ ok: false, error }`, so it cannot look like "no price changes". */
|
|
2919
|
+
declare function checkPriceDrift(opts?: {
|
|
2920
|
+
fetch?: typeof fetch;
|
|
2921
|
+
threshold?: number;
|
|
2922
|
+
}): Promise<PriceDriftResult>;
|
|
2923
|
+
|
|
2887
2924
|
declare const defaultProviders: Record<string, ProviderAdapter>;
|
|
2888
2925
|
|
|
2889
2926
|
/** Anthropic adapter stub (HTTP path). */
|
|
@@ -2902,8 +2939,8 @@ declare const falStubAdapter: ProviderAdapter;
|
|
|
2902
2939
|
* wires the live adapters. */
|
|
2903
2940
|
declare const stubProviders: Record<string, ProviderAdapter>;
|
|
2904
2941
|
|
|
2905
|
-
declare const VERSION: "0.
|
|
2906
|
-
declare const SDK_TAG: "@broberg/ai-sdk@0.
|
|
2942
|
+
declare const VERSION: "0.54.1";
|
|
2943
|
+
declare const SDK_TAG: "@broberg/ai-sdk@0.54.1";
|
|
2907
2944
|
|
|
2908
2945
|
/** Built-in defaults. Every entry is overridable via AiConfig.defaults or a
|
|
2909
2946
|
* per-call override.
|
|
@@ -3354,6 +3391,10 @@ interface TransportRequest {
|
|
|
3354
3391
|
spec: TierSpec;
|
|
3355
3392
|
/** HTTP details — required when spec.transport === "http". The adapter has
|
|
3356
3393
|
* already built the provider-specific URL/headers/body. */
|
|
3394
|
+
/** F073 — the fetch to use. Absent → the global fetch. An adapter given `fetch` in its
|
|
3395
|
+
* config MUST pass it here, or a test's fake fetch is silently bypassed and the call
|
|
3396
|
+
* reaches the real provider with whatever key is in the environment. */
|
|
3397
|
+
fetch?: typeof fetch;
|
|
3357
3398
|
http?: {
|
|
3358
3399
|
url: string;
|
|
3359
3400
|
method?: string;
|
|
@@ -3414,4 +3455,4 @@ interface StreamTransportRequest extends TransportRequest {
|
|
|
3414
3455
|
*/
|
|
3415
3456
|
declare function streamTransport(req: StreamTransportRequest): AsyncIterable<string>;
|
|
3416
3457
|
|
|
3417
|
-
export { AZURE_DANISH_VOICES, AZURE_DANISH_VOICE_LIST, type AiClient, type AiConfig, type AlignedWordTimings, type AzureVoiceInfo, type AzureWordBoundary, type BatchJob, type BatchRequestItem, type BatchResultItem, type BflAdapterConfig, type BflCredits, type BudgetConfig, BudgetExceededError, BudgetGuard, type BudgetStore, CONFIG_DERIVED_HOSTS, type CallOptions, type Capability, type ChatInput, type ChatRequest, type ChatResult, type ChatStreamEvent, type CheckVoiceOptions, type ClassifyInput, type ClassifyResult, type ContentPart, type Contracts, type CostQuery, type CostSink, type CostSummary, type CostSummaryQuery, type CostTimeseriesQuery, DEFAULT_BASE_URLS, DEFAULT_TIER_MAP, type DesignInput, type DesignResult, type DialogueRequest, type DialogueTurn, type DiscordSinkConfig, ELEVENLABS_DANISH_VOICES, type EmbeddingInput, type EmbeddingRequest, type EmbeddingResult, type ExtractInput, type ExtractResult, type FalAdapterConfig, type FixedHostProvider, type HttpResponse, type ImageInput, type ImageRequest, type ImageResult, type JudgeAnswer, type JudgeChoiceAnswer, type JudgeInput, type JudgeNoulAnswer, type JudgeQuestion, type JudgeResult, type JudgeScoreAnswer, LOCALLY_PINNED_HOSTS, type LoraWeight, type Message, type MockupInput, type MockupResult, type ModerationInput, type ModerationItem, type ModerationRequest, type ModerationResult, type OcrInput, type OcrPage, type OcrRequest, type OcrResult, type OpenAICompatibleConfig, type PodcastInput, type PodcastResult, type PricingEntry, type ProviderAdapter, type RefreshOptions, type RefreshResult, type Region, type RerankInput, type RerankResult, type Role, type RouteForecast, SDK_TAG, type SqliteBudgetStoreConfig, type SqliteSinkConfig, StreamHttpError, type SubprocessResponse, type Tier, type TierSpec, type Tool, type ToolCall, type TrainStyleInput, type TrainStyleRequest, type TrainStyleResult, type TranscribeInput, type TranscribeRequest, type TranscribeResult, type TranslateInput, type TranslateResult, type Transport, type TransportRequest, type TransportResponse, type TtsInput, type TtsRequest, type UpmetricsCostClientConfig, UpmetricsCostError, type UpmetricsCostRow, type UpmetricsCostSummary, type UpmetricsCostTimeseries, type UpmetricsSink, type UpmetricsSinkConfig, type UpmetricsSinkStats, type Usage, VERSION, type VideoInput, type VisionInput, type VoiceInfo, type VoiceProvider, type VoiceResolveResult, type VoiceStatus, VoiceUnavailableError, type WordTiming, aiConfigSchema, alignWordTimings, anthropicAdapter, anthropicApiAdapter, anthropicSubprocessAdapter, azureAdapter, bflAdapter, bflCredits, byokAdapter, chatInputSchema, checkVoice, classifyRegionName, computeCost, createAI, deepinfraAdapter, deeplAdapter, deepseekAdapter, defaultBaseUrl, defaultProviders, discordSink, elevenlabsAdapter, embeddingInputSchema, falAdapter, falStubAdapter, freshUsage, fromProviderToolCall, geminiAdapter, getCostSummary, getPrice, httpTransport, imageInputSchema, isByokKeyed, listAzureDanishVoices, listVoices, makeContracts, makeOpenAICompatibleAdapter, matchLabel, messageSchema, mistralAdapter, mistralStubAdapter, multiSink, noopSink, openaiAdapter, openaiStubAdapter, openrouterAdapter, parseClaudeCliJson, parseJsonLoose, refreshAvailability, regionOfHost, regionOfProvider, requestyAdapter, resetRefreshState, resetRegistry, resolveAzureVoice, resolveTier, resolveVoice, sqliteBudgetStore, sqliteSink, streamTransport, stubProviders, subprocessTransport, tierSpecSchema, toProviderTools, toolSchema, translateInputSchema, typesafeAdapter, upmetricsCostClient, upmetricsSink, usdFromMicro, vertexAdapter, visionInputSchema, wouldProviderRouteTo, wouldRouteTo };
|
|
3458
|
+
export { AZURE_DANISH_VOICES, AZURE_DANISH_VOICE_LIST, type AiClient, type AiConfig, type AlignedWordTimings, type AzureVoiceInfo, type AzureWordBoundary, type BatchJob, type BatchRequestItem, type BatchResultItem, type BflAdapterConfig, type BflCredits, type BudgetConfig, BudgetExceededError, BudgetGuard, type BudgetStore, CONFIG_DERIVED_HOSTS, type CallOptions, type Capability, type ChatInput, type ChatRequest, type ChatResult, type ChatStreamEvent, type CheckVoiceOptions, type ClassifyInput, type ClassifyResult, type ContentPart, type Contracts, type CostQuery, type CostSink, type CostSummary, type CostSummaryQuery, type CostTimeseriesQuery, DEFAULT_BASE_URLS, DEFAULT_TIER_MAP, type DesignInput, type DesignResult, type DialogueRequest, type DialogueTurn, type DiscordSinkConfig, ELEVENLABS_DANISH_VOICES, type EmbeddingInput, type EmbeddingRequest, type EmbeddingResult, type ExtractInput, type ExtractResult, type FalAdapterConfig, type FixedHostProvider, type HttpResponse, type ImageInput, type ImageRequest, type ImageResult, type JudgeAnswer, type JudgeChoiceAnswer, type JudgeInput, type JudgeNoulAnswer, type JudgeQuestion, type JudgeResult, type JudgeScoreAnswer, LOCALLY_PINNED_HOSTS, type LoraWeight, type Message, type MockupInput, type MockupResult, type ModerationInput, type ModerationItem, type ModerationRequest, type ModerationResult, type OcrInput, type OcrPage, type OcrRequest, type OcrResult, type OpenAICompatibleConfig, type PodcastInput, type PodcastResult, type PriceDriftResult, type PriceDriftRow, type PricingEntry, type ProviderAdapter, type RefreshOptions, type RefreshResult, type Region, type RerankInput, type RerankResult, type Role, type RouteForecast, SDK_TAG, type SqliteBudgetStoreConfig, type SqliteSinkConfig, StreamHttpError, type SubprocessResponse, type Tier, type TierSpec, type Tool, type ToolCall, type TrainStyleInput, type TrainStyleRequest, type TrainStyleResult, type TranscribeInput, type TranscribeRequest, type TranscribeResult, type TranslateInput, type TranslateResult, type Transport, type TransportRequest, type TransportResponse, type TtsInput, type TtsRequest, type UpmetricsCostClientConfig, UpmetricsCostError, type UpmetricsCostRow, type UpmetricsCostSummary, type UpmetricsCostTimeseries, type UpmetricsSink, type UpmetricsSinkConfig, type UpmetricsSinkStats, type Usage, VERSION, type VideoInput, type VisionInput, type VoiceInfo, type VoiceProvider, type VoiceResolveResult, type VoiceStatus, VoiceUnavailableError, type WordTiming, aiConfigSchema, alignWordTimings, anthropicAdapter, anthropicApiAdapter, anthropicSubprocessAdapter, azureAdapter, bflAdapter, bflCredits, byokAdapter, chatInputSchema, checkPriceDrift, checkVoice, classifyRegionName, computeCost, createAI, deepinfraAdapter, deeplAdapter, deepseekAdapter, defaultBaseUrl, defaultProviders, discordSink, elevenlabsAdapter, embeddingInputSchema, falAdapter, falStubAdapter, freshUsage, fromProviderToolCall, geminiAdapter, getCostSummary, getPrice, httpTransport, imageInputSchema, isByokKeyed, listAzureDanishVoices, listVoices, makeContracts, makeOpenAICompatibleAdapter, matchLabel, messageSchema, mistralAdapter, mistralStubAdapter, multiSink, noopSink, openaiAdapter, openaiStubAdapter, openrouterAdapter, parseClaudeCliJson, parseJsonLoose, refreshAvailability, regionOfHost, regionOfProvider, requestyAdapter, resetRefreshState, resetRegistry, resolveAzureVoice, resolveTier, resolveVoice, sqliteBudgetStore, sqliteSink, streamTransport, stubProviders, subprocessTransport, tierSpecSchema, toProviderTools, toolSchema, translateInputSchema, typesafeAdapter, upmetricsCostClient, upmetricsSink, usdFromMicro, vertexAdapter, visionInputSchema, wouldProviderRouteTo, wouldRouteTo };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEFAULT_TIER_MAP,
|
|
3
3
|
ModelUnavailableError,
|
|
4
|
+
allEntries,
|
|
4
5
|
assertOverrideProvider,
|
|
5
6
|
listModels,
|
|
6
7
|
providerIds,
|
|
@@ -8,17 +9,19 @@ import {
|
|
|
8
9
|
resolveModel,
|
|
9
10
|
resolveTier,
|
|
10
11
|
setAvailability
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-PDGFAD66.js";
|
|
12
13
|
import {
|
|
13
14
|
DEFAULT_CLIP_SEC,
|
|
15
|
+
MEDIA_PRICING,
|
|
14
16
|
MEDIA_PRICING_CHECKED_AT,
|
|
17
|
+
PRICING,
|
|
15
18
|
PRICING_STALE_AFTER_DAYS,
|
|
16
19
|
getMediaPrice,
|
|
17
20
|
getPrice,
|
|
18
21
|
listMediaPrices,
|
|
19
22
|
pricingFreshness,
|
|
20
23
|
pricingGeneratedAt
|
|
21
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-TSNJ5N4F.js";
|
|
22
25
|
|
|
23
26
|
// src/cost/default-hosts.ts
|
|
24
27
|
var DEFAULT_BASE_URLS = {
|
|
@@ -53,7 +56,7 @@ async function httpTransport(req) {
|
|
|
53
56
|
throw new Error("httpTransport: req.http is required for http transport");
|
|
54
57
|
}
|
|
55
58
|
const { url, method = "POST", headers, body } = req.http;
|
|
56
|
-
const res = await fetch(url, {
|
|
59
|
+
const res = await (req.fetch ?? fetch)(url, {
|
|
57
60
|
method,
|
|
58
61
|
headers,
|
|
59
62
|
body: body === void 0 ? void 0 : typeof body === "string" ? body : JSON.stringify(body)
|
|
@@ -363,7 +366,7 @@ function regionOfProvider(provider) {
|
|
|
363
366
|
function computeCost(provider, model, inputTokens, outputTokens, cacheReadTokens = 0, cacheCreationTokens = 0) {
|
|
364
367
|
const price = getPrice(provider, model);
|
|
365
368
|
if (!price) return 0;
|
|
366
|
-
const perToken = (
|
|
369
|
+
const perToken = (per1M2) => per1M2 / 1e6;
|
|
367
370
|
const inRate = perToken(price.inputPer1M);
|
|
368
371
|
const outRate = perToken(price.outputPer1M);
|
|
369
372
|
const cacheReadRate = price.cacheReadPer1M !== void 0 ? perToken(price.cacheReadPer1M) : inRate;
|
|
@@ -492,6 +495,7 @@ function anthropicAdapter(config = {}) {
|
|
|
492
495
|
const body = buildBody(req);
|
|
493
496
|
const res = await httpTransport({
|
|
494
497
|
spec: req.spec,
|
|
498
|
+
fetch: config.fetch,
|
|
495
499
|
http: {
|
|
496
500
|
url: `${baseUrl}/v1/messages`,
|
|
497
501
|
headers: {
|
|
@@ -771,6 +775,7 @@ function makeOpenAICompatibleAdapter(config) {
|
|
|
771
775
|
const body = buildChatBody(req, config);
|
|
772
776
|
const res = await httpTransport({
|
|
773
777
|
spec: req.spec,
|
|
778
|
+
fetch: config.fetch,
|
|
774
779
|
http: {
|
|
775
780
|
url: `${config.baseUrl}/chat/completions`,
|
|
776
781
|
headers: {
|
|
@@ -928,12 +933,13 @@ function mapFinishReason(reason) {
|
|
|
928
933
|
// src/providers/openai.ts
|
|
929
934
|
function openaiAdapter(config = {}) {
|
|
930
935
|
const baseUrl = config.baseUrl ?? DEFAULT_BASE_URLS.openai;
|
|
931
|
-
const base = makeOpenAICompatibleAdapter({ name: "openai", baseUrl, apiKey: config.apiKey });
|
|
936
|
+
const base = makeOpenAICompatibleAdapter({ name: "openai", baseUrl, apiKey: config.apiKey, fetch: config.fetch });
|
|
932
937
|
async function embedding(req) {
|
|
933
938
|
const apiKey = config.apiKey ?? process.env.OPENAI_API_KEY;
|
|
934
939
|
if (!apiKey) throw new Error("openai adapter: API key not set (env OPENAI_API_KEY)");
|
|
935
940
|
const res = await httpTransport({
|
|
936
941
|
spec: req.spec,
|
|
942
|
+
fetch: config.fetch,
|
|
937
943
|
http: {
|
|
938
944
|
url: `${baseUrl}/embeddings`,
|
|
939
945
|
headers: { "content-type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
@@ -1078,6 +1084,7 @@ function geminiAdapter(config = {}) {
|
|
|
1078
1084
|
const body = buildBody(req);
|
|
1079
1085
|
const res = await httpTransport({
|
|
1080
1086
|
spec: req.spec,
|
|
1087
|
+
fetch: config.fetch,
|
|
1081
1088
|
http: {
|
|
1082
1089
|
url: `${baseUrl}/models/${req.spec.model}:generateContent?key=${encodeURIComponent(apiKey)}`,
|
|
1083
1090
|
headers: { "content-type": "application/json" },
|
|
@@ -1373,7 +1380,7 @@ function deepseekAdapter(config = {}) {
|
|
|
1373
1380
|
// src/providers/mistral.ts
|
|
1374
1381
|
function mistralAdapter(config = {}) {
|
|
1375
1382
|
const baseUrl = config.baseUrl ?? DEFAULT_BASE_URLS.mistral;
|
|
1376
|
-
const base = makeOpenAICompatibleAdapter({ name: "mistral", baseUrl, apiKey: config.apiKey, supportsPromptCacheKey: true, supportsPrefix: true });
|
|
1383
|
+
const base = makeOpenAICompatibleAdapter({ name: "mistral", baseUrl, apiKey: config.apiKey, fetch: config.fetch, supportsPromptCacheKey: true, supportsPrefix: true });
|
|
1377
1384
|
function key() {
|
|
1378
1385
|
const k = config.apiKey ?? process.env.MISTRAL_API_KEY;
|
|
1379
1386
|
if (!k) throw new Error("mistral adapter: API key not set (env MISTRAL_API_KEY)");
|
|
@@ -3523,8 +3530,8 @@ var aiConfigSchema = z.object({
|
|
|
3523
3530
|
import { randomUUID } from "crypto";
|
|
3524
3531
|
|
|
3525
3532
|
// src/version.ts
|
|
3526
|
-
var VERSION = "0.
|
|
3527
|
-
var SDK_TAG = "@broberg/ai-sdk@0.
|
|
3533
|
+
var VERSION = "0.54.1";
|
|
3534
|
+
var SDK_TAG = "@broberg/ai-sdk@0.54.1";
|
|
3528
3535
|
|
|
3529
3536
|
// src/cost/sinks/upmetrics.ts
|
|
3530
3537
|
function isRetryableStatus(status) {
|
|
@@ -3871,6 +3878,21 @@ var DEFAULT_JUDGE_SPEC = { provider: "typesafe", model: "jev-latest", transport:
|
|
|
3871
3878
|
var DEFAULT_PODCAST_SPEC = { provider: "elevenlabs", model: "eleven_v3", transport: "http" };
|
|
3872
3879
|
var DEFAULT_TTS_SPEC = { provider: "elevenlabs", model: "eleven_multilingual_v2", transport: "http" };
|
|
3873
3880
|
var DEFAULT_BATCH_SPEC = { provider: "mistral", model: "mistral-small-latest", transport: "http" };
|
|
3881
|
+
var ROUTED_SPECS = [
|
|
3882
|
+
DEFAULT_IMAGE_SPEC,
|
|
3883
|
+
DEFAULT_LORA_IMAGE_SPEC,
|
|
3884
|
+
DEFAULT_TRAINSTYLE_SPEC,
|
|
3885
|
+
DEFAULT_ANIMATE_SPEC,
|
|
3886
|
+
DEFAULT_BFL_FINETUNE_SPEC,
|
|
3887
|
+
DEFAULT_BFL_REFERENCE_SPEC,
|
|
3888
|
+
DEFAULT_OCR_SPEC,
|
|
3889
|
+
DEFAULT_MODERATION_SPEC,
|
|
3890
|
+
DEFAULT_JUDGE_SPEC,
|
|
3891
|
+
DEFAULT_PODCAST_SPEC,
|
|
3892
|
+
DEFAULT_TTS_SPEC,
|
|
3893
|
+
DEFAULT_BATCH_SPEC,
|
|
3894
|
+
DEFAULT_TRANSCRIBE_SPEC
|
|
3895
|
+
];
|
|
3874
3896
|
function defaultCostSink() {
|
|
3875
3897
|
const apiKey = process.env.UPMETRICS_API_KEY;
|
|
3876
3898
|
if (!apiKey) return void 0;
|
|
@@ -4432,6 +4454,175 @@ function createAI(config = {}) {
|
|
|
4432
4454
|
return client;
|
|
4433
4455
|
}
|
|
4434
4456
|
|
|
4457
|
+
// src/catalogue/fetchers.ts
|
|
4458
|
+
async function getJson(f, url, headers) {
|
|
4459
|
+
const res = await f(url, { headers: { accept: "application/json", ...headers } });
|
|
4460
|
+
if (!res.ok) {
|
|
4461
|
+
const body = await res.text().catch(() => "");
|
|
4462
|
+
throw new Error(`${res.status} ${body.slice(0, 200)}`);
|
|
4463
|
+
}
|
|
4464
|
+
return res.json();
|
|
4465
|
+
}
|
|
4466
|
+
function per1M(v) {
|
|
4467
|
+
if (v == null) return void 0;
|
|
4468
|
+
const n = Number(v) * 1e6;
|
|
4469
|
+
return Number.isFinite(n) && n >= 0 ? n : void 0;
|
|
4470
|
+
}
|
|
4471
|
+
async function fetchOpenRouterCatalogue(opts = {}) {
|
|
4472
|
+
const f = opts.fetch ?? fetch;
|
|
4473
|
+
const base = opts.baseUrl ?? "https://openrouter.ai/api/v1";
|
|
4474
|
+
const json = await getJson(f, `${base}/models`, {});
|
|
4475
|
+
return (json.data ?? []).map((m) => {
|
|
4476
|
+
const inputPer1M = per1M(m.pricing?.prompt);
|
|
4477
|
+
const outputPer1M = per1M(m.pricing?.completion);
|
|
4478
|
+
return {
|
|
4479
|
+
provider: "openrouter",
|
|
4480
|
+
model: m.id,
|
|
4481
|
+
...inputPer1M !== void 0 ? { inputPer1M } : {},
|
|
4482
|
+
...outputPer1M !== void 0 ? { outputPer1M } : {},
|
|
4483
|
+
...m.context_length ? { contextLength: m.context_length } : {}
|
|
4484
|
+
};
|
|
4485
|
+
});
|
|
4486
|
+
}
|
|
4487
|
+
|
|
4488
|
+
// src/catalogue/types.ts
|
|
4489
|
+
function catalogueKey(m) {
|
|
4490
|
+
return `${m.provider}:${m.model}`;
|
|
4491
|
+
}
|
|
4492
|
+
|
|
4493
|
+
// src/catalogue/diff.ts
|
|
4494
|
+
function brandToken(model) {
|
|
4495
|
+
const tail = model.split("/").pop() ?? model;
|
|
4496
|
+
return tail.split("-")[0] ?? tail;
|
|
4497
|
+
}
|
|
4498
|
+
var DIRECT_PROVIDERS = /* @__PURE__ */ new Set(["openai", "anthropic", "gemini"]);
|
|
4499
|
+
var FULL_LIST_PROVIDERS = /* @__PURE__ */ new Set(["mistral", "deepseek", "elevenlabs"]);
|
|
4500
|
+
function undatedBase(id) {
|
|
4501
|
+
return id.replace(/-(\d{4}-\d{2}-\d{2}|\d{8}|\d{4})$/, "");
|
|
4502
|
+
}
|
|
4503
|
+
function knownKeys() {
|
|
4504
|
+
const known = /* @__PURE__ */ new Set([...Object.keys(PRICING), ...Object.keys(MEDIA_PRICING)]);
|
|
4505
|
+
for (const e of allEntries()) known.add(`${e.provider}:${e.id}`);
|
|
4506
|
+
for (const spec of [...Object.values(DEFAULT_TIER_MAP), ...ROUTED_SPECS]) known.add(`${spec.provider}:${spec.model}`);
|
|
4507
|
+
return known;
|
|
4508
|
+
}
|
|
4509
|
+
function diffCatalogue(fetched, opts = {}) {
|
|
4510
|
+
const threshold = opts.priceThreshold ?? 0.01;
|
|
4511
|
+
const fetchedProviders = new Set(opts.fetchedProviders ?? fetched.map((m) => m.provider));
|
|
4512
|
+
const fetchedByKey = /* @__PURE__ */ new Map();
|
|
4513
|
+
for (const m of fetched) {
|
|
4514
|
+
fetchedByKey.set(catalogueKey(m), m);
|
|
4515
|
+
for (const a of m.aliases ?? []) {
|
|
4516
|
+
const k = catalogueKey({ provider: m.provider, model: a });
|
|
4517
|
+
if (!fetchedByKey.has(k)) fetchedByKey.set(k, m);
|
|
4518
|
+
}
|
|
4519
|
+
}
|
|
4520
|
+
const known = knownKeys();
|
|
4521
|
+
const trackedBrands = /* @__PURE__ */ new Map();
|
|
4522
|
+
for (const key of Object.keys(PRICING)) {
|
|
4523
|
+
const [provider, model] = splitKey(key);
|
|
4524
|
+
if (!trackedBrands.has(provider)) trackedBrands.set(provider, /* @__PURE__ */ new Set());
|
|
4525
|
+
trackedBrands.get(provider).add(brandToken(model));
|
|
4526
|
+
}
|
|
4527
|
+
const added = [];
|
|
4528
|
+
const reportedGroups = /* @__PURE__ */ new Set();
|
|
4529
|
+
const listedByProvider = /* @__PURE__ */ new Map();
|
|
4530
|
+
for (const m of fetched) {
|
|
4531
|
+
if (!listedByProvider.has(m.provider)) listedByProvider.set(m.provider, /* @__PURE__ */ new Set());
|
|
4532
|
+
listedByProvider.get(m.provider).add(m.model);
|
|
4533
|
+
}
|
|
4534
|
+
const isSnapshotOfListed = (m) => {
|
|
4535
|
+
const base = undatedBase(m.model);
|
|
4536
|
+
if (base === m.model) return false;
|
|
4537
|
+
return listedByProvider.get(m.provider)?.has(base) === true || known.has(catalogueKey({ provider: m.provider, model: base }));
|
|
4538
|
+
};
|
|
4539
|
+
for (const m of fetched) {
|
|
4540
|
+
if (FULL_LIST_PROVIDERS.has(m.provider)) {
|
|
4541
|
+
if (m.deprecated) continue;
|
|
4542
|
+
const group = [m.model, ...m.aliases ?? []].map((id) => catalogueKey({ provider: m.provider, model: id }));
|
|
4543
|
+
if (group.some((k) => known.has(k) || reportedGroups.has(k))) continue;
|
|
4544
|
+
for (const k of group) reportedGroups.add(k);
|
|
4545
|
+
added.push(m);
|
|
4546
|
+
continue;
|
|
4547
|
+
}
|
|
4548
|
+
if (!DIRECT_PROVIDERS.has(m.provider)) continue;
|
|
4549
|
+
if (isSnapshotOfListed(m)) continue;
|
|
4550
|
+
const brands = trackedBrands.get(m.provider);
|
|
4551
|
+
if (!brands?.has(brandToken(m.model))) continue;
|
|
4552
|
+
if (PRICING[catalogueKey(m)]) continue;
|
|
4553
|
+
added.push(m);
|
|
4554
|
+
}
|
|
4555
|
+
const missingPrice = [];
|
|
4556
|
+
for (const spec of Object.values(DEFAULT_TIER_MAP)) {
|
|
4557
|
+
if (!getPrice(spec.provider, spec.model)) missingPrice.push(`${spec.provider}:${spec.model}`);
|
|
4558
|
+
}
|
|
4559
|
+
const priceChanged = [];
|
|
4560
|
+
const removedUpstream = [];
|
|
4561
|
+
for (const [key, entry] of Object.entries(PRICING)) {
|
|
4562
|
+
const [provider] = splitKey(key);
|
|
4563
|
+
if (!fetchedProviders.has(provider)) continue;
|
|
4564
|
+
const up = fetchedByKey.get(key);
|
|
4565
|
+
if (!up) {
|
|
4566
|
+
removedUpstream.push(key);
|
|
4567
|
+
continue;
|
|
4568
|
+
}
|
|
4569
|
+
if (up.inputPer1M === void 0 && up.outputPer1M === void 0) continue;
|
|
4570
|
+
const inDrift = relDrift(entry.inputPer1M, up.inputPer1M);
|
|
4571
|
+
const outDrift = relDrift(entry.outputPer1M, up.outputPer1M);
|
|
4572
|
+
if (inDrift > threshold || outDrift > threshold) {
|
|
4573
|
+
priceChanged.push({
|
|
4574
|
+
key,
|
|
4575
|
+
ourInputPer1M: entry.inputPer1M,
|
|
4576
|
+
ourOutputPer1M: entry.outputPer1M,
|
|
4577
|
+
...up.inputPer1M !== void 0 ? { upstreamInputPer1M: up.inputPer1M } : {},
|
|
4578
|
+
...up.outputPer1M !== void 0 ? { upstreamOutputPer1M: up.outputPer1M } : {}
|
|
4579
|
+
});
|
|
4580
|
+
}
|
|
4581
|
+
}
|
|
4582
|
+
return { added, missingPrice, priceChanged, removedUpstream };
|
|
4583
|
+
}
|
|
4584
|
+
function relDrift(ours, upstream) {
|
|
4585
|
+
if (upstream === void 0) return 0;
|
|
4586
|
+
if (upstream === 0) return ours === 0 ? 0 : Infinity;
|
|
4587
|
+
return Math.abs(ours - upstream) / upstream;
|
|
4588
|
+
}
|
|
4589
|
+
function splitKey(key) {
|
|
4590
|
+
const i = key.indexOf(":");
|
|
4591
|
+
return [key.slice(0, i), key.slice(i + 1)];
|
|
4592
|
+
}
|
|
4593
|
+
|
|
4594
|
+
// src/catalogue/price-drift.ts
|
|
4595
|
+
var COVERAGE = "OpenRouter catalogue only. Not covered: direct-provider prices (no price API) and speech/image/video/OCR (hand-checked, F050).";
|
|
4596
|
+
function pct(ours, up) {
|
|
4597
|
+
if (up === void 0) return void 0;
|
|
4598
|
+
if (ours === 0) return up === 0 ? 0 : Infinity;
|
|
4599
|
+
return Math.round((up - ours) / ours * 1e3) / 10;
|
|
4600
|
+
}
|
|
4601
|
+
async function checkPriceDrift(opts = {}) {
|
|
4602
|
+
const measuredAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4603
|
+
try {
|
|
4604
|
+
const models = await fetchOpenRouterCatalogue({ fetch: opts.fetch });
|
|
4605
|
+
if (models.length === 0) return { ok: false, measuredAt, error: "OpenRouter returned an empty catalogue" };
|
|
4606
|
+
const diff = diffCatalogue(models, { fetchedProviders: ["openrouter"], priceThreshold: opts.threshold ?? 0.01 });
|
|
4607
|
+
const drift = diff.priceChanged.map((p) => {
|
|
4608
|
+
const i = p.key.indexOf(":");
|
|
4609
|
+
return {
|
|
4610
|
+
key: p.key,
|
|
4611
|
+
provider: p.key.slice(0, i),
|
|
4612
|
+
model: p.key.slice(i + 1),
|
|
4613
|
+
ours: { inputPer1M: p.ourInputPer1M, outputPer1M: p.ourOutputPer1M },
|
|
4614
|
+
upstream: { inputPer1M: p.upstreamInputPer1M, outputPer1M: p.upstreamOutputPer1M },
|
|
4615
|
+
inputChangePct: pct(p.ourInputPer1M, p.upstreamInputPer1M),
|
|
4616
|
+
outputChangePct: pct(p.ourOutputPer1M, p.upstreamOutputPer1M)
|
|
4617
|
+
};
|
|
4618
|
+
});
|
|
4619
|
+
const checked = models.filter((m) => m.inputPer1M !== void 0 || m.outputPer1M !== void 0).length;
|
|
4620
|
+
return { ok: true, measuredAt, source: "openrouter", checked, drift, coverage: COVERAGE };
|
|
4621
|
+
} catch (e) {
|
|
4622
|
+
return { ok: false, measuredAt, error: e instanceof Error ? e.message : String(e) };
|
|
4623
|
+
}
|
|
4624
|
+
}
|
|
4625
|
+
|
|
4435
4626
|
// src/providers/stub.ts
|
|
4436
4627
|
function stubUsage(provider, model, transport, capability) {
|
|
4437
4628
|
return {
|
|
@@ -4874,6 +5065,7 @@ export {
|
|
|
4874
5065
|
bflCredits,
|
|
4875
5066
|
byokAdapter,
|
|
4876
5067
|
chatInputSchema,
|
|
5068
|
+
checkPriceDrift,
|
|
4877
5069
|
checkVoice,
|
|
4878
5070
|
classifyRegionName,
|
|
4879
5071
|
computeCost,
|