@broberg/ai-sdk 0.46.0 → 0.47.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/README.md CHANGED
@@ -146,6 +146,40 @@ personal data by default; override per call for an even cheaper non-personal rou
146
146
  (The `claude -p` subprocess transport is still available via explicit
147
147
  `override: { transport: "subprocess" }`, but is no longer a default route.)
148
148
 
149
+ ## Where WOULD this go? — `wouldRouteTo` (a forecast, not a fact)
150
+
151
+ `usage.region` answers **where a call went**. It cannot answer *may I send this?*, because
152
+ it only exists once the bytes have left. `wouldRouteTo` answers that half:
153
+
154
+ ```ts
155
+ import { wouldRouteTo } from "@broberg/ai-sdk";
156
+
157
+ const f = wouldRouteTo("smart");
158
+ // { kind: "forecast", provider: "mistral", host: "https://api.mistral.ai/v1",
159
+ // wouldRouteTo: "eu", onlyIf: [ …the assumptions… ] }
160
+ ```
161
+
162
+ **It is deliberately not shaped like `usage.region`.** There is no field called `region` on
163
+ it, and that is enforced by a test: a forecast that can be wrong must not wear the clothes
164
+ of a fact that cannot. `onlyIf` comes back **with** the answer rather than living in these
165
+ docs — the answer holds only while you override no `baseUrl` and pass no fallback, because
166
+ **a fallback IS a route and the route decides residency**.
167
+
168
+ **`"depends-on-config"` is an answer, not a failure.** `azure`, `vertex`, `deepl`, `requesty`
169
+ and `fal` build their host from values you supply. `requesty` is why this state exists: it
170
+ has *both* an EU and a non-EU host, so any region we named there would be a residency claim
171
+ decided by a table instead of by a route.
172
+
173
+ **It refuses nothing.** It never throws and never blocks a call — not even for a nonsense
174
+ provider. Residency policy belongs to each consumer, not to this package; this is the
175
+ information that makes that decision possible, which is the opposite of a gate.
176
+
177
+ **Why it can say `"eu"` where `regionOfProvider` says `"unknown"`:** it asks the *host* the
178
+ SDK would actually use, not the provider's *name*. `regionOfProvider("mistral")` is
179
+ `"unknown"` by design — that adapter takes a `baseUrl`. Filed by a consumer who had
180
+ hand-copied our tier table into their own repo to answer this; that copy can now be deleted
181
+ instead of maintained.
182
+
149
183
  ## Cost, budget & sinks
150
184
 
151
185
  ```ts
package/dist/index.d.ts CHANGED
@@ -2271,6 +2271,24 @@ declare function createAI(config?: AiConfig): AiClient;
2271
2271
  /** Pull the first JSON value out of a model reply (tolerates ```json fences + prose). */
2272
2272
  declare function parseJsonLoose(text: string): unknown;
2273
2273
  type ChatVision = Pick<AiClient, "chat" | "vision">;
2274
+ /** F052.2 — resolve the model's answer to one of the CALLER's labels, or to null.
2275
+ *
2276
+ * Reported by helpdesk from a live customer ticket (BR-2WYHD, 15 September 2026). They
2277
+ * pass labels carrying their own description — `"virker-ikke — noget er i stykker…"` —
2278
+ * and the model answered `"virker-ikke"`, the canonical short form. That IS in their
2279
+ * taxonomy. Exact equality threw a correct answer away, and it failed in the green
2280
+ * direction: `label: null` reads as "the model could not decide", a legitimate outcome
2281
+ * nobody investigates. The gradient runs TOWARD the bug — the better a consumer fills in
2282
+ * the description, the longer the label, the likelier the model answers with the head.
2283
+ *
2284
+ * NO SEPARATOR PARAMETER, deliberately. helpdesk proposed splitting on a separator and
2285
+ * flagged themselves that hardcoding an em-dash just moves the cliff. A PREFIX needs no
2286
+ * separator at all: "virker-ikke" is a prefix of the decorated label, whatever joins them.
2287
+ *
2288
+ * AND AMBIGUITY MUST STAY null. If the answer prefixes TWO labels ("betaling" against
2289
+ * "betaling — kort" and "betaling — faktura"), picking one would turn a discarded correct
2290
+ * answer into a confident wrong one — a downgrade wearing a fix's clothes. */
2291
+ declare function matchLabel(answer: unknown, labels: string[]): string | null;
2274
2292
  declare function makeContracts(client: ChatVision): Contracts;
