@broberg/ai-sdk 0.17.1 → 0.19.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.
package/dist/index.js CHANGED
@@ -6,6 +6,9 @@ import {
6
6
  resolveModel,
7
7
  setAvailability
8
8
  } from "./chunk-HVZSYNZ5.js";
9
+ import {
10
+ getPrice
11
+ } from "./chunk-V5AQYES2.js";
9
12
 
10
13
  // src/routing/tier-map.ts
11
14
  var DEFAULT_TIER_MAP = {
@@ -214,101 +217,6 @@ function parseArgs(raw) {
214
217
  return {};
215
218
  }
216
219
 
217
- // src/cost/pricing.ts
218
- var V = "2026-06-02";
219
- var MS = "2026-06-04-mistral.ai";
220
- var PRICING = {
221
- // Anthropic (direct API). DEFAULT_TIER_MAP: fast/cheap=haiku, smart/vision=sonnet, powerful=opus.
222
- "anthropic:claude-haiku-4-5": {
223
- inputPer1M: 0.8,
224
- outputPer1M: 4,
225
- cacheReadPer1M: 0.08,
226
- cacheWritePer1M: 1,
227
- version: V
228
- },
229
- "anthropic:claude-sonnet-4-6": {
230
- inputPer1M: 3,
231
- outputPer1M: 15,
232
- cacheReadPer1M: 0.3,
233
- cacheWritePer1M: 3.75,
234
- version: V
235
- },
236
- "anthropic:claude-opus-4-8": {
237
- inputPer1M: 15,
238
- outputPer1M: 75,
239
- cacheReadPer1M: 1.5,
240
- cacheWritePer1M: 18.75,
241
- version: V
242
- },
243
- // OpenAI. embedding default tier = text-embedding-3-small (no output tokens).
244
- "openai:text-embedding-3-small": { inputPer1M: 0.02, outputPer1M: 0, version: V },
245
- "openai:text-embedding-3-large": { inputPer1M: 0.13, outputPer1M: 0, version: V },
246
- "openai:gpt-4o": { inputPer1M: 2.5, outputPer1M: 10, version: V },
247
- "openai:gpt-4o-mini": { inputPer1M: 0.15, outputPer1M: 0.6, version: V },
248
- // Whisper is priced per minute, not per token — not representable here; transcribe
249
- // (F5.6) computes its own cost. Listed as 0 so token-based compute never charges it.
250
- "openai:whisper-1": { inputPer1M: 0, outputPer1M: 0, version: V },
251
- // OpenRouter (meta-router — model slugs include the upstream vendor). Slugs use
252
- // dots (claude-sonnet-4.6) to match OpenRouter's live ids; the dashed forms
253
- // never matched a real call. Caught by the F014 catalogue research.
254
- "openrouter:anthropic/claude-sonnet-4.6": { inputPer1M: 3, outputPer1M: 15, version: V },
255
- // OpenRouter ground-truth $1/$5 — a markup over Anthropic-direct's $0.8/$4
256
- // (the `anthropic:` entry above). Was masked while the slug used dashes.
257
- "openrouter:anthropic/claude-haiku-4.5": { inputPer1M: 1, outputPer1M: 5, version: "2026-06-04" },
258
- "openrouter:google/gemini-2.5-flash": { inputPer1M: 0.3, outputPer1M: 2.5, version: V },
259
- // Ground-truth from OpenRouter /api/v1/models (was a 0.3 estimate; now 0.279).
260
- "openrouter:minimax/minimax-m2.7": {
261
- inputPer1M: 0.279,
262
- outputPer1M: 1.2,
263
- version: "2026-06-04"
264
- },
265
- // DeepSeek V4 (CN-hosted — NOT GDPR-safe; non-personal-data workloads only).
266
- // On 2026-05-22 DeepSeek made the "75% off" promo the permanent official price.
267
- // V4-Pro $0.435/$0.87 is ~34x cheaper than GPT-5.5 on output; flash is cheaper
268
- // still. Numbers match OpenRouter /api/v1/models 1:1 (no router markup). A strong
269
- // cheap route for fleet background work once `claude -p` is API-billed (15 Jun).
270
- "openrouter:deepseek/deepseek-v4-pro": { inputPer1M: 0.435, outputPer1M: 0.87, version: "2026-05-22-deepseek-official" },
271
- "openrouter:deepseek/deepseek-v4-flash": { inputPer1M: 0.0983, outputPer1M: 0.1966, version: "2026-05-22-deepseek-official" },
272
- // Google Gemini (direct). Provider key is "gemini" — matches the adapter's
273
- // usage.provider + the override.provider callers pass. (Image-gen models are
274
- // priced per-image in the adapter, not here.)
275
- "gemini:gemini-2.5-flash": { inputPer1M: 0.3, outputPer1M: 2.5, version: V },
276
- // flash-lite is the default `video` tier (F019) — cheap native video understanding.
277
- "gemini:gemini-2.5-flash-lite": { inputPer1M: 0.1, outputPer1M: 0.4, version: "2026-06-04-or-xref" },
278
- // Mistral (direct, La Plateforme). Official prices from mistral.ai/pricing
279
- // (2026-06-04, per Christian's CD report). EU/Paris-hosted — the designated
280
- // GDPR-safe provider for client/personal-data workloads (see F015). NB:
281
- // medium-3.5 is the premium "Vibe" coding tier ($1.5/$7.5); Large 3 ($0.5/$1.5)
282
- // is the cheaper frontier general-purpose model despite the higher number.
283
- "mistral:mistral-large-latest": { inputPer1M: 0.5, outputPer1M: 1.5, version: MS },
284
- "mistral:mistral-large-2512": { inputPer1M: 0.5, outputPer1M: 1.5, version: MS },
285
- "mistral:mistral-medium-latest": { inputPer1M: 1.5, outputPer1M: 7.5, version: MS },
286
- "mistral:mistral-medium-3.5": { inputPer1M: 1.5, outputPer1M: 7.5, version: MS },
287
- "mistral:mistral-medium-3": { inputPer1M: 0.4, outputPer1M: 2, version: "2026-06-04-or-xref" },
288
- "mistral:mistral-small-latest": { inputPer1M: 0.1, outputPer1M: 0.3, version: MS },
289
- "mistral:mistral-small-2603": { inputPer1M: 0.1, outputPer1M: 0.3, version: MS },
290
- "mistral:ministral-3b-latest": { inputPer1M: 0.1, outputPer1M: 0.1, version: MS },
291
- "mistral:ministral-8b-latest": { inputPer1M: 0.15, outputPer1M: 0.15, version: MS },
292
- "mistral:ministral-14b-latest": { inputPer1M: 0.2, outputPer1M: 0.2, version: MS },
293
- "mistral:magistral-medium-latest": { inputPer1M: 2, outputPer1M: 5, version: MS },
294
- "mistral:magistral-small-latest": { inputPer1M: 0.5, outputPer1M: 1.5, version: MS },
295
- "mistral:devstral-latest": { inputPer1M: 0.4, outputPer1M: 2, version: MS },
296
- "mistral:codestral-latest": { inputPer1M: 0.3, outputPer1M: 0.9, version: MS },
297
- "mistral:open-mistral-nemo": { inputPer1M: 0.15, outputPer1M: 0.15, version: MS },
298
- // Moderation (F016.4) — per input token; output 0. (OCR is per-page in the adapter.)
299
- "mistral:mistral-moderation-latest": { inputPer1M: 0.1, outputPer1M: 0, version: MS },
300
- // Embeddings (F016.5) — per input token.
301
- "mistral:mistral-embed": { inputPer1M: 0.1, outputPer1M: 0, version: MS },
302
- "mistral:codestral-embed": { inputPer1M: 0.15, outputPer1M: 0, version: MS }
303
- };
304
- function getPrice(provider, model) {
305
- const exact = PRICING[`${provider}:${model}`];
306
- if (exact) return exact;
307
- const base = model.replace(/-\d{8}$/, "");
308
- if (base !== model) return PRICING[`${provider}:${base}`];
309
- return void 0;
310
- }
311
-
312
220
  // src/cost/usage.ts
313
221
  function computeCost(provider, model, inputTokens, outputTokens, cacheReadTokens = 0, cacheCreationTokens = 0) {
314
222
  const price = getPrice(provider, model);
@@ -1395,6 +1303,86 @@ function elevenlabsAdapter(config = {}) {
1395
1303
  return { name: "elevenlabs", dialogue, tts, listVoices };
1396
1304
  }
1397
1305
 
1306
+ // src/providers/azure.ts
1307
+ var AZURE_TTS_PRICE_PER_1K_CHARS = 0.016;
1308
+ var DEFAULT_REGION = "westeurope";
1309
+ var DEFAULT_FORMAT = "audio-24khz-48kbitrate-mono-mp3";
1310
+ var AZURE_DANISH_VOICE_LIST = [
1311
+ { name: "christel", voiceId: "da-DK-ChristelNeural", gender: "female", display: "Christel", native: true, defaultRate: 0.85 },
1312
+ { name: "seraphina", voiceId: "de-DE-SeraphinaMultilingualNeural", gender: "female", display: "Seraphina", native: false },
1313
+ { name: "ava", voiceId: "en-US-AvaMultilingualNeural", gender: "female", display: "Ava", native: false },
1314
+ { name: "jeppe", voiceId: "da-DK-JeppeNeural", gender: "male", display: "Jeppe", native: true },
1315
+ { name: "florian", voiceId: "de-DE-FlorianMultilingualNeural", gender: "male", display: "Florian", native: false },
1316
+ { name: "andrew", voiceId: "en-US-AndrewMultilingualNeural", gender: "male", display: "Andrew", native: false }
1317
+ ];
1318
+ var AZURE_DANISH_VOICES = Object.fromEntries(
1319
+ AZURE_DANISH_VOICE_LIST.map((v) => [v.name, v.voiceId])
1320
+ );
1321
+ function listAzureDanishVoices() {
1322
+ return AZURE_DANISH_VOICE_LIST;
1323
+ }
1324
+ function resolveAzureVoice(nameOrVoice) {
1325
+ return AZURE_DANISH_VOICES[nameOrVoice] ?? nameOrVoice;
1326
+ }
1327
+ function xmlEscape(s) {
1328
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
1329
+ }
1330
+ function localeOf(voice) {
1331
+ const parts = voice.split("-");
1332
+ return parts.length >= 2 ? `${parts[0]}-${parts[1]}` : "en-US";
1333
+ }
1334
+ function azureAdapter(config = {}) {
1335
+ const fetchImpl = config.fetch ?? fetch;
1336
+ function key() {
1337
+ const k = config.apiKey ?? process.env.AZURE_SPEECH_KEY;
1338
+ if (!k) throw new Error("azure adapter: API key not set (env AZURE_SPEECH_KEY)");
1339
+ return k;
1340
+ }
1341
+ function region() {
1342
+ return config.region ?? process.env.AZURE_SPEECH_REGION ?? DEFAULT_REGION;
1343
+ }
1344
+ function priceFor(chars, model) {
1345
+ const usage = freshUsage({
1346
+ provider: "azure",
1347
+ model,
1348
+ transport: "http",
1349
+ capability: "tts",
1350
+ inputTokens: 0,
1351
+ outputTokens: 0
1352
+ });
1353
+ usage.costUsd = chars / 1e3 * (config.pricePer1kChars ?? AZURE_TTS_PRICE_PER_1K_CHARS);
1354
+ return usage;
1355
+ }
1356
+ async function tts(req) {
1357
+ const voice = resolveAzureVoice(req.voiceId);
1358
+ const lang = req.lang ?? localeOf(voice);
1359
+ const format = req.format ?? DEFAULT_FORMAT;
1360
+ const escaped = xmlEscape(req.text);
1361
+ const effRate = req.rate ?? AZURE_DANISH_VOICE_LIST.find((v) => v.voiceId === voice)?.defaultRate;
1362
+ const inner = effRate != null && effRate !== 1 ? `<prosody rate='${effRate}'>${escaped}</prosody>` : escaped;
1363
+ const ssml = `<speak version='1.0' xml:lang='${lang}'><voice name='${voice}'>${inner}</voice></speak>`;
1364
+ const res = await fetchImpl(
1365
+ `https://${region()}.tts.speech.microsoft.com/cognitiveservices/v1`,
1366
+ {
1367
+ method: "POST",
1368
+ headers: {
1369
+ "Ocp-Apim-Subscription-Key": key(),
1370
+ "Content-Type": "application/ssml+xml",
1371
+ "X-Microsoft-OutputFormat": format
1372
+ },
1373
+ body: ssml
1374
+ }
1375
+ );
1376
+ if (!res.ok) {
1377
+ const body = await res.text().catch(() => "");
1378
+ throw new Error(`azure tts ${res.status}: ${body.slice(0, 300)}`);
1379
+ }
1380
+ const audio = new Uint8Array(await res.arrayBuffer());
1381
+ return { audio, mimeType: "audio/mpeg", usage: priceFor(req.text.length, req.spec.model) };
1382
+ }
1383
+ return { name: "azure", tts };
1384
+ }
1385
+
1398
1386
  // src/providers/fal.ts
1399
1387
  import { deflateRawSync, crc32 } from "zlib";
1400
1388
  var FAL_IMAGE_PRICE_ESTIMATE = {
@@ -1822,6 +1810,7 @@ var defaultProviders = {
1822
1810
  openrouter: openrouterAdapter(),
1823
1811
  mistral: mistralAdapter(),
1824
1812
  elevenlabs: elevenlabsAdapter(),
1813
+ azure: azureAdapter(),
1825
1814
  fal: falAdapter(),
1826
1815
  bfl: bflAdapter()
1827
1816
  };
@@ -2206,6 +2195,9 @@ var podcastInputSchema = z.object({
2206
2195
  var ttsInputSchema = z.object({
2207
2196
  text: z.string(),
2208
2197
  voice: z.string(),
2198
+ lang: z.string().optional(),
2199
+ format: z.string().optional(),
2200
+ rate: z.number().positive().optional(),
2209
2201
  ...callOptions
2210
2202
  });
2211
2203
  var budgetSchema = z.object({
@@ -2659,7 +2651,7 @@ function createAI(config = {}) {
2659
2651
  invoke: async (spec) => {
2660
2652
  const adapter = pickProvider(spec.provider);
2661
2653
  if (!adapter.tts) throw new Error(`createAI: provider "${spec.provider}" does not support tts`);
2662
- return adapter.tts({ text: input.text, voiceId: resolveVoice(input.voice), spec });
2654
+ return adapter.tts({ text: input.text, voiceId: resolveVoice(input.voice), lang: input.lang, format: input.format, rate: input.rate, spec });
2663
2655
  }
2664
2656
  });
2665
2657
  },
@@ -2809,8 +2801,8 @@ var stubProviders = {
2809
2801
  };
2810
2802
 
2811
2803
  // src/version.ts
2812
- var VERSION = "0.17.1";
2813
- var SDK_TAG = "@broberg/ai-sdk@0.17.1";
2804
+ var VERSION = "0.19.0";
2805
+ var SDK_TAG = "@broberg/ai-sdk@0.19.0";
2814
2806
 
2815
2807
  // src/availability/refresh.ts
2816
2808
  var NOT_REFRESHED = { refreshed: false, checked: 0, markedUnavailable: [] };
@@ -3083,7 +3075,76 @@ async function getCostSummary(dbPath) {
3083
3075
  }
3084
3076
  return { totalUsd: total?.total ?? 0, byProvider, byCapability };
3085
3077
  }
3078
+
3079
+ // src/cost/upmetrics-read.ts
3080
+ var UpmetricsCostError = class extends Error {
3081
+ status;
3082
+ code;
3083
+ constructor(message, opts) {
3084
+ super(message);
3085
+ this.name = "UpmetricsCostError";
3086
+ this.status = opts?.status;
3087
+ this.code = opts?.code;
3088
+ }
3089
+ };
3090
+ var usdFromMicro = (microUsd) => microUsd / 1e6;
3091
+ function buildQuery(q) {
3092
+ const p = new URLSearchParams();
3093
+ if (q.bucket) p.set("bucket", q.bucket);
3094
+ if (q.from !== void 0) p.set("from", String(q.from));
3095
+ if (q.to !== void 0) p.set("to", String(q.to));
3096
+ if (q.window && q.from === void 0 && q.to === void 0) p.set("window", q.window);
3097
+ if (q.provider) p.set("provider", q.provider);
3098
+ if (q.model) p.set("model", q.model);
3099
+ if (q.tier) p.set("tier", q.tier);
3100
+ if (q.agentName) p.set("agent_name", q.agentName);
3101
+ if (q.transport) p.set("transport", q.transport);
3102
+ if (q.groupBy) p.set("groupBy", q.groupBy);
3103
+ for (const [k, v] of Object.entries(q.tags ?? {})) p.set(`tag.${k}`, v);
3104
+ const s = p.toString();
3105
+ return s ? `?${s}` : "";
3106
+ }
3107
+ function upmetricsCostClient(config) {
3108
+ const doFetch = config.fetch ?? fetch;
3109
+ const base = config.baseUrl.replace(/\/$/, "");
3110
+ async function get(path, query) {
3111
+ let res;
3112
+ try {
3113
+ res = await doFetch(`${base}${path}${query}`, {
3114
+ method: "GET",
3115
+ headers: { "X-Upmetrics-Key": config.apiKey }
3116
+ });
3117
+ } catch (err) {
3118
+ throw new UpmetricsCostError(
3119
+ `upmetricsCostClient: ${path} unreachable: ${err?.message ?? String(err)}`
3120
+ );
3121
+ }
3122
+ if (!res.ok) {
3123
+ let code;
3124
+ const text = await res.text().catch(() => "");
3125
+ try {
3126
+ code = JSON.parse(text).error;
3127
+ } catch {
3128
+ }
3129
+ throw new UpmetricsCostError(
3130
+ `upmetricsCostClient: ${path} returned ${res.status}${code ? ` (${code})` : ""}`,
3131
+ { status: res.status, code }
3132
+ );
3133
+ }
3134
+ return await res.json();
3135
+ }
3136
+ return {
3137
+ summary(query = {}) {
3138
+ return get("/api/cost/summary", buildQuery(query));
3139
+ },
3140
+ timeseries(query = {}) {
3141
+ return get("/api/cost/timeseries", buildQuery(query));
3142
+ }
3143
+ };
3144
+ }
3086
3145
  export {
3146
+ AZURE_DANISH_VOICES,
3147
+ AZURE_DANISH_VOICE_LIST,
3087
3148
  BudgetExceededError,
3088
3149
  BudgetGuard,
3089
3150
  DEFAULT_TIER_MAP,
@@ -3091,11 +3152,13 @@ export {
3091
3152
  ModelUnavailableError,
3092
3153
  SDK_TAG,
3093
3154
  StreamHttpError,
3155
+ UpmetricsCostError,
3094
3156
  VERSION,
3095
3157
  aiConfigSchema,
3096
3158
  anthropicAdapter,
3097
3159
  anthropicApiAdapter,
3098
3160
  anthropicSubprocessAdapter,
3161
+ azureAdapter,
3099
3162
  bflAdapter,
3100
3163
  bflCredits,
3101
3164
  chatInputSchema,
@@ -3115,6 +3178,7 @@ export {
3115
3178
  getPrice,
3116
3179
  httpTransport,
3117
3180
  imageInputSchema,
3181
+ listAzureDanishVoices,
3118
3182
  listModels,
3119
3183
  makeContracts,
3120
3184
  makeOpenAICompatibleAdapter,
@@ -3130,6 +3194,7 @@ export {
3130
3194
  refreshAvailability,
3131
3195
  resetRefreshState,
3132
3196
  resetRegistry,
3197
+ resolveAzureVoice,
3133
3198
  resolveModel,
3134
3199
  resolveTier,
3135
3200
  resolveVoice,
@@ -3142,7 +3207,9 @@ export {
3142
3207
  toProviderTools,
3143
3208
  toolSchema,
3144
3209
  translateInputSchema,
3210
+ upmetricsCostClient,
3145
3211
  upmetricsSink,
3212
+ usdFromMicro,
3146
3213
  visionInputSchema
3147
3214
  };
3148
3215
  //# sourceMappingURL=index.js.map