@broberg/ai-sdk 0.7.0 → 0.9.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 +255 -21
- package/dist/index.js +260 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -280,7 +280,10 @@ var PRICING = {
|
|
|
280
280
|
"mistral:codestral-latest": { inputPer1M: 0.3, outputPer1M: 0.9, version: MS },
|
|
281
281
|
"mistral:open-mistral-nemo": { inputPer1M: 0.15, outputPer1M: 0.15, version: MS },
|
|
282
282
|
// Moderation (F016.4) — per input token; output 0. (OCR is per-page in the adapter.)
|
|
283
|
-
"mistral:mistral-moderation-latest": { inputPer1M: 0.1, outputPer1M: 0, version: MS }
|
|
283
|
+
"mistral:mistral-moderation-latest": { inputPer1M: 0.1, outputPer1M: 0, version: MS },
|
|
284
|
+
// Embeddings (F016.5) — per input token.
|
|
285
|
+
"mistral:mistral-embed": { inputPer1M: 0.1, outputPer1M: 0, version: MS },
|
|
286
|
+
"mistral:codestral-embed": { inputPer1M: 0.15, outputPer1M: 0, version: MS }
|
|
284
287
|
};
|
|
285
288
|
function getPrice(provider, model) {
|
|
286
289
|
const exact = PRICING[`${provider}:${model}`];
|
|
@@ -1028,6 +1031,11 @@ function openrouterAdapter(config = {}) {
|
|
|
1028
1031
|
|
|
1029
1032
|
// src/providers/mistral.ts
|
|
1030
1033
|
var MISTRAL_OCR_PRICE_PER_PAGE = 2e-3;
|
|
1034
|
+
var VOXTRAL_PRICE_PER_MIN = {
|
|
1035
|
+
"voxtral-mini-latest": 2e-3,
|
|
1036
|
+
"voxtral-mini-2507": 2e-3,
|
|
1037
|
+
"voxtral-mini-2602": 2e-3
|
|
1038
|
+
};
|
|
1031
1039
|
function mistralAdapter(config = {}) {
|
|
1032
1040
|
const baseUrl = config.baseUrl ?? "https://api.mistral.ai/v1";
|
|
1033
1041
|
const base = makeOpenAICompatibleAdapter({ name: "mistral", baseUrl, apiKey: config.apiKey });
|
|
@@ -1097,7 +1105,175 @@ function mistralAdapter(config = {}) {
|
|
|
1097
1105
|
});
|
|
1098
1106
|
return { results, usage };
|
|
1099
1107
|
}
|
|
1100
|
-
|
|
1108
|
+
async function embedding(req) {
|
|
1109
|
+
const res = await fetchImpl(`${baseUrl}/embeddings`, {
|
|
1110
|
+
method: "POST",
|
|
1111
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${key()}` },
|
|
1112
|
+
body: JSON.stringify({ model: req.spec.model, input: req.input })
|
|
1113
|
+
});
|
|
1114
|
+
if (!res.ok) {
|
|
1115
|
+
const body = await res.text().catch(() => "");
|
|
1116
|
+
throw new Error(`mistral embeddings ${res.status}: ${body.slice(0, 300)}`);
|
|
1117
|
+
}
|
|
1118
|
+
const data = await res.json();
|
|
1119
|
+
const vectors = (data.data ?? []).map((d) => d.embedding);
|
|
1120
|
+
const usage = freshUsage({
|
|
1121
|
+
provider: "mistral",
|
|
1122
|
+
model: req.spec.model,
|
|
1123
|
+
transport: "http",
|
|
1124
|
+
capability: "embedding",
|
|
1125
|
+
inputTokens: data.usage?.prompt_tokens ?? data.usage?.total_tokens ?? 0,
|
|
1126
|
+
outputTokens: 0
|
|
1127
|
+
});
|
|
1128
|
+
return { vectors, usage };
|
|
1129
|
+
}
|
|
1130
|
+
async function transcribe(req) {
|
|
1131
|
+
const form = new FormData();
|
|
1132
|
+
form.append("file", new Blob([req.audio]), "audio");
|
|
1133
|
+
form.append("model", req.spec.model);
|
|
1134
|
+
if (req.language) form.append("language", req.language);
|
|
1135
|
+
const res = await fetchImpl(`${baseUrl}/audio/transcriptions`, {
|
|
1136
|
+
method: "POST",
|
|
1137
|
+
headers: { authorization: `Bearer ${key()}` },
|
|
1138
|
+
body: form
|
|
1139
|
+
});
|
|
1140
|
+
if (!res.ok) {
|
|
1141
|
+
const body = await res.text().catch(() => "");
|
|
1142
|
+
throw new Error(`mistral transcribe ${res.status}: ${body.slice(0, 300)}`);
|
|
1143
|
+
}
|
|
1144
|
+
const data = await res.json();
|
|
1145
|
+
const usage = freshUsage({
|
|
1146
|
+
provider: "mistral",
|
|
1147
|
+
model: req.spec.model,
|
|
1148
|
+
transport: "http",
|
|
1149
|
+
capability: "transcribe",
|
|
1150
|
+
inputTokens: 0,
|
|
1151
|
+
outputTokens: 0
|
|
1152
|
+
});
|
|
1153
|
+
if (req.durationSec !== void 0) {
|
|
1154
|
+
usage.costUsd = req.durationSec / 60 * (VOXTRAL_PRICE_PER_MIN[req.spec.model] ?? 0);
|
|
1155
|
+
}
|
|
1156
|
+
return { text: data.text ?? "", usage };
|
|
1157
|
+
}
|
|
1158
|
+
async function batchSubmit(req) {
|
|
1159
|
+
const jsonl = req.items.map(
|
|
1160
|
+
(it) => JSON.stringify({
|
|
1161
|
+
custom_id: it.customId,
|
|
1162
|
+
body: { model: req.spec.model, messages: [{ role: "user", content: it.prompt }] }
|
|
1163
|
+
})
|
|
1164
|
+
).join("\n");
|
|
1165
|
+
const form = new FormData();
|
|
1166
|
+
form.append("purpose", "batch");
|
|
1167
|
+
form.append("file", new Blob([jsonl], { type: "application/jsonl" }), "batch.jsonl");
|
|
1168
|
+
const up = await fetchImpl(`${baseUrl}/files`, {
|
|
1169
|
+
method: "POST",
|
|
1170
|
+
headers: { authorization: `Bearer ${key()}` },
|
|
1171
|
+
body: form
|
|
1172
|
+
});
|
|
1173
|
+
if (!up.ok) throw new Error(`mistral batch upload ${up.status}: ${(await up.text().catch(() => "")).slice(0, 200)}`);
|
|
1174
|
+
const fileId = (await up.json()).id;
|
|
1175
|
+
const job = await fetchImpl(`${baseUrl}/batch/jobs`, {
|
|
1176
|
+
method: "POST",
|
|
1177
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${key()}` },
|
|
1178
|
+
body: JSON.stringify({ input_files: [fileId], model: req.spec.model, endpoint: "/v1/chat/completions" })
|
|
1179
|
+
});
|
|
1180
|
+
if (!job.ok) throw new Error(`mistral batch job ${job.status}: ${(await job.text().catch(() => "")).slice(0, 200)}`);
|
|
1181
|
+
const data = await job.json();
|
|
1182
|
+
return { jobId: data.id ?? "", status: data.status ?? "queued", total: data.total_requests };
|
|
1183
|
+
}
|
|
1184
|
+
async function batchStatus(req) {
|
|
1185
|
+
const res = await fetchImpl(`${baseUrl}/batch/jobs/${req.jobId}`, { headers: { authorization: `Bearer ${key()}` } });
|
|
1186
|
+
if (!res.ok) throw new Error(`mistral batch status ${res.status}`);
|
|
1187
|
+
const d = await res.json();
|
|
1188
|
+
return { jobId: d.id ?? req.jobId, status: d.status ?? "unknown", total: d.total_requests, completed: d.succeeded_requests };
|
|
1189
|
+
}
|
|
1190
|
+
async function batchResults(req) {
|
|
1191
|
+
const job = await fetchImpl(`${baseUrl}/batch/jobs/${req.jobId}`, { headers: { authorization: `Bearer ${key()}` } });
|
|
1192
|
+
if (!job.ok) throw new Error(`mistral batch results ${job.status}`);
|
|
1193
|
+
const outputFile = (await job.json()).output_file;
|
|
1194
|
+
if (!outputFile) throw new Error("mistral batch: job has no output_file yet (not finished)");
|
|
1195
|
+
const content = await fetchImpl(`${baseUrl}/files/${outputFile}/content`, { headers: { authorization: `Bearer ${key()}` } });
|
|
1196
|
+
if (!content.ok) throw new Error(`mistral batch download ${content.status}`);
|
|
1197
|
+
const lines = (await content.text()).trim().split("\n").filter(Boolean);
|
|
1198
|
+
return lines.map((line) => {
|
|
1199
|
+
const row = JSON.parse(line);
|
|
1200
|
+
return { customId: row.custom_id ?? "", text: row.response?.body?.choices?.[0]?.message?.content ?? "" };
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
return { ...base, ocr, moderate, embedding, transcribe, batchSubmit, batchStatus, batchResults };
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// src/providers/elevenlabs.ts
|
|
1207
|
+
var ELEVENLABS_PRICE_PER_1K_CHARS = 0.15;
|
|
1208
|
+
var ELEVENLABS_DANISH_VOICES = {
|
|
1209
|
+
soren: "xj6X4BCUsv9oxohm1E8o",
|
|
1210
|
+
jesper: "Bl1YwS3uJac5zEOSNESn",
|
|
1211
|
+
mads: "BIWC0507fYMfhPcAEIRP",
|
|
1212
|
+
noam: "V34B5u5UbLdNJVEkcgXp",
|
|
1213
|
+
camilla: "4RklGmuxoAskAbGXplXN"
|
|
1214
|
+
};
|
|
1215
|
+
function resolveVoice(nameOrId) {
|
|
1216
|
+
return ELEVENLABS_DANISH_VOICES[nameOrId] ?? nameOrId;
|
|
1217
|
+
}
|
|
1218
|
+
function elevenlabsAdapter(config = {}) {
|
|
1219
|
+
const baseUrl = config.baseUrl ?? "https://api.elevenlabs.io/v1";
|
|
1220
|
+
const fetchImpl = config.fetch ?? fetch;
|
|
1221
|
+
function key() {
|
|
1222
|
+
const k = config.apiKey ?? process.env.ELEVENLABS_API_KEY;
|
|
1223
|
+
if (!k) throw new Error("elevenlabs adapter: API key not set (env ELEVENLABS_API_KEY)");
|
|
1224
|
+
return k;
|
|
1225
|
+
}
|
|
1226
|
+
function priceFor(chars, model) {
|
|
1227
|
+
const usage = freshUsage({
|
|
1228
|
+
provider: "elevenlabs",
|
|
1229
|
+
model,
|
|
1230
|
+
transport: "http",
|
|
1231
|
+
capability: "podcast",
|
|
1232
|
+
inputTokens: 0,
|
|
1233
|
+
outputTokens: 0
|
|
1234
|
+
});
|
|
1235
|
+
usage.costUsd = chars / 1e3 * (config.pricePer1kChars ?? ELEVENLABS_PRICE_PER_1K_CHARS);
|
|
1236
|
+
return usage;
|
|
1237
|
+
}
|
|
1238
|
+
async function dialogue(req) {
|
|
1239
|
+
const res = await fetchImpl(`${baseUrl}/text-to-dialogue`, {
|
|
1240
|
+
method: "POST",
|
|
1241
|
+
headers: { "xi-api-key": key(), "content-type": "application/json", accept: "audio/mpeg" },
|
|
1242
|
+
body: JSON.stringify({
|
|
1243
|
+
model_id: req.spec.model,
|
|
1244
|
+
inputs: req.inputs.map((t) => ({ text: t.text, voice_id: t.voiceId })),
|
|
1245
|
+
...req.format ? { output_format: req.format } : {}
|
|
1246
|
+
})
|
|
1247
|
+
});
|
|
1248
|
+
if (!res.ok) {
|
|
1249
|
+
const body = await res.text().catch(() => "");
|
|
1250
|
+
throw new Error(`elevenlabs dialogue ${res.status}: ${body.slice(0, 300)}`);
|
|
1251
|
+
}
|
|
1252
|
+
const audio = new Uint8Array(await res.arrayBuffer());
|
|
1253
|
+
const chars = req.inputs.reduce((n, t) => n + t.text.length, 0);
|
|
1254
|
+
return { audio, mimeType: "audio/mpeg", usage: priceFor(chars, req.spec.model) };
|
|
1255
|
+
}
|
|
1256
|
+
async function tts(req) {
|
|
1257
|
+
const model = req.spec.model;
|
|
1258
|
+
const res = await fetchImpl(`${baseUrl}/text-to-speech/${req.voiceId}`, {
|
|
1259
|
+
method: "POST",
|
|
1260
|
+
headers: { "xi-api-key": key(), "content-type": "application/json", accept: "audio/mpeg" },
|
|
1261
|
+
body: JSON.stringify({ text: req.text, model_id: model })
|
|
1262
|
+
});
|
|
1263
|
+
if (!res.ok) {
|
|
1264
|
+
const body = await res.text().catch(() => "");
|
|
1265
|
+
throw new Error(`elevenlabs tts ${res.status}: ${body.slice(0, 300)}`);
|
|
1266
|
+
}
|
|
1267
|
+
const audio = new Uint8Array(await res.arrayBuffer());
|
|
1268
|
+
return { audio, mimeType: "audio/mpeg", usage: priceFor(req.text.length, model) };
|
|
1269
|
+
}
|
|
1270
|
+
async function listVoices() {
|
|
1271
|
+
const res = await fetchImpl(`${baseUrl}/voices`, { headers: { "xi-api-key": key() } });
|
|
1272
|
+
if (!res.ok) throw new Error(`elevenlabs voices ${res.status}`);
|
|
1273
|
+
const data = await res.json();
|
|
1274
|
+
return (data.voices ?? []).map((v) => ({ voiceId: v.voice_id, name: v.name, language: v.labels?.language }));
|
|
1275
|
+
}
|
|
1276
|
+
return { name: "elevenlabs", dialogue, tts, listVoices };
|
|
1101
1277
|
}
|
|
1102
1278
|
|
|
1103
1279
|
// src/providers/fal.ts
|
|
@@ -1188,6 +1364,7 @@ var defaultProviders = {
|
|
|
1188
1364
|
deepinfra: deepinfraAdapter(),
|
|
1189
1365
|
openrouter: openrouterAdapter(),
|
|
1190
1366
|
mistral: mistralAdapter(),
|
|
1367
|
+
elevenlabs: elevenlabsAdapter(),
|
|
1191
1368
|
fal: falAdapter()
|
|
1192
1369
|
};
|
|
1193
1370
|
|
|
@@ -1510,6 +1687,17 @@ var moderationInputSchema = z.object({
|
|
|
1510
1687
|
input: z.union([z.string(), z.array(z.string())]),
|
|
1511
1688
|
...callOptions
|
|
1512
1689
|
});
|
|
1690
|
+
var podcastInputSchema = z.object({
|
|
1691
|
+
script: z.array(z.object({ speaker: z.string(), text: z.string() })).min(1),
|
|
1692
|
+
voices: z.record(z.string(), z.string()),
|
|
1693
|
+
format: z.string().optional(),
|
|
1694
|
+
...callOptions
|
|
1695
|
+
});
|
|
1696
|
+
var ttsInputSchema = z.object({
|
|
1697
|
+
text: z.string(),
|
|
1698
|
+
voice: z.string(),
|
|
1699
|
+
...callOptions
|
|
1700
|
+
});
|
|
1513
1701
|
var budgetSchema = z.object({
|
|
1514
1702
|
perCallUsd: z.number().positive().optional(),
|
|
1515
1703
|
rollingUsd: z.number().positive().optional()
|
|
@@ -1531,6 +1719,9 @@ var DEFAULT_IMAGE_SPEC = {
|
|
|
1531
1719
|
};
|
|
1532
1720
|
var DEFAULT_OCR_SPEC = { provider: "mistral", model: "mistral-ocr-latest", transport: "http" };
|
|
1533
1721
|
var DEFAULT_MODERATION_SPEC = { provider: "mistral", model: "mistral-moderation-latest", transport: "http" };
|
|
1722
|
+
var DEFAULT_PODCAST_SPEC = { provider: "elevenlabs", model: "eleven_v3", transport: "http" };
|
|
1723
|
+
var DEFAULT_TTS_SPEC = { provider: "elevenlabs", model: "eleven_multilingual_v2", transport: "http" };
|
|
1724
|
+
var DEFAULT_BATCH_SPEC = { provider: "mistral", model: "mistral-small-latest", transport: "http" };
|
|
1534
1725
|
function createAI(config = {}) {
|
|
1535
1726
|
const cfg = aiConfigSchema.parse(config);
|
|
1536
1727
|
const providers = cfg.providers ?? defaultProviders;
|
|
@@ -1810,6 +2001,48 @@ function createAI(config = {}) {
|
|
|
1810
2001
|
}
|
|
1811
2002
|
});
|
|
1812
2003
|
},
|
|
2004
|
+
async podcast(input) {
|
|
2005
|
+
input = podcastInputSchema.parse(input);
|
|
2006
|
+
const inputs = input.script.map((turn) => {
|
|
2007
|
+
const mapped = input.voices[turn.speaker];
|
|
2008
|
+
if (!mapped) throw new Error(`ai.podcast: no voice mapped for speaker "${turn.speaker}"`);
|
|
2009
|
+
return { text: turn.text, voiceId: resolveVoice(mapped) };
|
|
2010
|
+
});
|
|
2011
|
+
const chars = input.script.reduce((n, t) => n + t.text.length, 0);
|
|
2012
|
+
return runCapability({
|
|
2013
|
+
primary: { ...DEFAULT_PODCAST_SPEC, ...input.override },
|
|
2014
|
+
fallback: input.fallback,
|
|
2015
|
+
capability: "podcast",
|
|
2016
|
+
purpose: input.purpose,
|
|
2017
|
+
labels: input.labels,
|
|
2018
|
+
estIn: chars,
|
|
2019
|
+
// per-character cost (not token-based)
|
|
2020
|
+
estOut: 0,
|
|
2021
|
+
invoke: async (spec) => {
|
|
2022
|
+
const adapter = pickProvider(spec.provider);
|
|
2023
|
+
if (!adapter.dialogue) throw new Error(`createAI: provider "${spec.provider}" does not support podcast/dialogue`);
|
|
2024
|
+
return adapter.dialogue({ inputs, format: input.format, spec });
|
|
2025
|
+
}
|
|
2026
|
+
});
|
|
2027
|
+
},
|
|
2028
|
+
async tts(input) {
|
|
2029
|
+
input = ttsInputSchema.parse(input);
|
|
2030
|
+
return runCapability({
|
|
2031
|
+
primary: { ...DEFAULT_TTS_SPEC, ...input.override },
|
|
2032
|
+
fallback: input.fallback,
|
|
2033
|
+
capability: "tts",
|
|
2034
|
+
purpose: input.purpose,
|
|
2035
|
+
labels: input.labels,
|
|
2036
|
+
estIn: input.text.length,
|
|
2037
|
+
// per-character cost
|
|
2038
|
+
estOut: 0,
|
|
2039
|
+
invoke: async (spec) => {
|
|
2040
|
+
const adapter = pickProvider(spec.provider);
|
|
2041
|
+
if (!adapter.tts) throw new Error(`createAI: provider "${spec.provider}" does not support tts`);
|
|
2042
|
+
return adapter.tts({ text: input.text, voiceId: resolveVoice(input.voice), spec });
|
|
2043
|
+
}
|
|
2044
|
+
});
|
|
2045
|
+
},
|
|
1813
2046
|
async embedding(input) {
|
|
1814
2047
|
input = embeddingInputSchema.parse(input);
|
|
1815
2048
|
const tier = input.tier ?? EMBEDDING_DEFAULT_TIER;
|
|
@@ -1848,6 +2081,26 @@ function createAI(config = {}) {
|
|
|
1848
2081
|
}
|
|
1849
2082
|
});
|
|
1850
2083
|
},
|
|
2084
|
+
batch: {
|
|
2085
|
+
async submit(input) {
|
|
2086
|
+
const spec = { ...DEFAULT_BATCH_SPEC, ...input.override };
|
|
2087
|
+
const adapter = pickProvider(spec.provider);
|
|
2088
|
+
if (!adapter.batchSubmit) throw new Error(`createAI: provider "${spec.provider}" does not support batch`);
|
|
2089
|
+
return adapter.batchSubmit({ items: input.requests, spec });
|
|
2090
|
+
},
|
|
2091
|
+
async status(jobId, override) {
|
|
2092
|
+
const spec = { ...DEFAULT_BATCH_SPEC, ...override };
|
|
2093
|
+
const adapter = pickProvider(spec.provider);
|
|
2094
|
+
if (!adapter.batchStatus) throw new Error(`createAI: provider "${spec.provider}" does not support batch`);
|
|
2095
|
+
return adapter.batchStatus({ jobId, spec });
|
|
2096
|
+
},
|
|
2097
|
+
async results(jobId, override) {
|
|
2098
|
+
const spec = { ...DEFAULT_BATCH_SPEC, ...override };
|
|
2099
|
+
const adapter = pickProvider(spec.provider);
|
|
2100
|
+
if (!adapter.batchResults) throw new Error(`createAI: provider "${spec.provider}" does not support batch`);
|
|
2101
|
+
return adapter.batchResults({ jobId, spec });
|
|
2102
|
+
}
|
|
2103
|
+
},
|
|
1851
2104
|
// Replaced below with the real prompt-contracts (needs the client itself).
|
|
1852
2105
|
contracts: void 0
|
|
1853
2106
|
};
|
|
@@ -1936,8 +2189,8 @@ var stubProviders = {
|
|
|
1936
2189
|
};
|
|
1937
2190
|
|
|
1938
2191
|
// src/version.ts
|
|
1939
|
-
var VERSION = "0.
|
|
1940
|
-
var SDK_TAG = "@broberg/ai-sdk@0.
|
|
2192
|
+
var VERSION = "0.9.0";
|
|
2193
|
+
var SDK_TAG = "@broberg/ai-sdk@0.9.0";
|
|
1941
2194
|
|
|
1942
2195
|
// src/cost/budget-store.ts
|
|
1943
2196
|
function sqliteBudgetStore(config) {
|
|
@@ -2169,6 +2422,7 @@ export {
|
|
|
2169
2422
|
BudgetExceededError,
|
|
2170
2423
|
BudgetGuard,
|
|
2171
2424
|
DEFAULT_TIER_MAP,
|
|
2425
|
+
ELEVENLABS_DANISH_VOICES,
|
|
2172
2426
|
SDK_TAG,
|
|
2173
2427
|
StreamHttpError,
|
|
2174
2428
|
VERSION,
|
|
@@ -2182,6 +2436,7 @@ export {
|
|
|
2182
2436
|
deepinfraAdapter,
|
|
2183
2437
|
defaultProviders,
|
|
2184
2438
|
discordSink,
|
|
2439
|
+
elevenlabsAdapter,
|
|
2185
2440
|
embeddingInputSchema,
|
|
2186
2441
|
falAdapter,
|
|
2187
2442
|
falStubAdapter,
|
|
@@ -2204,6 +2459,7 @@ export {
|
|
|
2204
2459
|
parseClaudeCliJson,
|
|
2205
2460
|
parseJsonLoose,
|
|
2206
2461
|
resolveTier,
|
|
2462
|
+
resolveVoice,
|
|
2207
2463
|
sqliteBudgetStore,
|
|
2208
2464
|
sqliteSink,
|
|
2209
2465
|
streamTransport,
|