2275
2293
 
2276
2294
  /** Convert SDK tools to a provider's request format. */
@@ -2534,8 +2552,8 @@ declare const falStubAdapter: ProviderAdapter;
2534
2552
  * wires the live adapters. */
2535
2553
  declare const stubProviders: Record<string, ProviderAdapter>;
2536
2554
 
2537
- declare const VERSION: "0.46.0";
2538
- declare const SDK_TAG: "@broberg/ai-sdk@0.46.0";
2555
+ declare const VERSION: "0.47.1";
2556
+ declare const SDK_TAG: "@broberg/ai-sdk@0.47.1";
2539
2557
 
2540
2558
  /** Built-in defaults. Every entry is overridable via AiConfig.defaults or a
2541
2559
  * per-call override.
@@ -2881,6 +2899,69 @@ interface PricingEntry {
2881
2899
  }
2882
2900
  declare function getPrice(provider: string, model: string): PricingEntry | undefined;
2883
2901
 
2902
+ /** What a forecast can say. `"depends-on-config"` is NOT a region — it is the honest
2903
+ * answer for a provider whose host you supply part of (azure, vertex, deepl, requesty,
2904
+ * fal). `requesty` is why it exists: it has both an EU and a non-EU host, so a table
2905
+ * answering there would be a residency claim decided by a table rather than by a route. */
2906
+ type RouteForecast = {
2907
+ kind: "forecast";
2908
+ /** The provider the tier resolves to today. */
2909
+ provider: string;
2910
+ /** The host the SDK would use if you override nothing. */
2911
+ host: string;
2912
+ /** Region of THAT host. Only `"eu"` is a positive claim — `"unknown"` means we
2913
+ * cannot say, never "probably fine". */
2914
+ wouldRouteTo: Region;
2915
+ /** The assumptions, returned WITH the answer rather than left in the docs. Never
2916
+ * empty: a forecast whose conditions are invisible reads as a fact. */
2917
+ onlyIf: string[];
2918
+ } | {
2919
+ kind: "depends-on-config";
2920
+ provider: string;
2921
+ /** Why no table can answer for this provider. */
2922
+ because: string;
2923
+ onlyIf: string[];
2924
+ } | {
2925
+ kind: "unknown-provider";
2926
+ provider: string;
2927
+ onlyIf: string[];
2928
+ };
2929
+ /** Forecast for a PROVIDER, using the host the SDK would default to. */
2930
+ declare function wouldProviderRouteTo(provider: string): RouteForecast;
2931
+ /** Forecast for a TIER — the question helpdesk actually asked. */
2932
+ declare function wouldRouteTo(tier: Tier): RouteForecast;
2933
+
2934
+ /** Providers whose default endpoint is a FIXED host. */
2935
+ declare const DEFAULT_BASE_URLS: {
2936
+ readonly anthropic: "https://api.anthropic.com";
2937
+ readonly deepinfra: "https://api.deepinfra.com/v1/openai";
2938
+ readonly deepseek: "https://api.deepseek.com/v1";
2939
+ readonly elevenlabs: "https://api.elevenlabs.io/v1";
2940
+ readonly gemini: "https://generativelanguage.googleapis.com/v1beta";
2941
+ readonly mistral: "https://api.mistral.ai/v1";
2942
+ readonly openai: "https://api.openai.com/v1";
2943
+ readonly openrouter: "https://openrouter.ai/api/v1";
2944
+ };
2945
+ type FixedHostProvider = keyof typeof DEFAULT_BASE_URLS;
2946
+ /** `bfl` is DELIBERATELY ABSENT. Its adapter hard-pins `https://api.eu.bfl.ai` in a local
2947
+ * constant and calls that pin the GDPR crux, non-negotiable — faces and portraits go
2948
+ * through it. Hoisting the value here would make a deliberate pin editable from a shared
2949
+ * file that nine other providers also read, which is a worse trade than one more name for
2950
+ * one URL. It is not a drifting copy either: there is exactly one place, it is just not
2951
+ * this one. The scanner below does not flag it because bfl never used the
2952
+ * `baseUrl ?? "https://…"` form it forbids. */
2953
+ declare const LOCALLY_PINNED_HOSTS: Record<string, string>;
2954
+ /** Providers whose host is DERIVED FROM CONFIG, so no table can answer for them.
2955
+ *
2956
+ * Measured 12 September 2026, and this list is the honest half of F056: a flat
2957
+ * "default host per provider" table would answer for these too, and be wrong in the
2958
+ * GREEN direction — it would say something instead of saying it cannot.
2959
+ *
2960
+ * `requesty` is the sharp one: it has BOTH an EU and a non-EU host, so a guess there
2961
+ * would be a residency claim decided by a table rather than by the route. */
2962
+ declare const CONFIG_DERIVED_HOSTS: Record<string, string>;
2963
+ declare function defaultBaseUrl(provider: string): string | undefined;
2964
+
2884
2965
  interface TransportRequest {
2885
2966
  /** Resolved routing for this call (transport field selects the path). */
2886
2967
  spec: TierSpec;
@@ -2946,4 +3027,4 @@ interface StreamTransportRequest extends TransportRequest {
2946
3027
  */
2947
3028
  declare function streamTransport(req: StreamTransportRequest): AsyncIterable<string>;
2948
3029
 
2949
- 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, 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_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 HttpResponse, type ImageInput, type ImageRequest, type ImageResult, 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, 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 UpmetricsSinkConfig, 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, chatInputSchema, checkVoice, classifyRegionName, computeCost, createAI, deepinfraAdapter, deeplAdapter, deepseekAdapter, defaultProviders, discordSink, elevenlabsAdapter, embeddingInputSchema, falAdapter, falStubAdapter, freshUsage, fromProviderToolCall, geminiAdapter, getCostSummary, getPrice, httpTransport, imageInputSchema, listAzureDanishVoices, listVoices, makeContracts, makeOpenAICompatibleAdapter, 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, upmetricsCostClient, upmetricsSink, usdFromMicro, vertexAdapter, visionInputSchema };
3030
+ 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, 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 UpmetricsSinkConfig, 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, chatInputSchema, checkVoice, classifyRegionName, computeCost, createAI, deepinfraAdapter, deeplAdapter, deepseekAdapter, defaultBaseUrl, defaultProviders, discordSink, elevenlabsAdapter, embeddingInputSchema, falAdapter, falStubAdapter, freshUsage, fromProviderToolCall, geminiAdapter, getCostSummary, getPrice, httpTransport, imageInputSchema, 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, upmetricsCostClient, upmetricsSink, usdFromMicro, vertexAdapter, visionInputSchema, wouldProviderRouteTo, wouldRouteTo };
package/dist/index.js CHANGED
@@ -20,6 +20,31 @@ import {
20
20
  pricingGeneratedAt
21
21
  } from "./chunk-OWXXPBKN.js";
