@broberg/ai-sdk 0.53.0 → 0.54.0

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.
@@ -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-XDBN6QJ4.js.map
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-ZSLRCOYM.js.map
480
+ //# sourceMappingURL=chunk-TSNJ5N4F.js.map
package/dist/index.d.ts CHANGED
@@ -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.53.0";
2906
- declare const SDK_TAG: "@broberg/ai-sdk@0.53.0";
2942
+ declare const VERSION: "0.54.0";
2943
+ declare const SDK_TAG: "@broberg/ai-sdk@0.54.0";
2907
2944
 
2908
2945
  /** Built-in defaults. Every entry is overridable via AiConfig.defaults or a
2909
2946
  * per-call override.
@@ -3414,4 +3451,4 @@ interface StreamTransportRequest extends TransportRequest {
3414
3451
  */
3415
3452
  declare function streamTransport(req: StreamTransportRequest): AsyncIterable<string>;
3416
3453
 
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 };
3454
+ 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-XDBN6QJ4.js";
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-ZSLRCOYM.js";
24
+ } from "./chunk-TSNJ5N4F.js";
22
25
 
23
26
  // src/cost/default-hosts.ts
24
27
  var DEFAULT_BASE_URLS = {
@@ -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 = (per1M) => per1M / 1e6;
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;
@@ -3523,8 +3526,8 @@ var aiConfigSchema = z.object({
3523
3526
  import { randomUUID } from "crypto";
3524
3527
 
3525
3528
  // src/version.ts
3526
- var VERSION = "0.53.0";
3527
- var SDK_TAG = "@broberg/ai-sdk@0.53.0";
3529
+ var VERSION = "0.54.0";
3530
+ var SDK_TAG = "@broberg/ai-sdk@0.54.0";
3528
3531
 
3529
3532
  // src/cost/sinks/upmetrics.ts
3530
3533
  function isRetryableStatus(status) {
@@ -3871,6 +3874,21 @@ var DEFAULT_JUDGE_SPEC = { provider: "typesafe", model: "jev-latest", transport:
3871
3874
  var DEFAULT_PODCAST_SPEC = { provider: "elevenlabs", model: "eleven_v3", transport: "http" };
3872
3875
  var DEFAULT_TTS_SPEC = { provider: "elevenlabs", model: "eleven_multilingual_v2", transport: "http" };
3873
3876
  var DEFAULT_BATCH_SPEC = { provider: "mistral", model: "mistral-small-latest", transport: "http" };
3877
+ var ROUTED_SPECS = [
3878
+ DEFAULT_IMAGE_SPEC,
3879
+ DEFAULT_LORA_IMAGE_SPEC,
3880
+ DEFAULT_TRAINSTYLE_SPEC,
3881
+ DEFAULT_ANIMATE_SPEC,
3882
+ DEFAULT_BFL_FINETUNE_SPEC,
3883
+ DEFAULT_BFL_REFERENCE_SPEC,
3884
+ DEFAULT_OCR_SPEC,
3885
+ DEFAULT_MODERATION_SPEC,
3886
+ DEFAULT_JUDGE_SPEC,
3887
+ DEFAULT_PODCAST_SPEC,
3888
+ DEFAULT_TTS_SPEC,
3889
+ DEFAULT_BATCH_SPEC,
3890
+ DEFAULT_TRANSCRIBE_SPEC
3891
+ ];
3874
3892
  function defaultCostSink() {
3875
3893
  const apiKey = process.env.UPMETRICS_API_KEY;
3876
3894
  if (!apiKey) return void 0;
@@ -4432,6 +4450,175 @@ function createAI(config = {}) {
4432
4450
  return client;
4433
4451
  }
4434
4452
 
4453
+ // src/catalogue/fetchers.ts
4454
+ async function getJson(f, url, headers) {
4455
+ const res = await f(url, { headers: { accept: "application/json", ...headers } });
4456
+ if (!res.ok) {
4457
+ const body = await res.text().catch(() => "");
4458
+ throw new Error(`${res.status} ${body.slice(0, 200)}`);
4459
+ }
4460
+ return res.json();
4461
+ }
4462
+ function per1M(v) {
4463
+ if (v == null) return void 0;
4464
+ const n = Number(v) * 1e6;
4465
+ return Number.isFinite(n) && n >= 0 ? n : void 0;
4466
+ }
4467
+ async function fetchOpenRouterCatalogue(opts = {}) {
4468
+ const f = opts.fetch ?? fetch;
4469
+ const base = opts.baseUrl ?? "https://openrouter.ai/api/v1";
4470
+ const json = await getJson(f, `${base}/models`, {});
4471
+ return (json.data ?? []).map((m) => {
4472
+ const inputPer1M = per1M(m.pricing?.prompt);
4473
+ const outputPer1M = per1M(m.pricing?.completion);
4474
+ return {
4475
+ provider: "openrouter",
4476
+ model: m.id,
4477
+ ...inputPer1M !== void 0 ? { inputPer1M } : {},
4478
+ ...outputPer1M !== void 0 ? { outputPer1M } : {},
4479
+ ...m.context_length ? { contextLength: m.context_length } : {}
4480
+ };
4481
+ });
4482
+ }
4483
+
4484
+ // src/catalogue/types.ts
4485
+ function catalogueKey(m) {
4486
+ return `${m.provider}:${m.model}`;
4487
+ }
4488
+
4489
+ // src/catalogue/diff.ts
4490
+ function brandToken(model) {
4491
+ const tail = model.split("/").pop() ?? model;
4492
+ return tail.split("-")[0] ?? tail;
4493
+ }
4494
+ var DIRECT_PROVIDERS = /* @__PURE__ */ new Set(["openai", "anthropic", "gemini"]);
4495
+ var FULL_LIST_PROVIDERS = /* @__PURE__ */ new Set(["mistral", "deepseek", "elevenlabs"]);
4496
+ function undatedBase(id) {
4497
+ return id.replace(/-(\d{4}-\d{2}-\d{2}|\d{8}|\d{4})$/, "");
4498
+ }
4499
+ function knownKeys() {
4500
+ const known = /* @__PURE__ */ new Set([...Object.keys(PRICING), ...Object.keys(MEDIA_PRICING)]);
4501
+ for (const e of allEntries()) known.add(`${e.provider}:${e.id}`);
4502
+ for (const spec of [...Object.values(DEFAULT_TIER_MAP), ...ROUTED_SPECS]) known.add(`${spec.provider}:${spec.model}`);
4503
+ return known;
4504
+ }
4505
+ function diffCatalogue(fetched, opts = {}) {
4506
+ const threshold = opts.priceThreshold ?? 0.01;
4507
+ const fetchedProviders = new Set(opts.fetchedProviders ?? fetched.map((m) => m.provider));
4508
+ const fetchedByKey = /* @__PURE__ */ new Map();
4509
+ for (const m of fetched) {
4510
+ fetchedByKey.set(catalogueKey(m), m);
4511
+ for (const a of m.aliases ?? []) {
4512
+ const k = catalogueKey({ provider: m.provider, model: a });
4513
+ if (!fetchedByKey.has(k)) fetchedByKey.set(k, m);
4514
+ }
4515
+ }
4516
+ const known = knownKeys();
4517
+ const trackedBrands = /* @__PURE__ */ new Map();
4518
+ for (const key of Object.keys(PRICING)) {
4519
+ const [provider, model] = splitKey(key);
4520
+ if (!trackedBrands.has(provider)) trackedBrands.set(provider, /* @__PURE__ */ new Set());
4521
+ trackedBrands.get(provider).add(brandToken(model));
4522
+ }
4523
+ const added = [];
4524
+ const reportedGroups = /* @__PURE__ */ new Set();
4525
+ const listedByProvider = /* @__PURE__ */ new Map();
4526
+ for (const m of fetched) {
4527
+ if (!listedByProvider.has(m.provider)) listedByProvider.set(m.provider, /* @__PURE__ */ new Set());
4528
+ listedByProvider.get(m.provider).add(m.model);
4529
+ }
4530
+ const isSnapshotOfListed = (m) => {
4531
+ const base = undatedBase(m.model);
4532
+ if (base === m.model) return false;
4533
+ return listedByProvider.get(m.provider)?.has(base) === true || known.has(catalogueKey({ provider: m.provider, model: base }));
4534
+ };
4535
+ for (const m of fetched) {
4536
+ if (FULL_LIST_PROVIDERS.has(m.provider)) {
4537
+ if (m.deprecated) continue;
4538
+ const group = [m.model, ...m.aliases ?? []].map((id) => catalogueKey({ provider: m.provider, model: id }));
4539
+ if (group.some((k) => known.has(k) || reportedGroups.has(k))) continue;
4540
+ for (const k of group) reportedGroups.add(k);
4541
+ added.push(m);
4542
+ continue;
4543
+ }
4544
+ if (!DIRECT_PROVIDERS.has(m.provider)) continue;
4545
+ if (isSnapshotOfListed(m)) continue;
4546
+ const brands = trackedBrands.get(m.provider);
4547
+ if (!brands?.has(brandToken(m.model))) continue;
4548
+ if (PRICING[catalogueKey(m)]) continue;
4549
+ added.push(m);
4550
+ }
4551
+ const missingPrice = [];
4552
+ for (const spec of Object.values(DEFAULT_TIER_MAP)) {
4553
+ if (!getPrice(spec.provider, spec.model)) missingPrice.push(`${spec.provider}:${spec.model}`);
4554
+ }
4555
+ const priceChanged = [];
4556
+ const removedUpstream = [];
4557
+ for (const [key, entry] of Object.entries(PRICING)) {
4558
+ const [provider] = splitKey(key);
4559
+ if (!fetchedProviders.has(provider)) continue;
4560
+ const up = fetchedByKey.get(key);
4561
+ if (!up) {
4562
+ removedUpstream.push(key);
4563
+ continue;
4564
+ }
4565
+ if (up.inputPer1M === void 0 && up.outputPer1M === void 0) continue;
4566
+ const inDrift = relDrift(entry.inputPer1M, up.inputPer1M);
4567
+ const outDrift = relDrift(entry.outputPer1M, up.outputPer1M);
4568
+ if (inDrift > threshold || outDrift > threshold) {
4569
+ priceChanged.push({
4570
+ key,
4571
+ ourInputPer1M: entry.inputPer1M,
4572
+ ourOutputPer1M: entry.outputPer1M,
4573
+ ...up.inputPer1M !== void 0 ? { upstreamInputPer1M: up.inputPer1M } : {},
4574
+ ...up.outputPer1M !== void 0 ? { upstreamOutputPer1M: up.outputPer1M } : {}
4575
+ });
4576
+ }
4577
+ }
4578
+ return { added, missingPrice, priceChanged, removedUpstream };
4579
+ }
4580
+ function relDrift(ours, upstream) {
4581
+ if (upstream === void 0) return 0;
4582
+ if (upstream === 0) return ours === 0 ? 0 : Infinity;
4583
+ return Math.abs(ours - upstream) / upstream;
4584
+ }
4585
+ function splitKey(key) {
4586
+ const i = key.indexOf(":");
4587
+ return [key.slice(0, i), key.slice(i + 1)];
4588
+ }
4589
+
4590
+ // src/catalogue/price-drift.ts
4591
+ var COVERAGE = "OpenRouter catalogue only. Not covered: direct-provider prices (no price API) and speech/image/video/OCR (hand-checked, F050).";
4592
+ function pct(ours, up) {
4593
+ if (up === void 0) return void 0;
4594
+ if (ours === 0) return up === 0 ? 0 : Infinity;
4595
+ return Math.round((up - ours) / ours * 1e3) / 10;
4596
+ }
4597
+ async function checkPriceDrift(opts = {}) {
4598
+ const measuredAt = (/* @__PURE__ */ new Date()).toISOString();
4599
+ try {
4600
+ const models = await fetchOpenRouterCatalogue({ fetch: opts.fetch });
4601
+ if (models.length === 0) return { ok: false, measuredAt, error: "OpenRouter returned an empty catalogue" };
4602
+ const diff = diffCatalogue(models, { fetchedProviders: ["openrouter"], priceThreshold: opts.threshold ?? 0.01 });
4603
+ const drift = diff.priceChanged.map((p) => {
4604
+ const i = p.key.indexOf(":");
4605
+ return {
4606
+ key: p.key,
4607
+ provider: p.key.slice(0, i),
4608
+ model: p.key.slice(i + 1),
4609
+ ours: { inputPer1M: p.ourInputPer1M, outputPer1M: p.ourOutputPer1M },
4610
+ upstream: { inputPer1M: p.upstreamInputPer1M, outputPer1M: p.upstreamOutputPer1M },
4611
+ inputChangePct: pct(p.ourInputPer1M, p.upstreamInputPer1M),
4612
+ outputChangePct: pct(p.ourOutputPer1M, p.upstreamOutputPer1M)
4613
+ };
4614
+ });
4615
+ const checked = models.filter((m) => m.inputPer1M !== void 0 || m.outputPer1M !== void 0).length;
4616
+ return { ok: true, measuredAt, source: "openrouter", checked, drift, coverage: COVERAGE };
4617
+ } catch (e) {
4618
+ return { ok: false, measuredAt, error: e instanceof Error ? e.message : String(e) };
4619
+ }
4620
+ }
4621
+
4435
4622
  // src/providers/stub.ts
4436
4623
  function stubUsage(provider, model, transport, capability) {
4437
4624
  return {
@@ -4874,6 +5061,7 @@ export {
4874
5061
  bflCredits,
4875
5062
  byokAdapter,
4876
5063
  chatInputSchema,
5064
+ checkPriceDrift,
4877
5065
  checkVoice,
4878
5066
  classifyRegionName,
4879
5067
  computeCost,