@broberg/ai-sdk 0.38.0 → 0.39.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.d.ts +55 -4
- package/dist/index.js +80 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -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";
|
|
@@ -423,6 +435,7 @@ interface PodcastResult {
|
|
|
423
435
|
mimeType: string;
|
|
424
436
|
usage: Usage;
|
|
425
437
|
}
|
|
438
|
+
|
|
426
439
|
interface TtsRequest {
|
|
427
440
|
text: string;
|
|
428
441
|
voiceId: string;
|
|
@@ -432,6 +445,14 @@ interface TtsRequest {
|
|
|
432
445
|
format?: string;
|
|
433
446
|
/** Speaking-rate multiplier (Azure): 1 = normal, 0.9 = 10% slower, 1.1 = faster. ElevenLabs ignores it. */
|
|
434
447
|
rate?: number;
|
|
448
|
+
/** F051 — pronunciation dictionary. A Danish voice says English jargon wrongly:
|
|
449
|
+
* measured on da-DK, "AI" comes out as the word "aj", "native" as "nativ".
|
|
450
|
+
*
|
|
451
|
+
* Azure renders these as SSML (`<sub alias>` / `<phoneme alphabet="ipa" ph>`);
|
|
452
|
+
* ElevenLabs has no SSML and can only apply `alias`. **This is the CONTROLLED DOOR
|
|
453
|
+
* into SSML** — the substitution happens adapter-side AFTER the text is escaped, so
|
|
454
|
+
* `text` can never inject markup, and `alias`/`ipa` are escaped too. */
|
|
455
|
+
pronunciations?: Pronunciation[];
|
|
435
456
|
spec: TierSpec;
|
|
436
457
|
}
|
|
437
458
|
interface BatchRequestItem {
|
|
@@ -1807,6 +1828,24 @@ declare const ttsInputSchema: z.ZodObject<{
|
|
|
1807
1828
|
labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
1808
1829
|
text: z.ZodString;
|
|
1809
1830
|
voice: z.ZodString;
|
|
1831
|
+
/** F051 — see TtsRequest.pronunciations. The alias/ipa exclusivity is enforced in
|
|
1832
|
+
* the adapter, not here: the message must name the provider that cannot do it. */
|
|
1833
|
+
pronunciations: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
1834
|
+
word: z.ZodString;
|
|
1835
|
+
alias: z.ZodOptional<z.ZodString>;
|
|
1836
|
+
ipa: z.ZodOptional<z.ZodString>;
|
|
1837
|
+
lang: z.ZodOptional<z.ZodString>;
|
|
1838
|
+
}, "strip", z.ZodTypeAny, {
|
|
1839
|
+
word: string;
|
|
1840
|
+
alias?: string | undefined;
|
|
1841
|
+
ipa?: string | undefined;
|
|
1842
|
+
lang?: string | undefined;
|
|
1843
|
+
}, {
|
|
1844
|
+
word: string;
|
|
1845
|
+
alias?: string | undefined;
|
|
1846
|
+
ipa?: string | undefined;
|
|
1847
|
+
lang?: string | undefined;
|
|
1848
|
+
}>, "many">>;
|
|
1810
1849
|
/** F037: voice to use if `voice` is one we know the provider has retired. Without
|
|
1811
1850
|
* it a retired voice throws VoiceUnavailableError rather than reaching the API.
|
|
1812
1851
|
*
|
|
@@ -1838,8 +1877,14 @@ declare const ttsInputSchema: z.ZodObject<{
|
|
|
1838
1877
|
})[] | undefined;
|
|
1839
1878
|
labels?: Record<string, string> | undefined;
|
|
1840
1879
|
format?: string | undefined;
|
|
1841
|
-
voiceFallback?: string | undefined;
|
|
1842
1880
|
lang?: string | undefined;
|
|
1881
|
+
pronunciations?: {
|
|
1882
|
+
word: string;
|
|
1883
|
+
alias?: string | undefined;
|
|
1884
|
+
ipa?: string | undefined;
|
|
1885
|
+
lang?: string | undefined;
|
|
1886
|
+
}[] | undefined;
|
|
1887
|
+
voiceFallback?: string | undefined;
|
|
1843
1888
|
rate?: number | undefined;
|
|
1844
1889
|
}, {
|
|
1845
1890
|
text: string;
|
|
@@ -1858,8 +1903,14 @@ declare const ttsInputSchema: z.ZodObject<{
|
|
|
1858
1903
|
})[] | undefined;
|
|
1859
1904
|
labels?: Record<string, string> | undefined;
|
|
1860
1905
|
format?: string | undefined;
|
|
1861
|
-
voiceFallback?: string | undefined;
|
|
1862
1906
|
lang?: string | undefined;
|
|
1907
|
+
pronunciations?: {
|
|
1908
|
+
word: string;
|
|
1909
|
+
alias?: string | undefined;
|
|
1910
|
+
ipa?: string | undefined;
|
|
1911
|
+
lang?: string | undefined;
|
|
1912
|
+
}[] | undefined;
|
|
1913
|
+
voiceFallback?: string | undefined;
|
|
1863
1914
|
rate?: number | undefined;
|
|
1864
1915
|
}>;
|
|
1865
1916
|
declare const aiConfigSchema: z.ZodObject<{
|
|
@@ -2251,8 +2302,8 @@ declare const falStubAdapter: ProviderAdapter;
|
|
|
2251
2302
|
* wires the live adapters. */
|
|
2252
2303
|
declare const stubProviders: Record<string, ProviderAdapter>;
|
|
2253
2304
|
|
|
2254
|
-
declare const VERSION: "0.
|
|
2255
|
-
declare const SDK_TAG: "@broberg/ai-sdk@0.
|
|
2305
|
+
declare const VERSION: "0.39.0";
|
|
2306
|
+
declare const SDK_TAG: "@broberg/ai-sdk@0.39.0";
|
|
2256
2307
|
|
|
2257
2308
|
/** Built-in defaults. Every entry is overridable via AiConfig.defaults or a
|
|
2258
2309
|
* per-call override.
|
package/dist/index.js
CHANGED
|
@@ -1547,6 +1547,47 @@ function mistralAdapter(config = {}) {
|
|
|
1547
1547
|
return { ...base, ocr, moderate, embedding, transcribe, batchSubmit, batchStatus, batchResults };
|
|
1548
1548
|
}
|
|
1549
1549
|
|
|
1550
|
+
// src/providers/pronunciation.ts
|
|
1551
|
+
function xmlEscape(s) {
|
|
1552
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1553
|
+
}
|
|
1554
|
+
function escapeRegex(s) {
|
|
1555
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1556
|
+
}
|
|
1557
|
+
function assertPronunciations(list, provider) {
|
|
1558
|
+
for (const p of list ?? []) {
|
|
1559
|
+
if (!p.word.trim()) {
|
|
1560
|
+
throw new Error(`${provider} adapter: a pronunciation entry has an empty "word".`);
|
|
1561
|
+
}
|
|
1562
|
+
if (p.alias !== void 0 && p.ipa !== void 0) {
|
|
1563
|
+
throw new Error(
|
|
1564
|
+
`${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.`
|
|
1565
|
+
);
|
|
1566
|
+
}
|
|
1567
|
+
if (p.alias === void 0 && p.ipa === void 0) {
|
|
1568
|
+
throw new Error(
|
|
1569
|
+
`${provider} adapter: pronunciation "${p.word}" sets neither alias nor ipa, so there is nothing to say instead.`
|
|
1570
|
+
);
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
function applyPronunciations(haystack, list, render, escape = (s) => s) {
|
|
1575
|
+
const entries = (list ?? []).filter((p) => p.word.trim().length > 0);
|
|
1576
|
+
if (entries.length === 0) return haystack;
|
|
1577
|
+
const sorted = [...entries].sort((a, b) => escape(b.word).length - escape(a.word).length);
|
|
1578
|
+
const byLower = /* @__PURE__ */ new Map();
|
|
1579
|
+
for (const p of sorted) {
|
|
1580
|
+
const k = escape(p.word).toLowerCase();
|
|
1581
|
+
if (!byLower.has(k)) byLower.set(k, p);
|
|
1582
|
+
}
|
|
1583
|
+
const alternation = sorted.map((p) => escapeRegex(escape(p.word))).join("|");
|
|
1584
|
+
const re = new RegExp(`(?<![\\w-])(${alternation})(?![\\w-])`, "gi");
|
|
1585
|
+
return haystack.replace(re, (matched) => {
|
|
1586
|
+
const entry = byLower.get(matched.toLowerCase());
|
|
1587
|
+
return entry ? render(entry, matched) : matched;
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1550
1591
|
// src/providers/elevenlabs.ts
|
|
1551
1592
|
var ELEVENLABS_PRICE_PER_1K_CHARS = 0.15;
|
|
1552
1593
|
var ELEVENLABS_DANISH_VOICES = {
|
|
@@ -1559,6 +1600,17 @@ var ELEVENLABS_DANISH_VOICES = {
|
|
|
1559
1600
|
function resolveVoice(nameOrId) {
|
|
1560
1601
|
return ELEVENLABS_DANISH_VOICES[nameOrId] ?? nameOrId;
|
|
1561
1602
|
}
|
|
1603
|
+
function ttsText(req) {
|
|
1604
|
+
assertPronunciations(req.pronunciations, "elevenlabs");
|
|
1605
|
+
for (const p of req.pronunciations ?? []) {
|
|
1606
|
+
if (p.ipa !== void 0) {
|
|
1607
|
+
throw new Error(
|
|
1608
|
+
`elevenlabs adapter: pronunciation "${p.word}" uses ipa, which needs SSML \u2014 ElevenLabs has none. Use { alias } here, or route this call to azure.`
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
return applyPronunciations(req.text, req.pronunciations, (p) => p.alias);
|
|
1613
|
+
}
|
|
1562
1614
|
function elevenlabsAdapter(config = {}) {
|
|
1563
1615
|
const baseUrl = config.baseUrl ?? "https://api.elevenlabs.io/v1";
|
|
1564
1616
|
const fetchImpl = config.fetch ?? fetch;
|
|
@@ -1602,7 +1654,7 @@ function elevenlabsAdapter(config = {}) {
|
|
|
1602
1654
|
const res = await fetchImpl(`${baseUrl}/text-to-speech/${req.voiceId}`, {
|
|
1603
1655
|
method: "POST",
|
|
1604
1656
|
headers: { "xi-api-key": key(), "content-type": "application/json", accept: "audio/mpeg" },
|
|
1605
|
-
body: JSON.stringify({ text: req
|
|
1657
|
+
body: JSON.stringify({ text: ttsText(req), model_id: model })
|
|
1606
1658
|
});
|
|
1607
1659
|
if (!res.ok) {
|
|
1608
1660
|
const body = await res.text().catch(() => "");
|
|
@@ -1662,9 +1714,6 @@ function listAzureDanishVoices() {
|
|
|
1662
1714
|
function resolveAzureVoice(nameOrVoice) {
|
|
1663
1715
|
return AZURE_DANISH_VOICES[nameOrVoice] ?? nameOrVoice;
|
|
1664
1716
|
}
|
|
1665
|
-
function xmlEscape(s) {
|
|
1666
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1667
|
-
}
|
|
1668
1717
|
function localeOf(voice) {
|
|
1669
1718
|
const parts = voice.split("-");
|
|
1670
1719
|
return parts.length >= 2 ? `${parts[0]}-${parts[1]}` : "en-US";
|
|
@@ -1710,7 +1759,13 @@ function azureAdapter(config = {}) {
|
|
|
1710
1759
|
const voice = resolveAzureVoice(req.voiceId);
|
|
1711
1760
|
const lang = req.lang ?? localeOf(voice);
|
|
1712
1761
|
const format = req.format ?? DEFAULT_FORMAT;
|
|
1713
|
-
|
|
1762
|
+
assertPronunciations(req.pronunciations, "azure");
|
|
1763
|
+
const escaped = applyPronunciations(
|
|
1764
|
+
xmlEscape(req.text),
|
|
1765
|
+
req.pronunciations,
|
|
1766
|
+
(p, matched) => p.ipa !== void 0 ? `<phoneme alphabet='ipa' ph='${xmlEscape(p.ipa)}'>${matched}</phoneme>` : `<sub alias='${xmlEscape(p.alias)}'>${matched}</sub>`,
|
|
1767
|
+
xmlEscape
|
|
1768
|
+
);
|
|
1714
1769
|
const effRate = req.rate ?? AZURE_DANISH_VOICE_LIST.find((v) => v.voiceId === voice)?.defaultRate;
|
|
1715
1770
|
const inner = effRate != null && effRate !== 1 ? `<prosody rate='${effRate}'>${escaped}</prosody>` : escaped;
|
|
1716
1771
|
const ssml = `<speak version='1.0' xml:lang='${lang}'><voice name='${voice}'>${inner}</voice></speak>`;
|
|
@@ -3023,9 +3078,18 @@ var podcastInputSchema = z.object({
|
|
|
3023
3078
|
format: z.string().optional(),
|
|
3024
3079
|
...callOptions
|
|
3025
3080
|
});
|
|
3081
|
+
var pronunciationSchema = z.object({
|
|
3082
|
+
word: z.string(),
|
|
3083
|
+
alias: z.string().optional(),
|
|
3084
|
+
ipa: z.string().optional(),
|
|
3085
|
+
lang: z.string().optional()
|
|
3086
|
+
});
|
|
3026
3087
|
var ttsInputSchema = z.object({
|
|
3027
3088
|
text: z.string(),
|
|
3028
3089
|
voice: z.string(),
|
|
3090
|
+
/** F051 — see TtsRequest.pronunciations. The alias/ipa exclusivity is enforced in
|
|
3091
|
+
* the adapter, not here: the message must name the provider that cannot do it. */
|
|
3092
|
+
pronunciations: z.array(pronunciationSchema).optional(),
|
|
3029
3093
|
/** F037: voice to use if `voice` is one we know the provider has retired. Without
|
|
3030
3094
|
* it a retired voice throws VoiceUnavailableError rather than reaching the API.
|
|
3031
3095
|
*
|
|
@@ -3067,8 +3131,8 @@ var aiConfigSchema = z.object({
|
|
|
3067
3131
|
});
|
|
3068
3132
|
|
|
3069
3133
|
// src/version.ts
|
|
3070
|
-
var VERSION = "0.
|
|
3071
|
-
var SDK_TAG = "@broberg/ai-sdk@0.
|
|
3134
|
+
var VERSION = "0.39.0";
|
|
3135
|
+
var SDK_TAG = "@broberg/ai-sdk@0.39.0";
|
|
3072
3136
|
|
|
3073
3137
|
// src/cost/sinks/upmetrics.ts
|
|
3074
3138
|
function upmetricsSink(config) {
|
|
@@ -3611,7 +3675,15 @@ function createAI(config = {}) {
|
|
|
3611
3675
|
invoke: async (spec) => {
|
|
3612
3676
|
const adapter = pickProvider(spec.provider);
|
|
3613
3677
|
if (!adapter.tts) throw new Error(`createAI: provider "${spec.provider}" does not support tts`);
|
|
3614
|
-
return adapter.tts({
|
|
3678
|
+
return adapter.tts({
|
|
3679
|
+
text: input.text,
|
|
3680
|
+
voiceId,
|
|
3681
|
+
lang: input.lang,
|
|
3682
|
+
format: input.format,
|
|
3683
|
+
rate: input.rate,
|
|
3684
|
+
pronunciations: input.pronunciations,
|
|
3685
|
+
spec
|
|
3686
|
+
});
|
|
3615
3687
|
}
|
|
3616
3688
|
});
|
|
3617
3689
|
},
|