22
22
 
23
+ // src/cost/default-hosts.ts
24
+ var DEFAULT_BASE_URLS = {
25
+ anthropic: "https://api.anthropic.com",
26
+ deepinfra: "https://api.deepinfra.com/v1/openai",
27
+ deepseek: "https://api.deepseek.com/v1",
28
+ elevenlabs: "https://api.elevenlabs.io/v1",
29
+ gemini: "https://generativelanguage.googleapis.com/v1beta",
30
+ mistral: "https://api.mistral.ai/v1",
31
+ openai: "https://api.openai.com/v1",
32
+ openrouter: "https://openrouter.ai/api/v1"
33
+ };
34
+ var LOCALLY_PINNED_HOSTS = {
35
+ bfl: "https://api.eu.bfl.ai"
36
+ };
37
+ var CONFIG_DERIVED_HOSTS = {
38
+ azure: "host comes from your region + resource name",
39
+ vertex: "host comes from your region (europe-west1 by default)",
40
+ deepl: "host differs between the free and paid plans",
41
+ requesty: "has BOTH an EU and a non-EU host \u2014 your config decides",
42
+ fal: "uses several hosts depending on the call"
43
+ };
44
+ function defaultBaseUrl(provider) {
45
+ return DEFAULT_BASE_URLS[provider];
46
+ }
47
+
23
48
  // src/transport/http.ts
