@broberg/ai-sdk 0.38.0 → 0.40.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/{chunk-QUOZWQ7G.js → chunk-KXTCLLVF.js} +173 -15
- package/dist/chunk-KXTCLLVF.js.map +1 -0
- package/dist/index.d.ts +74 -5
- package/dist/index.js +138 -90
- package/dist/index.js.map +1 -1
- package/dist/pricing.d.ts +84 -4
- package/dist/pricing.js +9 -1
- package/package.json +2 -2
- package/dist/chunk-QUOZWQ7G.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export { AvailabilitySource, AvailabilityStatus, ModelStatus, ModelUnavailableError, ResolveOptions, ResolveResult, listModels, resolveModel } from './registry.js';
|
|
3
|
-
export { PRICING_STALE_AFTER_DAYS, PricingFreshness, pricingFreshness, pricingGeneratedAt } from './pricing.js';
|
|
3
|
+
export { DEFAULT_CLIP_SEC, MEDIA_PRICING_CHECKED_AT, MediaPrice, MediaUnit, PRICING_STALE_AFTER_DAYS, PricingFreshness, UnitFreshness, getMediaPrice, listMediaPrices, pricingFreshness, pricingGeneratedAt } from './pricing.js';
|
|
4
4
|
|
|
5
5
|
/** Coarse data-residency of the endpoint a call went to.
|
|
6
6
|
*
|
|
@@ -39,6 +39,18 @@ declare function classifyRegionName(name: string | undefined): Region;
|
|
|
39
39
|
* is it?" — a narrower question than "where will my call go?". */
|
|
40
40
|
declare function regionOfProvider(provider: string): Region;
|
|
41
41
|
|
|
42
|
+
/** One dictionary entry. `alias` says it differently; `ipa` says it precisely. */
|
|
43
|
+
interface Pronunciation {
|
|
44
|
+
/** The word as it appears in the text. Matched whole-word, case-insensitively. */
|
|
45
|
+
word: string;
|
|
46
|
+
/** Say this instead. Azure `<sub alias>`; ElevenLabs plain substitution. */
|
|
47
|
+
alias?: string;
|
|
48
|
+
/** IPA phonemes. Azure `<phoneme alphabet="ipa" ph>`. ElevenLabs cannot do this. */
|
|
49
|
+
ipa?: string;
|
|
50
|
+
/** Reserved for a future per-entry language switch; carried but not yet emitted. */
|
|
51
|
+
lang?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
42
54
|
/** How a call reaches the model. `http` = provider REST API; `subprocess` = local
|
|
43
55
|
* `claude -p` CLI (Max plan, costUsd 0). */
|
|
44
56
|
type Transport = "http" | "subprocess";
|
|
@@ -147,6 +159,24 @@ interface Usage {
|
|
|
147
159
|
cacheReadTokens: number;
|
|
148
160
|
cacheCreationTokens: number;
|
|
149
161
|
costUsd: number;
|
|
162
|
+
/** How `costUsd` was arrived at (F050). Absent means the same as `"computed"`;
|
|
163
|
+
* it is optional only so that adding it could not break a consumer building a
|
|
164
|
+
* Usage by hand.
|
|
165
|
+
*
|
|
166
|
+
* - `"reported"` — the PROVIDER told us what the call cost (BFL returns credits).
|
|
167
|
+
* - `"computed"` — a price table rate × a quantity we actually measured (tokens).
|
|
168
|
+
* - `"estimated"` — a quantity or a rate was ASSUMED. A video call with no
|
|
169
|
+
* `durationSec` is billed at {@link DEFAULT_CLIP_SEC} seconds regardless of the
|
|
170
|
+
* clip that came back, and fal's per-image rates are fal's own estimates.
|
|
171
|
+
* - `"unpriced"` — we have NO price for this model, so `costUsd` is 0 because we
|
|
172
|
+
* could not answer, not because the call was free. Those two were the same
|
|
173
|
+
* number, and the free one is much rarer than the unknown one.
|
|
174
|
+
*
|
|
175
|
+
* This is a DATA field on purpose. upmetrics distinguishes reported / computed /
|
|
176
|
+
* unpriced / untokened, and it reads fields — a JSDoc note saying "this one is a
|
|
177
|
+
* guess" never reaches it, so an assumed number arrived there indistinguishable
|
|
178
|
+
* from a measured one. super did exactly that and passed it on as measured. */
|
|
179
|
+
costBasis?: "reported" | "computed" | "estimated" | "unpriced";
|
|
150
180
|
toolCalls?: {
|
|
151
181
|
name: string;
|
|
152
182
|
count: number;
|
|
@@ -423,6 +453,7 @@ interface PodcastResult {
|
|
|
423
453
|
mimeType: string;
|
|
424
454
|
usage: Usage;
|
|
425
455
|
}
|
|
456
|
+
|
|
426
457
|
interface TtsRequest {
|
|
427
458
|
text: string;
|
|
428
459
|
voiceId: string;
|
|
@@ -432,6 +463,14 @@ interface TtsRequest {
|
|
|
432
463
|
format?: string;
|
|
433
464
|
/** Speaking-rate multiplier (Azure): 1 = normal, 0.9 = 10% slower, 1.1 = faster. ElevenLabs ignores it. */
|
|
434
465
|
rate?: number;
|
|
466
|
+
/** F051 — pronunciation dictionary. A Danish voice says English jargon wrongly:
|
|
467
|
+
* measured on da-DK, "AI" comes out as the word "aj", "native" as "nativ".
|
|
468
|
+
*
|
|
469
|
+
* Azure renders these as SSML (`<sub alias>` / `<phoneme alphabet="ipa" ph>`);
|
|
470
|
+
* ElevenLabs has no SSML and can only apply `alias`. **This is the CONTROLLED DOOR
|
|
471
|
+
* into SSML** — the substitution happens adapter-side AFTER the text is escaped, so
|
|
472
|
+
* `text` can never inject markup, and `alias`/`ipa` are escaped too. */
|
|
473
|
+
pronunciations?: Pronunciation[];
|
|
435
474
|
spec: TierSpec;
|
|
436
475
|
}
|
|
437
476
|
interface BatchRequestItem {
|
|
@@ -1807,6 +1846,24 @@ declare const ttsInputSchema: z.ZodObject<{
|
|
|
1807
1846
|
labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
1808
1847
|
text: z.ZodString;
|
|
1809
1848
|
voice: z.ZodString;
|
|
1849
|
+
/** F051 — see TtsRequest.pronunciations. The alias/ipa exclusivity is enforced in
|
|
1850
|
+
* the adapter, not here: the message must name the provider that cannot do it. */
|
|
1851
|
+
pronunciations: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
1852
|
+
word: z.ZodString;
|
|
1853
|
+
alias: z.ZodOptional<z.ZodString>;
|
|
1854
|
+
ipa: z.ZodOptional<z.ZodString>;
|
|
1855
|
+
lang: z.ZodOptional<z.ZodString>;
|
|
1856
|
+
}, "strip", z.ZodTypeAny, {
|
|
1857
|
+
word: string;
|
|
1858
|
+
alias?: string | undefined;
|
|
1859
|
+
ipa?: string | undefined;
|
|
1860
|
+
lang?: string | undefined;
|
|
1861
|
+
}, {
|
|
1862
|
+
word: string;
|
|
1863
|
+
alias?: string | undefined;
|
|
1864
|
+
ipa?: string | undefined;
|
|
1865
|
+
lang?: string | undefined;
|
|
1866
|
+
}>, "many">>;
|
|
1810
1867
|
/** F037: voice to use if `voice` is one we know the provider has retired. Without
|
|
1811
1868
|
* it a retired voice throws VoiceUnavailableError rather than reaching the API.
|
|
1812
1869
|
*
|
|
@@ -1838,8 +1895,14 @@ declare const ttsInputSchema: z.ZodObject<{
|
|
|
1838
1895
|
})[] | undefined;
|
|
1839
1896
|
labels?: Record<string, string> | undefined;
|
|
1840
1897
|
format?: string | undefined;
|
|
1841
|
-
voiceFallback?: string | undefined;
|
|
1842
1898
|
lang?: string | undefined;
|
|
1899
|
+
pronunciations?: {
|
|
1900
|
+
word: string;
|
|
1901
|
+
alias?: string | undefined;
|
|
1902
|
+
ipa?: string | undefined;
|
|
1903
|
+
lang?: string | undefined;
|
|
1904
|
+
}[] | undefined;
|
|
1905
|
+
voiceFallback?: string | undefined;
|
|
1843
1906
|
rate?: number | undefined;
|
|
1844
1907
|
}, {
|
|
1845
1908
|
text: string;
|
|
@@ -1858,8 +1921,14 @@ declare const ttsInputSchema: z.ZodObject<{
|
|
|
1858
1921
|
})[] | undefined;
|
|
1859
1922
|
labels?: Record<string, string> | undefined;
|
|
1860
1923
|
format?: string | undefined;
|
|
1861
|
-
voiceFallback?: string | undefined;
|
|
1862
1924
|
lang?: string | undefined;
|
|
1925
|
+
pronunciations?: {
|
|
1926
|
+
word: string;
|
|
1927
|
+
alias?: string | undefined;
|
|
1928
|
+
ipa?: string | undefined;
|
|
1929
|
+
lang?: string | undefined;
|
|
1930
|
+
}[] | undefined;
|
|
1931
|
+
voiceFallback?: string | undefined;
|
|
1863
1932
|
rate?: number | undefined;
|
|
1864
1933
|
}>;
|
|
1865
1934
|
declare const aiConfigSchema: z.ZodObject<{
|
|
@@ -2251,8 +2320,8 @@ declare const falStubAdapter: ProviderAdapter;
|
|
|
2251
2320
|
* wires the live adapters. */
|
|
2252
2321
|
declare const stubProviders: Record<string, ProviderAdapter>;
|
|
2253
2322
|
|
|
2254
|
-
declare const VERSION: "0.
|
|
2255
|
-
declare const SDK_TAG: "@broberg/ai-sdk@0.
|
|
2323
|
+
declare const VERSION: "0.40.0";
|
|
2324
|
+
declare const SDK_TAG: "@broberg/ai-sdk@0.40.0";
|
|
2256
2325
|
|
|
2257
2326
|
/** Built-in defaults. Every entry is overridable via AiConfig.defaults or a
|
|
2258
2327
|
* per-call override.
|
package/dist/index.js
CHANGED
|
@@ -10,11 +10,15 @@ import {
|
|
|
10
10
|
setAvailability
|
|
11
11
|
} from "./chunk-ZFWSLSE7.js";
|
|
12
12
|
import {
|
|
13
|
+
DEFAULT_CLIP_SEC,
|
|
14
|
+
MEDIA_PRICING_CHECKED_AT,
|
|
13
15
|
PRICING_STALE_AFTER_DAYS,
|
|
16
|
+
getMediaPrice,
|
|
14
17
|
getPrice,
|
|
18
|
+
listMediaPrices,
|
|
15
19
|
pricingFreshness,
|
|
16
20
|
pricingGeneratedAt
|
|
17
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-KXTCLLVF.js";
|
|
18
22
|
|
|
19
23
|
// src/transport/http.ts
|
|
20
24
|
async function httpTransport(req) {
|
|
@@ -355,6 +359,11 @@ function freshUsage(args) {
|
|
|
355
359
|
cacheReadTokens,
|
|
356
360
|
cacheCreationTokens,
|
|
357
361
|
costUsd,
|
|
362
|
+
// F050 — say HOW we got the number. A $0 because the model is not in the price
|
|
363
|
+
// table and a $0 because the call was genuinely free were the same value, and the
|
|
364
|
+
// free case is much rarer than the unknown one. Adapters that bill a non-token
|
|
365
|
+
// unit overwrite this with "estimated"/"reported" right after.
|
|
366
|
+
costBasis: args.subprocess ? "computed" : getPrice(args.provider, args.model) ? "computed" : "unpriced",
|
|
358
367
|
latencyMs: 0,
|
|
359
368
|
capability: args.capability,
|
|
360
369
|
ts: ""
|
|
@@ -882,9 +891,6 @@ function mapFinishReason(reason) {
|
|
|
882
891
|
}
|
|
883
892
|
|
|
884
893
|
// src/providers/openai.ts
|
|
885
|
-
var WHISPER_PRICE_PER_MIN = {
|
|
886
|
-
"whisper-1": 6e-3
|
|
887
|
-
};
|
|
888
894
|
function openaiAdapter(config = {}) {
|
|
889
895
|
const baseUrl = config.baseUrl ?? "https://api.openai.com/v1";
|
|
890
896
|
const base = makeOpenAICompatibleAdapter({ name: "openai", baseUrl, apiKey: config.apiKey });
|
|
@@ -945,7 +951,7 @@ function openaiAdapter(config = {}) {
|
|
|
945
951
|
// Whisper is per-minute, not token-priced.
|
|
946
952
|
});
|
|
947
953
|
if (req.durationSec !== void 0) {
|
|
948
|
-
const perMinute =
|
|
954
|
+
const perMinute = getMediaPrice("openai", req.spec.model)?.usd ?? 0;
|
|
949
955
|
usage.costUsd = req.durationSec / 60 * perMinute;
|
|
950
956
|
}
|
|
951
957
|
const result = { text: data.text ?? "", usage };
|
|
@@ -985,17 +991,6 @@ function sniffMime(b) {
|
|
|
985
991
|
}
|
|
986
992
|
|
|
987
993
|
// src/providers/gemini.ts
|
|
988
|
-
var GEMINI_IMAGE_PRICE_PER_IMAGE = {
|
|
989
|
-
"gemini-2.5-flash-image": 0.039,
|
|
990
|
-
// "nano-banana" — 1024px = 1290 tok
|
|
991
|
-
"gemini-3.1-flash-image": 0.067,
|
|
992
|
-
// 1K=$0.067, 2K=$0.101, 4K=$0.151
|
|
993
|
-
"gemini-3.1-flash-image-preview": 0.067,
|
|
994
|
-
"gemini-3-pro-image": 0.134,
|
|
995
|
-
// premium — 1K/2K=$0.134, 4K=$0.24
|
|
996
|
-
"gemini-3-pro-image-preview": 0.134
|
|
997
|
-
// was $0.039 — wrong (that's the flash price); pro is $0.134
|
|
998
|
-
};
|
|
999
994
|
function splitCached(meta) {
|
|
1000
995
|
const prompt = meta?.promptTokenCount ?? 0;
|
|
1001
996
|
const cached = meta?.cachedContentTokenCount;
|
|
@@ -1014,16 +1009,6 @@ function partsFrom(content) {
|
|
|
1014
1009
|
return { inlineData: { mimeType: p.mimeType ?? "image/png", data } };
|
|
1015
1010
|
});
|
|
1016
1011
|
}
|
|
1017
|
-
var VEO_PRICE_PER_SEC = {
|
|
1018
|
-
"veo-3.1-generate-preview": 0.4,
|
|
1019
|
-
// standard; 4K = 0.60
|
|
1020
|
-
"veo-3.1-fast-generate-preview": 0.1,
|
|
1021
|
-
// 720p; 1080p = 0.12, 4K = 0.30
|
|
1022
|
-
"veo-3.1-lite-generate-preview": 0.05,
|
|
1023
|
-
// 720p; 1080p = 0.08
|
|
1024
|
-
"veo-3.0-generate-001": 0.4,
|
|
1025
|
-
"veo-3.0-fast-generate-001": 0.1
|
|
1026
|
-
};
|
|
1027
1012
|
function geminiAdapter(config = {}) {
|
|
1028
1013
|
const baseUrl = config.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta";
|
|
1029
1014
|
function resolveKey() {
|
|
@@ -1180,7 +1165,7 @@ function geminiAdapter(config = {}) {
|
|
|
1180
1165
|
...splitCached(data.usageMetadata),
|
|
1181
1166
|
outputTokens: data.usageMetadata?.candidatesTokenCount ?? 0
|
|
1182
1167
|
});
|
|
1183
|
-
usage.costUsd = config.pricePerImage ??
|
|
1168
|
+
usage.costUsd = config.pricePerImage ?? getMediaPrice("gemini", req.spec.model)?.usd ?? 0;
|
|
1184
1169
|
return { url, usage };
|
|
1185
1170
|
}
|
|
1186
1171
|
async function animate(req) {
|
|
@@ -1232,8 +1217,9 @@ function geminiAdapter(config = {}) {
|
|
|
1232
1217
|
inputTokens: 0,
|
|
1233
1218
|
outputTokens: 0
|
|
1234
1219
|
});
|
|
1235
|
-
const perSec = config.pricePerSecond ??
|
|
1236
|
-
usage.costUsd = perSec * (req.durationSec ??
|
|
1220
|
+
const perSec = config.pricePerSecond ?? getMediaPrice("gemini", req.spec.model)?.usd ?? 0;
|
|
1221
|
+
usage.costUsd = perSec * (req.durationSec ?? DEFAULT_CLIP_SEC);
|
|
1222
|
+
usage.costBasis = req.durationSec === void 0 ? "estimated" : "computed";
|
|
1237
1223
|
return { url: videoUri, bytes, mimeType: "video/mp4", usage };
|
|
1238
1224
|
}
|
|
1239
1225
|
return { name: "gemini", chat, chatStream, image, animate, vision: chat };
|
|
@@ -1259,10 +1245,6 @@ function deepinfraAdapter(config = {}) {
|
|
|
1259
1245
|
}
|
|
1260
1246
|
|
|
1261
1247
|
// src/providers/openrouter.ts
|
|
1262
|
-
var OPENROUTER_IMAGE_PRICE_ESTIMATE = {
|
|
1263
|
-
"recraft/recraft-v4.1": 0.035,
|
|
1264
|
-
"recraft/recraft-v4.1-vector": 0.08
|
|
1265
|
-
};
|
|
1266
1248
|
function openrouterAdapter(config = {}) {
|
|
1267
1249
|
const baseUrl = config.baseUrl ?? "https://openrouter.ai/api/v1";
|
|
1268
1250
|
const headers = {
|
|
@@ -1317,7 +1299,7 @@ function openrouterAdapter(config = {}) {
|
|
|
1317
1299
|
inputTokens: 0,
|
|
1318
1300
|
outputTokens: 0
|
|
1319
1301
|
});
|
|
1320
|
-
usage.costUsd = data.usage?.cost ?? config.pricePerImage ??
|
|
1302
|
+
usage.costUsd = data.usage?.cost ?? config.pricePerImage ?? getMediaPrice("openrouter", req.spec.model)?.usd ?? 0;
|
|
1321
1303
|
return { url: `data:${first.media_type ?? "image/png"};base64,${first.b64_json}`, usage };
|
|
1322
1304
|
}
|
|
1323
1305
|
return { ...base, image };
|
|
@@ -1354,12 +1336,6 @@ function deepseekAdapter(config = {}) {
|
|
|
1354
1336
|
}
|
|
1355
1337
|
|
|
1356
1338
|
// src/providers/mistral.ts
|
|
1357
|
-
var MISTRAL_OCR_PRICE_PER_PAGE = 2e-3;
|
|
1358
|
-
var VOXTRAL_PRICE_PER_MIN = {
|
|
1359
|
-
"voxtral-mini-latest": 2e-3,
|
|
1360
|
-
"voxtral-mini-2507": 2e-3,
|
|
1361
|
-
"voxtral-mini-2602": 2e-3
|
|
1362
|
-
};
|
|
1363
1339
|
function mistralAdapter(config = {}) {
|
|
1364
1340
|
const baseUrl = config.baseUrl ?? "https://api.mistral.ai/v1";
|
|
1365
1341
|
const base = makeOpenAICompatibleAdapter({ name: "mistral", baseUrl, apiKey: config.apiKey, supportsPromptCacheKey: true, supportsPrefix: true });
|
|
@@ -1401,7 +1377,7 @@ function mistralAdapter(config = {}) {
|
|
|
1401
1377
|
inputTokens: 0,
|
|
1402
1378
|
outputTokens: 0
|
|
1403
1379
|
});
|
|
1404
|
-
usage.costUsd = pagesProcessed * (config.pricePerPage ??
|
|
1380
|
+
usage.costUsd = pagesProcessed * (config.pricePerPage ?? getMediaPrice("mistral", "ocr")?.usd ?? 0);
|
|
1405
1381
|
return { pages, usage };
|
|
1406
1382
|
}
|
|
1407
1383
|
async function moderate(req) {
|
|
@@ -1495,7 +1471,7 @@ function mistralAdapter(config = {}) {
|
|
|
1495
1471
|
outputTokens: 0
|
|
1496
1472
|
});
|
|
1497
1473
|
if (req.durationSec !== void 0) {
|
|
1498
|
-
usage.costUsd = req.durationSec / 60 * (
|
|
1474
|
+
usage.costUsd = req.durationSec / 60 * (getMediaPrice("mistral", req.spec.model)?.usd ?? 0);
|
|
1499
1475
|
}
|
|
1500
1476
|
return { text: data.text ?? "", usage };
|
|
1501
1477
|
}
|
|
@@ -1547,8 +1523,48 @@ function mistralAdapter(config = {}) {
|
|
|
1547
1523
|
return { ...base, ocr, moderate, embedding, transcribe, batchSubmit, batchStatus, batchResults };
|
|
1548
1524
|
}
|
|
1549
1525
|
|
|
1526
|
+
// src/providers/pronunciation.ts
|
|
1527
|
+
function xmlEscape(s) {
|
|
1528
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1529
|
+
}
|
|
1530
|
+
function escapeRegex(s) {
|
|
1531
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1532
|
+
}
|
|
1533
|
+
function assertPronunciations(list, provider) {
|
|
1534
|
+
for (const p of list ?? []) {
|
|
1535
|
+
if (!p.word.trim()) {
|
|
1536
|
+
throw new Error(`${provider} adapter: a pronunciation entry has an empty "word".`);
|
|
1537
|
+
}
|
|
1538
|
+
if (p.alias !== void 0 && p.ipa !== void 0) {
|
|
1539
|
+
throw new Error(
|
|
1540
|
+
`${provider} adapter: pronunciation "${p.word}" sets BOTH alias and ipa. They are different instructions \u2014 alias says it differently, ipa says it precisely. Pick one.`
|
|
1541
|
+
);
|
|
1542
|
+
}
|
|
1543
|
+
if (p.alias === void 0 && p.ipa === void 0) {
|
|
1544
|
+
throw new Error(
|
|
1545
|
+
`${provider} adapter: pronunciation "${p.word}" sets neither alias nor ipa, so there is nothing to say instead.`
|
|
1546
|
+
);
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
function applyPronunciations(haystack, list, render, escape = (s) => s) {
|
|
1551
|
+
const entries = (list ?? []).filter((p) => p.word.trim().length > 0);
|
|
1552
|
+
if (entries.length === 0) return haystack;
|
|
1553
|
+
const sorted = [...entries].sort((a, b) => escape(b.word).length - escape(a.word).length);
|
|
1554
|
+
const byLower = /* @__PURE__ */ new Map();
|
|
1555
|
+
for (const p of sorted) {
|
|
1556
|
+
const k = escape(p.word).toLowerCase();
|
|
1557
|
+
if (!byLower.has(k)) byLower.set(k, p);
|
|
1558
|
+
}
|
|
1559
|
+
const alternation = sorted.map((p) => escapeRegex(escape(p.word))).join("|");
|
|
1560
|
+
const re = new RegExp(`(?<![\\w-])(${alternation})(?![\\w-])`, "gi");
|
|
1561
|
+
return haystack.replace(re, (matched) => {
|
|
1562
|
+
const entry = byLower.get(matched.toLowerCase());
|
|
1563
|
+
return entry ? render(entry, matched) : matched;
|
|
1564
|
+
});
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1550
1567
|
// src/providers/elevenlabs.ts
|
|
1551
|
-
var ELEVENLABS_PRICE_PER_1K_CHARS = 0.15;
|
|
1552
1568
|
var ELEVENLABS_DANISH_VOICES = {
|
|
1553
1569
|
soren: "xj6X4BCUsv9oxohm1E8o",
|
|
1554
1570
|
jesper: "Bl1YwS3uJac5zEOSNESn",
|
|
@@ -1559,6 +1575,17 @@ var ELEVENLABS_DANISH_VOICES = {
|
|
|
1559
1575
|
function resolveVoice(nameOrId) {
|
|
1560
1576
|
return ELEVENLABS_DANISH_VOICES[nameOrId] ?? nameOrId;
|
|
1561
1577
|
}
|
|
1578
|
+
function ttsText(req) {
|
|
1579
|
+
assertPronunciations(req.pronunciations, "elevenlabs");
|
|
1580
|
+
for (const p of req.pronunciations ?? []) {
|
|
1581
|
+
if (p.ipa !== void 0) {
|
|
1582
|
+
throw new Error(
|
|
1583
|
+
`elevenlabs adapter: pronunciation "${p.word}" uses ipa, which needs SSML \u2014 ElevenLabs has none. Use { alias } here, or route this call to azure.`
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
return applyPronunciations(req.text, req.pronunciations, (p) => p.alias);
|
|
1588
|
+
}
|
|
1562
1589
|
function elevenlabsAdapter(config = {}) {
|
|
1563
1590
|
const baseUrl = config.baseUrl ?? "https://api.elevenlabs.io/v1";
|
|
1564
1591
|
const fetchImpl = config.fetch ?? fetch;
|
|
@@ -1576,7 +1603,8 @@ function elevenlabsAdapter(config = {}) {
|
|
|
1576
1603
|
inputTokens: 0,
|
|
1577
1604
|
outputTokens: 0
|
|
1578
1605
|
});
|
|
1579
|
-
usage.costUsd = chars / 1e3 * (config.pricePer1kChars ??
|
|
1606
|
+
usage.costUsd = chars / 1e3 * (config.pricePer1kChars ?? getMediaPrice("elevenlabs", "tts")?.usd ?? 0);
|
|
1607
|
+
usage.costBasis = config.pricePer1kChars !== void 0 ? "computed" : "estimated";
|
|
1580
1608
|
return usage;
|
|
1581
1609
|
}
|
|
1582
1610
|
async function dialogue(req) {
|
|
@@ -1602,7 +1630,7 @@ function elevenlabsAdapter(config = {}) {
|
|
|
1602
1630
|
const res = await fetchImpl(`${baseUrl}/text-to-speech/${req.voiceId}`, {
|
|
1603
1631
|
method: "POST",
|
|
1604
1632
|
headers: { "xi-api-key": key(), "content-type": "application/json", accept: "audio/mpeg" },
|
|
1605
|
-
body: JSON.stringify({ text: req
|
|
1633
|
+
body: JSON.stringify({ text: ttsText(req), model_id: model })
|
|
1606
1634
|
});
|
|
1607
1635
|
if (!res.ok) {
|
|
1608
1636
|
const body = await res.text().catch(() => "");
|
|
@@ -1621,8 +1649,6 @@ function elevenlabsAdapter(config = {}) {
|
|
|
1621
1649
|
}
|
|
1622
1650
|
|
|
1623
1651
|
// src/providers/azure.ts
|
|
1624
|
-
var AZURE_TTS_PRICE_PER_1K_CHARS = 0.016;
|
|
1625
|
-
var AZURE_STT_PRICE_PER_MIN = 0.0167;
|
|
1626
1652
|
var DEFAULT_STT_API_VERSION = "2025-10-15";
|
|
1627
1653
|
var DEFAULT_REGION = "westeurope";
|
|
1628
1654
|
var DEFAULT_FORMAT = "audio-24khz-48kbitrate-mono-mp3";
|
|
@@ -1662,9 +1688,6 @@ function listAzureDanishVoices() {
|
|
|
1662
1688
|
function resolveAzureVoice(nameOrVoice) {
|
|
1663
1689
|
return AZURE_DANISH_VOICES[nameOrVoice] ?? nameOrVoice;
|
|
1664
1690
|
}
|
|
1665
|
-
function xmlEscape(s) {
|
|
1666
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1667
|
-
}
|
|
1668
1691
|
function localeOf(voice) {
|
|
1669
1692
|
const parts = voice.split("-");
|
|
1670
1693
|
return parts.length >= 2 ? `${parts[0]}-${parts[1]}` : "en-US";
|
|
@@ -1703,14 +1726,20 @@ function azureAdapter(config = {}) {
|
|
|
1703
1726
|
inputTokens: 0,
|
|
1704
1727
|
outputTokens: 0
|
|
1705
1728
|
});
|
|
1706
|
-
usage.costUsd = chars / 1e3 * (config.pricePer1kChars ??
|
|
1729
|
+
usage.costUsd = chars / 1e3 * (config.pricePer1kChars ?? getMediaPrice("azure", "tts")?.usd ?? 0);
|
|
1707
1730
|
return usage;
|
|
1708
1731
|
}
|
|
1709
1732
|
async function tts(req) {
|
|
1710
1733
|
const voice = resolveAzureVoice(req.voiceId);
|
|
1711
1734
|
const lang = req.lang ?? localeOf(voice);
|
|
1712
1735
|
const format = req.format ?? DEFAULT_FORMAT;
|
|
1713
|
-
|
|
1736
|
+
assertPronunciations(req.pronunciations, "azure");
|
|
1737
|
+
const escaped = applyPronunciations(
|
|
1738
|
+
xmlEscape(req.text),
|
|
1739
|
+
req.pronunciations,
|
|
1740
|
+
(p, matched) => p.ipa !== void 0 ? `<phoneme alphabet='ipa' ph='${xmlEscape(p.ipa)}'>${matched}</phoneme>` : `<sub alias='${xmlEscape(p.alias)}'>${matched}</sub>`,
|
|
1741
|
+
xmlEscape
|
|
1742
|
+
);
|
|
1714
1743
|
const effRate = req.rate ?? AZURE_DANISH_VOICE_LIST.find((v) => v.voiceId === voice)?.defaultRate;
|
|
1715
1744
|
const inner = effRate != null && effRate !== 1 ? `<prosody rate='${effRate}'>${escaped}</prosody>` : escaped;
|
|
1716
1745
|
const ssml = `<speak version='1.0' xml:lang='${lang}'><voice name='${voice}'>${inner}</voice></speak>`;
|
|
@@ -1764,7 +1793,7 @@ function azureAdapter(config = {}) {
|
|
|
1764
1793
|
inputTokens: 0,
|
|
1765
1794
|
outputTokens: 0
|
|
1766
1795
|
});
|
|
1767
|
-
usage.costUsd = minutes * (config.sttPricePerMin ??
|
|
1796
|
+
usage.costUsd = minutes * (config.sttPricePerMin ?? getMediaPrice("azure", "stt")?.usd ?? 0);
|
|
1768
1797
|
const result = { text, usage };
|
|
1769
1798
|
if (req.timestamps && req.timestamps.length > 0 && data.phrases) {
|
|
1770
1799
|
const toSec = (ms) => (ms ?? 0) / 1e3;
|
|
@@ -1795,13 +1824,6 @@ import { readFileSync } from "fs";
|
|
|
1795
1824
|
var TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token";
|
|
1796
1825
|
var DEFAULT_REGION2 = "europe-west1";
|
|
1797
1826
|
var CLOUD_PLATFORM_SCOPE = "https://www.googleapis.com/auth/cloud-platform";
|
|
1798
|
-
var VERTEX_VEO_PRICE_PER_SEC = {
|
|
1799
|
-
"veo-3.1-generate-preview": 0.4,
|
|
1800
|
-
"veo-3.1-fast-generate-preview": 0.1,
|
|
1801
|
-
"veo-3.1-lite-generate-preview": 0.05,
|
|
1802
|
-
"veo-3.0-generate-001": 0.4,
|
|
1803
|
-
"veo-3.0-fast-generate-001": 0.1
|
|
1804
|
-
};
|
|
1805
1827
|
function base64url(input) {
|
|
1806
1828
|
const buf = typeof input === "string" ? Buffer.from(input) : input;
|
|
1807
1829
|
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
@@ -1941,8 +1963,9 @@ function vertexAdapter(config = {}) {
|
|
|
1941
1963
|
inputTokens: 0,
|
|
1942
1964
|
outputTokens: 0
|
|
1943
1965
|
});
|
|
1944
|
-
const perSec = config.pricePerSecond ??
|
|
1945
|
-
usage.costUsd = perSec * (req.durationSec ??
|
|
1966
|
+
const perSec = config.pricePerSecond ?? getMediaPrice("vertex", req.spec.model)?.usd ?? 0;
|
|
1967
|
+
usage.costUsd = perSec * (req.durationSec ?? DEFAULT_CLIP_SEC);
|
|
1968
|
+
usage.costBasis = req.durationSec === void 0 ? "estimated" : "computed";
|
|
1946
1969
|
return { url: `vertex://${op.name}`, bytes: Buffer.from(videoB64, "base64"), mimeType: videoMime, usage };
|
|
1947
1970
|
}
|
|
1948
1971
|
async function vision(req) {
|
|
@@ -1994,7 +2017,6 @@ function vertexAdapter(config = {}) {
|
|
|
1994
2017
|
}
|
|
1995
2018
|
|
|
1996
2019
|
// src/providers/deepl.ts
|
|
1997
|
-
var DEEPL_PRICE_PER_1K_CHARS_ESTIMATE = 0.0217;
|
|
1998
2020
|
function deeplAdapter(config = {}) {
|
|
1999
2021
|
const fetchImpl = config.fetch ?? fetch;
|
|
2000
2022
|
function key() {
|
|
@@ -2030,7 +2052,8 @@ function deeplAdapter(config = {}) {
|
|
|
2030
2052
|
inputTokens: 0,
|
|
2031
2053
|
outputTokens: 0
|
|
2032
2054
|
});
|
|
2033
|
-
usage.costUsd = req.text.length / 1e3 * (config.pricePer1kChars ??
|
|
2055
|
+
usage.costUsd = req.text.length / 1e3 * (config.pricePer1kChars ?? getMediaPrice("deepl", "translate")?.usd ?? 0);
|
|
2056
|
+
usage.costBasis = config.pricePer1kChars !== void 0 ? "computed" : "estimated";
|
|
2034
2057
|
return { text, usage };
|
|
2035
2058
|
}
|
|
2036
2059
|
return { name: "deepl", translate };
|
|
@@ -2038,21 +2061,6 @@ function deeplAdapter(config = {}) {
|
|
|
2038
2061
|
|
|
2039
2062
|
// src/providers/fal.ts
|
|
2040
2063
|
import { deflateRawSync, crc32 } from "zlib";
|
|
2041
|
-
var FAL_IMAGE_PRICE_ESTIMATE = {
|
|
2042
|
-
"fal-ai/flux/schnell": 3e-3,
|
|
2043
|
-
"fal-ai/flux/dev": 0.025,
|
|
2044
|
-
"fal-ai/flux-lora": 0.025,
|
|
2045
|
-
"fal-ai/flux-pro": 0.05,
|
|
2046
|
-
"fal-ai/flux-pro/v1.1": 0.04
|
|
2047
|
-
};
|
|
2048
|
-
var FAL_TRAIN_PRICE_ESTIMATE = 2;
|
|
2049
|
-
var FAL_VIDEO_PRICE_PER_SEC = {
|
|
2050
|
-
// Kling 2.5 Turbo Pro i2v — the blessed FAL_KEY-only image→video route (F014
|
|
2051
|
-
// contentpush spike). $0.35 for the first 5 s + $0.07/additional s = a flat
|
|
2052
|
-
// $0.07/s (1080p); per-second-billed, commercial-use, not deprecated.
|
|
2053
|
-
"fal-ai/kling-video/v2.5-turbo/pro/image-to-video": 0.07
|
|
2054
|
-
};
|
|
2055
|
-
var FAL_VIDEO_DEFAULT_SEC = 8;
|
|
2056
2064
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2057
2065
|
function falAdapter(config = {}) {
|
|
2058
2066
|
const doFetch = config.fetch ?? fetch;
|
|
@@ -2092,7 +2100,9 @@ function falAdapter(config = {}) {
|
|
|
2092
2100
|
inputTokens: 0,
|
|
2093
2101
|
outputTokens: 0
|
|
2094
2102
|
});
|
|
2095
|
-
|
|
2103
|
+
const perImage = config.pricePerImage ?? getMediaPrice("fal", req.spec.model)?.usd;
|
|
2104
|
+
usage.costUsd = (perImage ?? 0) * calls;
|
|
2105
|
+
usage.costBasis = config.pricePerImage !== void 0 ? "computed" : "estimated";
|
|
2096
2106
|
return { url, usage };
|
|
2097
2107
|
}
|
|
2098
2108
|
async function animate(req) {
|
|
@@ -2121,8 +2131,9 @@ function falAdapter(config = {}) {
|
|
|
2121
2131
|
inputTokens: 0,
|
|
2122
2132
|
outputTokens: 0
|
|
2123
2133
|
});
|
|
2124
|
-
const perSec = config.pricePerSecond ??
|
|
2125
|
-
usage.costUsd = perSec * (req.durationSec ??
|
|
2134
|
+
const perSec = config.pricePerSecond ?? getMediaPrice("fal", req.spec.model)?.usd ?? 0;
|
|
2135
|
+
usage.costUsd = perSec * (req.durationSec ?? DEFAULT_CLIP_SEC);
|
|
2136
|
+
usage.costBasis = req.durationSec === void 0 ? "estimated" : "computed";
|
|
2126
2137
|
return { url, usage };
|
|
2127
2138
|
}
|
|
2128
2139
|
async function trainStyle(req) {
|
|
@@ -2158,7 +2169,8 @@ function falAdapter(config = {}) {
|
|
|
2158
2169
|
inputTokens: 0,
|
|
2159
2170
|
outputTokens: 0
|
|
2160
2171
|
});
|
|
2161
|
-
usage.costUsd = config.pricePerTraining ??
|
|
2172
|
+
usage.costUsd = config.pricePerTraining ?? getMediaPrice("fal", "train")?.usd ?? 0;
|
|
2173
|
+
usage.costBasis = config.pricePerTraining !== void 0 ? "computed" : "estimated";
|
|
2162
2174
|
return { loraUrl, configUrl: configUrl ?? "", usage };
|
|
2163
2175
|
}
|
|
2164
2176
|
async function resolveImagesUrl(images, apiKey) {
|
|
@@ -2356,7 +2368,6 @@ function buildZip(files) {
|
|
|
2356
2368
|
// src/providers/bfl.ts
|
|
2357
2369
|
var EU_BASE2 = "https://api.eu.bfl.ai";
|
|
2358
2370
|
var BFL_CREDIT_USD = 0.01;
|
|
2359
|
-
var BFL_IMAGE_PRICE = 0.06;
|
|
2360
2371
|
var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2361
2372
|
function gcd(a, b) {
|
|
2362
2373
|
return b === 0 ? a : gcd(b, a % b);
|
|
@@ -2432,7 +2443,13 @@ function bflAdapter(config = {}) {
|
|
|
2432
2443
|
inputTokens: 0,
|
|
2433
2444
|
outputTokens: 0
|
|
2434
2445
|
});
|
|
2435
|
-
|
|
2446
|
+
if (typeof submit.cost === "number") {
|
|
2447
|
+
usage.costUsd = submit.cost * BFL_CREDIT_USD;
|
|
2448
|
+
usage.costBasis = "reported";
|
|
2449
|
+
} else {
|
|
2450
|
+
usage.costUsd = config.pricePerImage ?? getMediaPrice("bfl", "flux-pro-1.1-ultra-finetuned")?.usd ?? 0;
|
|
2451
|
+
usage.costBasis = config.pricePerImage !== void 0 ? "computed" : "estimated";
|
|
2452
|
+
}
|
|
2436
2453
|
return { url: sample, usage };
|
|
2437
2454
|
}
|
|
2438
2455
|
function toBflImage(img) {
|
|
@@ -3023,9 +3040,18 @@ var podcastInputSchema = z.object({
|
|
|
3023
3040
|
format: z.string().optional(),
|
|
3024
3041
|
...callOptions
|
|
3025
3042
|
});
|
|
3043
|
+
var pronunciationSchema = z.object({
|
|
3044
|
+
word: z.string(),
|
|
3045
|
+
alias: z.string().optional(),
|
|
3046
|
+
ipa: z.string().optional(),
|
|
3047
|
+
lang: z.string().optional()
|
|
3048
|
+
});
|
|
3026
3049
|
var ttsInputSchema = z.object({
|
|
3027
3050
|
text: z.string(),
|
|
3028
3051
|
voice: z.string(),
|
|
3052
|
+
/** F051 — see TtsRequest.pronunciations. The alias/ipa exclusivity is enforced in
|
|
3053
|
+
* the adapter, not here: the message must name the provider that cannot do it. */
|
|
3054
|
+
pronunciations: z.array(pronunciationSchema).optional(),
|
|
3029
3055
|
/** F037: voice to use if `voice` is one we know the provider has retired. Without
|
|
3030
3056
|
* it a retired voice throws VoiceUnavailableError rather than reaching the API.
|
|
3031
3057
|
*
|
|
@@ -3067,8 +3093,8 @@ var aiConfigSchema = z.object({
|
|
|
3067
3093
|
});
|
|
3068
3094
|
|
|
3069
3095
|
// src/version.ts
|
|
3070
|
-
var VERSION = "0.
|
|
3071
|
-
var SDK_TAG = "@broberg/ai-sdk@0.
|
|
3096
|
+
var VERSION = "0.40.0";
|
|
3097
|
+
var SDK_TAG = "@broberg/ai-sdk@0.40.0";
|
|
3072
3098
|
|
|
3073
3099
|
// src/cost/sinks/upmetrics.ts
|
|
3074
3100
|
function upmetricsSink(config) {
|
|
@@ -3108,6 +3134,11 @@ function upmetricsSink(config) {
|
|
|
3108
3134
|
// capability/transport — no ingest-schema change, and without it the one
|
|
3109
3135
|
// field built for auditability existed only in memory.
|
|
3110
3136
|
region: usage.region,
|
|
3137
|
+
// F050: HOW cost_usd was arrived at. upmetrics already distinguishes
|
|
3138
|
+
// reported / computed / unpriced — we were sending an assumed number in
|
|
3139
|
+
// the same field as a measured one, so their labels could not be right
|
|
3140
|
+
// about our rows however carefully they were applied.
|
|
3141
|
+
cost_basis: usage.costBasis ?? "computed",
|
|
3111
3142
|
sdk: SDK_TAG
|
|
3112
3143
|
}
|
|
3113
3144
|
};
|
|
@@ -3611,7 +3642,15 @@ function createAI(config = {}) {
|
|
|
3611
3642
|
invoke: async (spec) => {
|
|
3612
3643
|
const adapter = pickProvider(spec.provider);
|
|
3613
3644
|
if (!adapter.tts) throw new Error(`createAI: provider "${spec.provider}" does not support tts`);
|
|
3614
|
-
return adapter.tts({
|
|
3645
|
+
return adapter.tts({
|
|
3646
|
+
text: input.text,
|
|
3647
|
+
voiceId,
|
|
3648
|
+
lang: input.lang,
|
|
3649
|
+
format: input.format,
|
|
3650
|
+
rate: input.rate,
|
|
3651
|
+
pronunciations: input.pronunciations,
|
|
3652
|
+
spec
|
|
3653
|
+
});
|
|
3615
3654
|
}
|
|
3616
3655
|
});
|
|
3617
3656
|
},
|
|
@@ -3926,6 +3965,7 @@ CREATE TABLE IF NOT EXISTS ai_usage (
|
|
|
3926
3965
|
cache_read_tokens INTEGER NOT NULL,
|
|
3927
3966
|
cache_creation_tokens INTEGER NOT NULL,
|
|
3928
3967
|
cost_usd REAL NOT NULL,
|
|
3968
|
+
cost_basis TEXT NOT NULL DEFAULT 'unknown',
|
|
3929
3969
|
latency_ms INTEGER NOT NULL,
|
|
3930
3970
|
subprocess INTEGER NOT NULL DEFAULT 0
|
|
3931
3971
|
)`;
|
|
@@ -3942,13 +3982,16 @@ function sqliteSink(config) {
|
|
|
3942
3982
|
if (!cols.some((c) => c.name === "region")) {
|
|
3943
3983
|
db.run(`ALTER TABLE ai_usage ADD COLUMN region TEXT NOT NULL DEFAULT 'unknown'`);
|
|
3944
3984
|
}
|
|
3985
|
+
if (!cols.some((c) => c.name === "cost_basis")) {
|
|
3986
|
+
db.run(`ALTER TABLE ai_usage ADD COLUMN cost_basis TEXT NOT NULL DEFAULT 'unknown'`);
|
|
3987
|
+
}
|
|
3945
3988
|
const insert = db.prepare(
|
|
3946
3989
|
`INSERT INTO ai_usage
|
|
3947
3990
|
(ts, provider, model, tier, transport, capability, region, purpose,
|
|
3948
3991
|
input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens,
|
|
3949
|
-
cost_usd, latency_ms, subprocess)
|
|
3992
|
+
cost_usd, cost_basis, latency_ms, subprocess)
|
|
3950
3993
|
VALUES ($ts, $provider, $model, $tier, $transport, $capability, $region, $purpose,
|
|
3951
|
-
$input, $output, $cacheRead, $cacheCreation, $cost, $latency, $subprocess)`
|
|
3994
|
+
$input, $output, $cacheRead, $cacheCreation, $cost, $costBasis, $latency, $subprocess)`
|
|
3952
3995
|
);
|
|
3953
3996
|
return insert;
|
|
3954
3997
|
};
|
|
@@ -3969,6 +4012,7 @@ function sqliteSink(config) {
|
|
|
3969
4012
|
$cacheRead: usage.cacheReadTokens,
|
|
3970
4013
|
$cacheCreation: usage.cacheCreationTokens,
|
|
3971
4014
|
$cost: usage.costUsd,
|
|
4015
|
+
$costBasis: usage.costBasis ?? "computed",
|
|
3972
4016
|
$latency: usage.latencyMs,
|
|
3973
4017
|
$subprocess: usage.subprocess ? 1 : 0
|
|
3974
4018
|
});
|
|
@@ -4061,8 +4105,10 @@ export {
|
|
|
4061
4105
|
AZURE_DANISH_VOICE_LIST,
|
|
4062
4106
|
BudgetExceededError,
|
|
4063
4107
|
BudgetGuard,
|
|
4108
|
+
DEFAULT_CLIP_SEC,
|
|
4064
4109
|
DEFAULT_TIER_MAP,
|
|
4065
4110
|
ELEVENLABS_DANISH_VOICES,
|
|
4111
|
+
MEDIA_PRICING_CHECKED_AT,
|
|
4066
4112
|
ModelUnavailableError,
|
|
4067
4113
|
PRICING_STALE_AFTER_DAYS,
|
|
4068
4114
|
SDK_TAG,
|
|
@@ -4095,10 +4141,12 @@ export {
|
|
|
4095
4141
|
fromProviderToolCall,
|
|
4096
4142
|
geminiAdapter,
|
|
4097
4143
|
getCostSummary,
|
|
4144
|
+
getMediaPrice,
|
|
4098
4145
|
getPrice,
|
|
4099
4146
|
httpTransport,
|
|
4100
4147
|
imageInputSchema,
|
|
4101
4148
|
listAzureDanishVoices,
|
|
4149
|
+
listMediaPrices,
|
|
4102
4150
|
listModels,
|
|
4103
4151
|
listVoices,
|
|
4104
4152
|
makeContracts,
|