24
49
  async function httpTransport(req) {
25
50
  if (!req.http) {
@@ -401,7 +426,7 @@ function flattenForSubprocess(messages) {
401
426
  return { prompt: turns.join("\n\n"), system: sys.length ? sys.join("\n") : void 0 };
402
427
  }
403
428
  function anthropicAdapter(config = {}) {
404
- const baseUrl = config.baseUrl ?? "https://api.anthropic.com";
429
+ const baseUrl = config.baseUrl ?? DEFAULT_BASE_URLS.anthropic;
405
430
  const version = config.anthropicVersion ?? "2023-06-01";
406
431
  function buildBody(req) {
407
432
  const system = [];
@@ -892,7 +917,7 @@ function mapFinishReason(reason) {
892
917
 
893
918
  // src/providers/openai.ts
894
919
  function openaiAdapter(config = {}) {
895
- const baseUrl = config.baseUrl ?? "https://api.openai.com/v1";
920
+ const baseUrl = config.baseUrl ?? DEFAULT_BASE_URLS.openai;
896
921
  const base = makeOpenAICompatibleAdapter({ name: "openai", baseUrl, apiKey: config.apiKey });
897
922
  async function embedding(req) {
898
923
  const apiKey = config.apiKey ?? process.env.OPENAI_API_KEY;
@@ -1010,7 +1035,7 @@ function partsFrom(content) {
1010
1035
  });
1011
1036
  }
1012
1037
  function geminiAdapter(config = {}) {
1013
- const baseUrl = config.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta";
1038
+ const baseUrl = config.baseUrl ?? DEFAULT_BASE_URLS.gemini;
1014
1039
  function resolveKey() {
1015
1040
  const apiKey = config.apiKey ?? process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY;
1016
1041
  if (!apiKey) throw new Error("gemini adapter: API key not set (env GOOGLE_API_KEY or GEMINI_API_KEY)");
@@ -1239,14 +1264,14 @@ function mapGeminiFinish(reason) {
1239
1264
  function deepinfraAdapter(config = {}) {
1240
1265
  return makeOpenAICompatibleAdapter({
1241
1266
  name: "deepinfra",
1242
- baseUrl: config.baseUrl ?? "https://api.deepinfra.com/v1/openai",
1267
+ baseUrl: config.baseUrl ?? DEFAULT_BASE_URLS.deepinfra,
1243
1268
  apiKey: config.apiKey
1244
1269
  });
1245
1270
  }
1246
1271
 
1247
1272
  // src/providers/openrouter.ts
1248
1273
  function openrouterAdapter(config = {}) {
1249
- const baseUrl = config.baseUrl ?? "https://openrouter.ai/api/v1";
1274
+ const baseUrl = config.baseUrl ?? DEFAULT_BASE_URLS.openrouter;
1250
1275
  const headers = {
1251
1276
  "HTTP-Referer": config.referer ?? "https://broberg.ai",
1252
1277
  "X-Title": config.title ?? "@broberg/ai-sdk"
@@ -1328,7 +1353,7 @@ function deepseekAdapter(config = {}) {
1328
1353
  return makeOpenAICompatibleAdapter({
1329
1354
  name: "deepseek",
1330
1355
  // → key DEEPSEEK_API_KEY
1331
- baseUrl: config.baseUrl ?? "https://api.deepseek.com/v1",
1356
+ baseUrl: config.baseUrl ?? DEFAULT_BASE_URLS.deepseek,
1332
1357
  apiKey: config.apiKey,
1333
1358
  // Direct API returns no usage.cost → price from the table (not response).
1334
1359
  costFromResponseField: false
@@ -1337,7 +1362,7 @@ function deepseekAdapter(config = {}) {
1337
1362
 
1338
1363
  // src/providers/mistral.ts
1339
1364
  function mistralAdapter(config = {}) {
1340
- const baseUrl = config.baseUrl ?? "https://api.mistral.ai/v1";
1365
+ const baseUrl = config.baseUrl ?? DEFAULT_BASE_URLS.mistral;
1341
1366
  const base = makeOpenAICompatibleAdapter({ name: "mistral", baseUrl, apiKey: config.apiKey, supportsPromptCacheKey: true, supportsPrefix: true });
1342
1367
  function key() {
1343
1368
  const k = config.apiKey ?? process.env.MISTRAL_API_KEY;
@@ -1591,7 +1616,7 @@ function ttsText(req) {
1591
1616
  return applyPronunciations(req.text, req.pronunciations, (p) => p.alias);
1592
1617
  }
1593
1618
  function elevenlabsAdapter(config = {}) {
1594
- const baseUrl = config.baseUrl ?? "https://api.elevenlabs.io/v1";
1619
+ const baseUrl = config.baseUrl ?? DEFAULT_BASE_URLS.elevenlabs;
1595
1620
  const fetchImpl = config.fetch ?? fetch;
1596
1621
  function key() {
1597
1622
  const k = config.apiKey ?? process.env.ELEVENLABS_API_KEY;
@@ -2851,6 +2876,23 @@ function parseJsonLoose(text) {
2851
2876
  const end = Math.max(lastObj, lastArr);
2852
2877
  return JSON.parse(slice.slice(0, end + 1));
2853
2878
  }
2879
+ function matchLabel(answer, labels) {
2880
+ if (typeof answer !== "string") return null;
2881
+ if (labels.includes(answer)) return answer;
2882
+ const norm = (s) => s.trim().replace(/^["'`]|["'`]$/g, "").trim().toLowerCase();
2883
+ const a = norm(answer);
2884
+ if (a.length === 0) return null;
2885
+ const exactish = labels.filter((l) => norm(l) === a);
2886
+ if (exactish.length === 1) return exactish[0];
2887
+ if (exactish.length > 1) return null;
2888
+ const prefixed = labels.filter((l) => {
2889
+ const n = norm(l);
2890
+ if (!n.startsWith(a)) return false;
2891
+ const next = n.charAt(a.length);
2892
+ return next === "" || /\s/.test(next);
2893
+ });
2894
+ return prefixed.length === 1 ? prefixed[0] : null;
2895
+ }
2854
2896
  function makeContracts(client) {
2855
2897
  return {
2856
2898
  async mockup(input) {
@@ -2913,10 +2955,10 @@ ${input.text}`,
2913
2955
  purpose: input.purpose ?? "contract:classify"
2914
2956
  });
2915
2957
  const parsed = parseJsonLoose(res.text);
2916
- const matched = typeof parsed.label === "string" && input.labels.includes(parsed.label);
2958
+ const matched = matchLabel(parsed.label, input.labels);
2917
2959
  return {
2918
- label: matched ? parsed.label : null,
2919
- ...matched ? {} : { rawLabel: typeof parsed.label === "string" ? parsed.label : res.text.slice(0, 200) },
2960
+ label: matched,
2961
+ ...matched !== null ? {} : { rawLabel: typeof parsed.label === "string" ? parsed.label : res.text.slice(0, 200) },
2920
2962
  // 0 is a real confidence; "no confidence reported" is not 0.
2921
2963
  confidence: typeof parsed.confidence === "number" ? parsed.confidence : null,
2922
2964
  usage: res.usage
@@ -3337,8 +3379,8 @@ var aiConfigSchema = z.object({
3337
3379
  });
3338
3380
 
3339
3381
  // src/version.ts
3340
- var VERSION = "0.46.0";
3341
- var SDK_TAG = "@broberg/ai-sdk@0.46.0";
3382
+ var VERSION = "0.47.1";
3383
+ var SDK_TAG = "@broberg/ai-sdk@0.47.1";
3342
3384
 
3343
3385
  // src/cost/sinks/upmetrics.ts
3344
3386
  function upmetricsSink(config) {
@@ -4345,14 +4387,39 @@ function upmetricsCostClient(config) {
4345
4387
  }
4346
4388
  };
4347
4389
  }
4390
+
4391
+ // src/cost/would-route.ts
4392
+ var ASSUMPTIONS = [
4393
+ "you do not set your own baseUrl \u2014 we cannot know where your gateway forwards to",
4394
+ "you pass no fallback: a fallback IS a route, and the route decides residency",
4395
+ "you pass no override that changes the provider"
4396
+ ];
4397
+ function wouldProviderRouteTo(provider) {
4398
+ const onlyIf = [...ASSUMPTIONS];
4399
+ const host = DEFAULT_BASE_URLS[provider] ?? LOCALLY_PINNED_HOSTS[provider];
4400
+ if (host) {
4401
+ return { kind: "forecast", provider, host, wouldRouteTo: regionOfHost(host), onlyIf };
4402
+ }
4403
+ const because = CONFIG_DERIVED_HOSTS[provider];
4404
+ if (because) return { kind: "depends-on-config", provider, because, onlyIf };
4405
+ return { kind: "unknown-provider", provider, onlyIf };
4406
+ }
4407
+ function wouldRouteTo(tier) {
4408
+ const spec = DEFAULT_TIER_MAP[tier];
4409
+ if (!spec) return { kind: "unknown-provider", provider: String(tier), onlyIf: [...ASSUMPTIONS] };
4410
+ return wouldProviderRouteTo(spec.provider);
4411
+ }
4348
4412
  export {
4349
4413
  AZURE_DANISH_VOICES,
4350
4414
  AZURE_DANISH_VOICE_LIST,
4351
4415
  BudgetExceededError,
4352
4416
  BudgetGuard,
4417
+ CONFIG_DERIVED_HOSTS,
4418
+ DEFAULT_BASE_URLS,
4353
4419
  DEFAULT_CLIP_SEC,
4354
4420
  DEFAULT_TIER_MAP,
4355
4421
  ELEVENLABS_DANISH_VOICES,
4422
+ LOCALLY_PINNED_HOSTS,
4356
4423
  MEDIA_PRICING_CHECKED_AT,
4357
4424
  ModelUnavailableError,
4358
4425
  PRICING_STALE_AFTER_DAYS,
@@ -4377,6 +4444,7 @@ export {
4377
4444
  deepinfraAdapter,
4378
4445
  deeplAdapter,
4379
4446
  deepseekAdapter,
4447
+ defaultBaseUrl,
4380
4448
  defaultProviders,
4381
4449
  discordSink,
4382
4450
  elevenlabsAdapter,
@@ -4397,6 +4465,7 @@ export {
4397
4465
  listVoices,
4398
4466
  makeContracts,
4399
4467
  makeOpenAICompatibleAdapter,
4468
+ matchLabel,
4400
4469
  messageSchema,
4401
4470
  mistralAdapter,
4402
4471
  mistralStubAdapter,
@@ -4432,6 +4501,8 @@ export {
4432
4501
  upmetricsSink,
4433
4502
  usdFromMicro,
4434
4503
  vertexAdapter,
4435
- visionInputSchema
4504
+ visionInputSchema,
4505
+ wouldProviderRouteTo,
4506
+ wouldRouteTo
4436
4507
  };
4437
4508
  //# sourceMappingURL=index.js.map