@broberg/ai-sdk 0.21.1 → 0.23.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 CHANGED
@@ -335,6 +335,11 @@ interface ProviderAdapter {
335
335
  /** Streaming chat (F8). Optional — absence is a typed "no streaming support".
336
336
  * Same request shape as chat; yields ChatStreamEvents as the turn unfolds. */
337
337
  chatStream?(req: ChatRequest): AsyncIterable<ChatStreamEvent>;
338
+ /** Dedicated translation engine (F032) — e.g. DeepL. When absent, `ai.translate`
339
+ * falls back to a chat prompt-contract (the historical default for every
340
+ * other provider). `to`/`from` are provider-specific: a chat-routed call
341
+ * accepts free-form names ("Danish"); DeepL requires real codes ("DA"). */
342
+ translate?(req: TranslateRequest): Promise<TranslateResult>;
338
343
  vision?(req: ChatRequest): Promise<ChatResult>;
339
344
  image?(req: ImageRequest): Promise<ImageResult>;
340
345
  /** Image-to-video generation (F024) — animate a still into a short clip. fal. */
@@ -367,6 +372,15 @@ interface TranslateResult {
367
372
  text: string;
368
373
  usage: Usage;
369
374
  }
375
+ /** Dedicated-engine translation request (F032) — used only by adapters that
376
+ * implement `ProviderAdapter.translate` directly (e.g. DeepL); chat-routed
377
+ * providers never see this shape, they get a built prompt instead. */
378
+ interface TranslateRequest {
379
+ text: string;
380
+ to: string;
381
+ from?: string;
382
+ spec: TierSpec;
383
+ }
370
384
 
371
385
  interface MockupInput {
372
386
  description: string;
@@ -1010,12 +1024,12 @@ declare const imageInputSchema: z.ZodObject<{
1010
1024
  retryOnBlack: z.ZodOptional<z.ZodBoolean>;
1011
1025
  }, "strip", z.ZodTypeAny, {
1012
1026
  prompt: string;
1027
+ seed?: number | undefined;
1013
1028
  purpose?: string | undefined;
1014
1029
  loras?: {
1015
1030
  path: string;
1016
1031
  scale?: number | undefined;
1017
1032
  }[] | undefined;
1018
- seed?: number | undefined;
1019
1033
  lora?: string | undefined;
1020
1034
  width?: number | undefined;
1021
1035
  height?: number | undefined;
@@ -1039,12 +1053,12 @@ declare const imageInputSchema: z.ZodObject<{
1039
1053
  retryOnBlack?: boolean | undefined;
1040
1054
  }, {
1041
1055
  prompt: string;
1056
+ seed?: number | undefined;
1042
1057
  purpose?: string | undefined;
1043
1058
  loras?: {
1044
1059
  path: string;
1045
1060
  scale?: number | undefined;
1046
1061
  }[] | undefined;
1047
- seed?: number | undefined;
1048
1062
  lora?: string | undefined;
1049
1063
  width?: number | undefined;
1050
1064
  height?: number | undefined;
@@ -1804,12 +1818,16 @@ declare function deepinfraAdapter(config?: {
1804
1818
  baseUrl?: string;
1805
1819
  }): ProviderAdapter;
1806
1820
 
1807
- declare function openrouterAdapter(config?: {
1821
+ interface OpenRouterAdapterConfig {
1808
1822
  apiKey?: string;
1809
1823
  baseUrl?: string;
1810
1824
  referer?: string;
1811
1825
  title?: string;
1812
- }): ProviderAdapter;
1826
+ fetch?: typeof fetch;
1827
+ /** Override the per-image USD price (else OPENROUTER_IMAGE_PRICE_ESTIMATE, else 0). */
1828
+ pricePerImage?: number;
1829
+ }
1830
+ declare function openrouterAdapter(config?: OpenRouterAdapterConfig): ProviderAdapter;
1813
1831
 
1814
1832
  declare function requestyAdapter(config?: {
1815
1833
  apiKey?: string;
@@ -1896,6 +1914,26 @@ declare function azureAdapter(config?: {
1896
1914
  sttBiasingWeight?: number;
1897
1915
  }): ProviderAdapter;
1898
1916
 
1917
+ declare function vertexAdapter(config?: {
1918
+ /** Inline service-account JSON; else env GOOGLE_VERTEX_CREDENTIALS or GOOGLE_APPLICATION_CREDENTIALS (file path). */
1919
+ credentials?: string;
1920
+ /** GCP project id; else env GOOGLE_VERTEX_PROJECT. Required — never guessed. */
1921
+ project?: string;
1922
+ /** Vertex region; default "europe-west1" (EU by default — this adapter's reason to exist). */
1923
+ region?: string;
1924
+ fetch?: typeof fetch;
1925
+ pricePerSecond?: number;
1926
+ pollIntervalMs?: number;
1927
+ videoTimeoutMs?: number;
1928
+ }): ProviderAdapter;
1929
+
1930
+ declare function deeplAdapter(config?: {
1931
+ apiKey?: string;
1932
+ baseUrl?: string;
1933
+ fetch?: typeof fetch;
1934
+ pricePer1kChars?: number;
1935
+ }): ProviderAdapter;
1936
+
1899
1937
  interface FalAdapterConfig {
1900
1938
  apiKey?: string;
1901
1939
  /** "sync" (default — fal.run, fast models) or "queue" (queue.fal.run, polled). */
@@ -1979,8 +2017,8 @@ declare const falStubAdapter: ProviderAdapter;
1979
2017
  * wires the live adapters. */
1980
2018
  declare const stubProviders: Record<string, ProviderAdapter>;
1981
2019
 
1982
- declare const VERSION: "0.21.1";
1983
- declare const SDK_TAG: "@broberg/ai-sdk@0.21.1";
2020
+ declare const VERSION: "0.23.0";
2021
+ declare const SDK_TAG: "@broberg/ai-sdk@0.23.0";
1984
2022
 
1985
2023
  /** Built-in defaults. Every entry is overridable via AiConfig.defaults or a
1986
2024
  * per-call override.
@@ -2307,4 +2345,4 @@ interface StreamTransportRequest extends TransportRequest {
2307
2345
  */
2308
2346
  declare function streamTransport(req: StreamTransportRequest): AsyncIterable<string>;
2309
2347
 
2310
- export { AZURE_DANISH_VOICES, AZURE_DANISH_VOICE_LIST, type AiClient, type AiConfig, type AzureVoiceInfo, 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 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 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, aiConfigSchema, anthropicAdapter, anthropicApiAdapter, anthropicSubprocessAdapter, azureAdapter, bflAdapter, bflCredits, chatInputSchema, computeCost, createAI, deepinfraAdapter, deepseekAdapter, defaultProviders, discordSink, elevenlabsAdapter, embeddingInputSchema, falAdapter, falStubAdapter, freshUsage, fromProviderToolCall, geminiAdapter, getCostSummary, getPrice, httpTransport, imageInputSchema, listAzureDanishVoices, makeContracts, makeOpenAICompatibleAdapter, messageSchema, mistralAdapter, mistralStubAdapter, multiSink, noopSink, openaiAdapter, openaiStubAdapter, openrouterAdapter, parseClaudeCliJson, parseJsonLoose, refreshAvailability, requestyAdapter, resetRefreshState, resetRegistry, resolveAzureVoice, resolveTier, resolveVoice, sqliteBudgetStore, sqliteSink, streamTransport, stubProviders, subprocessTransport, tierSpecSchema, toProviderTools, toolSchema, translateInputSchema, upmetricsCostClient, upmetricsSink, usdFromMicro, visionInputSchema };
2348
+ export { AZURE_DANISH_VOICES, AZURE_DANISH_VOICE_LIST, type AiClient, type AiConfig, type AzureVoiceInfo, 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 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 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, aiConfigSchema, anthropicAdapter, anthropicApiAdapter, anthropicSubprocessAdapter, azureAdapter, bflAdapter, bflCredits, chatInputSchema, computeCost, createAI, deepinfraAdapter, deeplAdapter, deepseekAdapter, defaultProviders, discordSink, elevenlabsAdapter, embeddingInputSchema, falAdapter, falStubAdapter, freshUsage, fromProviderToolCall, geminiAdapter, getCostSummary, getPrice, httpTransport, imageInputSchema, listAzureDanishVoices, makeContracts, makeOpenAICompatibleAdapter, messageSchema, mistralAdapter, mistralStubAdapter, multiSink, noopSink, openaiAdapter, openaiStubAdapter, openrouterAdapter, parseClaudeCliJson, parseJsonLoose, refreshAvailability, requestyAdapter, resetRefreshState, resetRegistry, resolveAzureVoice, resolveTier, resolveVoice, sqliteBudgetStore, sqliteSink, streamTransport, stubProviders, subprocessTransport, tierSpecSchema, toProviderTools, toolSchema, translateInputSchema, upmetricsCostClient, upmetricsSink, usdFromMicro, vertexAdapter, visionInputSchema };
package/dist/index.js CHANGED
@@ -758,6 +758,30 @@ function openaiAdapter(config = {}) {
758
758
  return { ...base, embedding, transcribe };
759
759
  }
760
760
 
761
+ // src/providers/media.ts
762
+ async function toInlineImage(image, fetchImpl) {
763
+ if (typeof image !== "string") {
764
+ return { data: Buffer.from(image).toString("base64"), mimeType: sniffMime(image) };
765
+ }
766
+ if (/^https?:\/\//i.test(image)) {
767
+ const res = await fetchImpl(image);
768
+ if (!res.ok) throw new Error(`toInlineImage: failed to fetch image (${res.status})`);
769
+ const bytes = new Uint8Array(await res.arrayBuffer());
770
+ const mimeType2 = res.headers.get("content-type") ?? sniffMime(bytes);
771
+ return { data: Buffer.from(bytes).toString("base64"), mimeType: mimeType2 };
772
+ }
773
+ const comma = image.startsWith("data:") ? image.indexOf(",") : -1;
774
+ const b64 = comma >= 0 ? image.slice(comma + 1) : image;
775
+ const mimeType = image.startsWith("data:") ? image.slice(5, image.indexOf(";")) : "image/png";
776
+ return { data: b64, mimeType };
777
+ }
778
+ function sniffMime(b) {
779
+ if (b[0] === 137 && b[1] === 80) return "image/png";
780
+ if (b[0] === 71 && b[1] === 73) return "image/gif";
781
+ if (b[0] === 82 && b[1] === 73 && b[8] === 87) return "image/webp";
782
+ return "image/jpeg";
783
+ }
784
+
761
785
  // src/providers/gemini.ts
762
786
  var GEMINI_IMAGE_PRICE_PER_IMAGE = {
763
787
  "gemini-2.5-flash-image": 0.039,
@@ -1002,28 +1026,6 @@ function geminiAdapter(config = {}) {
1002
1026
  }
1003
1027
  return { name: "gemini", chat, chatStream, image, animate, vision: chat };
1004
1028
  }
1005
- async function toInlineImage(image, fetchImpl) {
1006
- if (typeof image !== "string") {
1007
- return { data: Buffer.from(image).toString("base64"), mimeType: sniffMime(image) };
1008
- }
1009
- if (/^https?:\/\//i.test(image)) {
1010
- const res = await fetchImpl(image);
1011
- if (!res.ok) throw new Error(`gemini animate: failed to fetch image (${res.status})`);
1012
- const bytes = new Uint8Array(await res.arrayBuffer());
1013
- const mimeType2 = res.headers.get("content-type") ?? sniffMime(bytes);
1014
- return { data: Buffer.from(bytes).toString("base64"), mimeType: mimeType2 };
1015
- }
1016
- const comma = image.startsWith("data:") ? image.indexOf(",") : -1;
1017
- const b64 = comma >= 0 ? image.slice(comma + 1) : image;
1018
- const mimeType = image.startsWith("data:") ? image.slice(5, image.indexOf(";")) : "image/png";
1019
- return { data: b64, mimeType };
1020
- }
1021
- function sniffMime(b) {
1022
- if (b[0] === 137 && b[1] === 80) return "image/png";
1023
- if (b[0] === 71 && b[1] === 73) return "image/gif";
1024
- if (b[0] === 82 && b[1] === 73 && b[8] === 87) return "image/webp";
1025
- return "image/jpeg";
1026
- }
1027
1029
  function mapGeminiFinish(reason) {
1028
1030
  switch (reason) {
1029
1031
  case "MAX_TOKENS":
@@ -1045,19 +1047,68 @@ function deepinfraAdapter(config = {}) {
1045
1047
  }
1046
1048
 
1047
1049
  // src/providers/openrouter.ts
1050
+ var OPENROUTER_IMAGE_PRICE_ESTIMATE = {
1051
+ "recraft/recraft-v4.1": 0.035,
1052
+ "recraft/recraft-v4.1-vector": 0.08
1053
+ };
1048
1054
  function openrouterAdapter(config = {}) {
1049
- return makeOpenAICompatibleAdapter({
1055
+ const baseUrl = config.baseUrl ?? "https://openrouter.ai/api/v1";
1056
+ const headers = {
1057
+ "HTTP-Referer": config.referer ?? "https://broberg.ai",
1058
+ "X-Title": config.title ?? "@broberg/ai-sdk"
1059
+ };
1060
+ const base = makeOpenAICompatibleAdapter({
1050
1061
  name: "openrouter",
1051
- baseUrl: config.baseUrl ?? "https://openrouter.ai/api/v1",
1062
+ baseUrl,
1052
1063
  apiKey: config.apiKey,
1053
- extraHeaders: {
1054
- "HTTP-Referer": config.referer ?? "https://broberg.ai",
1055
- "X-Title": config.title ?? "@broberg/ai-sdk"
1056
- },
1064
+ extraHeaders: headers,
1065
+ // Forward the injectable fetch so an override applies uniformly to
1066
+ // chat/chatStream/vision as well as the image() path below.
1067
+ fetch: config.fetch,
1057
1068
  // OpenRouter returns ground-truth usage.cost (USD) when usage:{include:true}
1058
1069
  // is set — use it over the local pricing-table estimate (F010).
1059
1070
  costFromResponseField: true
1060
1071
  });
1072
+ async function image(req) {
1073
+ const apiKey = config.apiKey ?? process.env.OPENROUTER_API_KEY;
1074
+ if (!apiKey) throw new Error("openrouter adapter: OPENROUTER_API_KEY not set");
1075
+ const doFetch = config.fetch ?? fetch;
1076
+ const body = { model: req.spec.model, prompt: req.prompt };
1077
+ if (req.width !== void 0 && req.height !== void 0) {
1078
+ body.size = `${req.width}x${req.height}`;
1079
+ }
1080
+ if (req.seed !== void 0) body.seed = req.seed;
1081
+ if (req.outputFormat !== void 0) body.output_format = req.outputFormat;
1082
+ const res = await doFetch(`${baseUrl}/images`, {
1083
+ method: "POST",
1084
+ headers: {
1085
+ "content-type": "application/json",
1086
+ Authorization: `Bearer ${apiKey}`,
1087
+ ...headers
1088
+ },
1089
+ body: JSON.stringify(body)
1090
+ });
1091
+ if (!res.ok) {
1092
+ throw new Error(`openrouter images ${res.status}: ${(await res.text().catch(() => "")).slice(0, 300)}`);
1093
+ }
1094
+ const data = await res.json();
1095
+ const first = data.data?.[0];
1096
+ if (!first?.b64_json) {
1097
+ const errMsg = typeof data.error === "string" ? data.error : data.error?.message;
1098
+ throw new Error(`openrouter images: ${errMsg ?? "no image data in response"}`);
1099
+ }
1100
+ const usage = freshUsage({
1101
+ provider: "openrouter",
1102
+ model: req.spec.model,
1103
+ transport: "http",
1104
+ capability: "image",
1105
+ inputTokens: 0,
1106
+ outputTokens: 0
1107
+ });
1108
+ usage.costUsd = data.usage?.cost ?? config.pricePerImage ?? OPENROUTER_IMAGE_PRICE_ESTIMATE[req.spec.model] ?? 0;
1109
+ return { url: `data:${first.media_type ?? "image/png"};base64,${first.b64_json}`, usage };
1110
+ }
1111
+ return { ...base, image };
1061
1112
  }
1062
1113
 
1063
1114
  // src/providers/requesty.ts
@@ -1477,6 +1528,203 @@ function azureAdapter(config = {}) {
1477
1528
  return { name: "azure", tts, transcribe };
1478
1529
  }
1479
1530
 
1531
+ // src/providers/vertex.ts
1532
+ import { createSign } from "crypto";
1533
+ import { readFileSync } from "fs";
1534
+ var TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token";
1535
+ var DEFAULT_REGION2 = "europe-west1";
1536
+ var CLOUD_PLATFORM_SCOPE = "https://www.googleapis.com/auth/cloud-platform";
1537
+ var VERTEX_VEO_PRICE_PER_SEC = {
1538
+ "veo-3.1-generate-preview": 0.4,
1539
+ "veo-3.1-fast-generate-preview": 0.1,
1540
+ "veo-3.1-lite-generate-preview": 0.05,
1541
+ "veo-3.0-generate-001": 0.4,
1542
+ "veo-3.0-fast-generate-001": 0.1
1543
+ };
1544
+ function base64url(input) {
1545
+ const buf = typeof input === "string" ? Buffer.from(input) : input;
1546
+ return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1547
+ }
1548
+ function resolveCredentials(config) {
1549
+ const inline = config.credentials ?? process.env.GOOGLE_VERTEX_CREDENTIALS;
1550
+ if (inline) {
1551
+ try {
1552
+ return JSON.parse(inline);
1553
+ } catch {
1554
+ throw new Error("vertex adapter: GOOGLE_VERTEX_CREDENTIALS is not valid JSON");
1555
+ }
1556
+ }
1557
+ const path = process.env.GOOGLE_APPLICATION_CREDENTIALS;
1558
+ if (path) {
1559
+ let raw;
1560
+ try {
1561
+ raw = readFileSync(path, "utf8");
1562
+ } catch (err) {
1563
+ throw new Error(`vertex adapter: failed to read GOOGLE_APPLICATION_CREDENTIALS file: ${err.message}`);
1564
+ }
1565
+ return JSON.parse(raw);
1566
+ }
1567
+ throw new Error(
1568
+ "vertex adapter: service-account credentials not set (env GOOGLE_VERTEX_CREDENTIALS inline JSON, or GOOGLE_APPLICATION_CREDENTIALS file path)"
1569
+ );
1570
+ }
1571
+ async function mintAccessToken(creds, fetchImpl) {
1572
+ const now = Math.floor(Date.now() / 1e3);
1573
+ const header = { alg: "RS256", typ: "JWT" };
1574
+ const claims = {
1575
+ iss: creds.client_email,
1576
+ scope: CLOUD_PLATFORM_SCOPE,
1577
+ aud: TOKEN_ENDPOINT,
1578
+ iat: now,
1579
+ exp: now + 3600
1580
+ };
1581
+ const unsigned = `${base64url(JSON.stringify(header))}.${base64url(JSON.stringify(claims))}`;
1582
+ const signature = createSign("RSA-SHA256").update(unsigned).sign(creds.private_key);
1583
+ const jwt = `${unsigned}.${base64url(signature)}`;
1584
+ const res = await fetchImpl(TOKEN_ENDPOINT, {
1585
+ method: "POST",
1586
+ headers: { "content-type": "application/x-www-form-urlencoded" },
1587
+ body: new URLSearchParams({
1588
+ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
1589
+ assertion: jwt
1590
+ }).toString()
1591
+ });
1592
+ if (!res.ok) {
1593
+ throw new Error(`vertex adapter: token exchange failed ${res.status}: ${(await res.text().catch(() => "")).slice(0, 300)}`);
1594
+ }
1595
+ const data = await res.json();
1596
+ if (!data.access_token) throw new Error("vertex adapter: token exchange returned no access_token");
1597
+ return { token: data.access_token, expiresAt: Date.now() + (data.expires_in ?? 3600) * 1e3 };
1598
+ }
1599
+ function vertexAdapter(config = {}) {
1600
+ const fetchImpl = config.fetch ?? fetch;
1601
+ let cached = null;
1602
+ function region() {
1603
+ return config.region ?? process.env.GOOGLE_VERTEX_REGION ?? DEFAULT_REGION2;
1604
+ }
1605
+ function project() {
1606
+ const p = config.project ?? process.env.GOOGLE_VERTEX_PROJECT;
1607
+ if (!p) throw new Error("vertex adapter: project not set (config.project or env GOOGLE_VERTEX_PROJECT)");
1608
+ return p;
1609
+ }
1610
+ async function accessToken() {
1611
+ if (cached && cached.expiresAt - 6e4 > Date.now()) return cached.token;
1612
+ const creds = resolveCredentials(config);
1613
+ cached = await mintAccessToken(creds, fetchImpl);
1614
+ return cached.token;
1615
+ }
1616
+ async function animate(req) {
1617
+ const token = await accessToken();
1618
+ const proj = project();
1619
+ const reg = region();
1620
+ const pollIntervalMs = config.pollIntervalMs ?? 5e3;
1621
+ const deadline = Date.now() + (config.videoTimeoutMs ?? 3e5);
1622
+ const baseUrl = `https://${reg}-aiplatform.googleapis.com/v1`;
1623
+ const { data, mimeType } = await toInlineImage(req.image, fetchImpl);
1624
+ const parameters = {};
1625
+ if (req.durationSec !== void 0) parameters.durationSeconds = req.durationSec;
1626
+ if (req.resolution !== void 0) parameters.resolution = req.resolution;
1627
+ const body = {
1628
+ instances: [{ prompt: req.prompt ?? "", image: { bytesBase64Encoded: data, mimeType } }],
1629
+ ...Object.keys(parameters).length ? { parameters } : {}
1630
+ };
1631
+ const submit = await fetchImpl(
1632
+ `${baseUrl}/projects/${proj}/locations/${reg}/publishers/google/models/${req.spec.model}:predictLongRunning`,
1633
+ {
1634
+ method: "POST",
1635
+ headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
1636
+ body: JSON.stringify(body)
1637
+ }
1638
+ );
1639
+ if (!submit.ok) {
1640
+ throw new Error(`vertex animate ${submit.status}: ${(await submit.text().catch(() => "")).slice(0, 300)}`);
1641
+ }
1642
+ const op = await submit.json();
1643
+ if (!op.name) throw new Error("vertex animate: no operation name in submit response");
1644
+ let videoB64;
1645
+ let videoMime = "video/mp4";
1646
+ for (; ; ) {
1647
+ const poll = await fetchImpl(`${baseUrl}/${op.name}`, { headers: { authorization: `Bearer ${token}` } });
1648
+ if (!poll.ok) throw new Error(`vertex animate poll ${poll.status}`);
1649
+ const opData = await poll.json();
1650
+ if (opData.error) throw new Error(`vertex animate: ${opData.error.message ?? "operation error"}`);
1651
+ if (opData.done) {
1652
+ const video = opData.response?.videos?.[0];
1653
+ if (!video) {
1654
+ throw new Error(`vertex animate: done but no video in response: ${JSON.stringify(opData.response).slice(0, 300)}`);
1655
+ }
1656
+ if (!video.bytesBase64Encoded) {
1657
+ if (video.gcsUri) {
1658
+ throw new Error(
1659
+ `vertex animate: response returned a gcsUri ("${video.gcsUri}") \u2014 GCS download not yet supported (F031.x); this build only handles inline bytes`
1660
+ );
1661
+ }
1662
+ throw new Error(`vertex animate: done but no bytesBase64Encoded in response: ${JSON.stringify(opData.response).slice(0, 300)}`);
1663
+ }
1664
+ videoB64 = video.bytesBase64Encoded;
1665
+ videoMime = video.mimeType ?? "video/mp4";
1666
+ break;
1667
+ }
1668
+ if (Date.now() >= deadline) throw new Error("vertex animate: timed out");
1669
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
1670
+ }
1671
+ const usage = freshUsage({
1672
+ provider: "vertex",
1673
+ model: req.spec.model,
1674
+ transport: "http",
1675
+ capability: "animate",
1676
+ inputTokens: 0,
1677
+ outputTokens: 0
1678
+ });
1679
+ const perSec = config.pricePerSecond ?? VERTEX_VEO_PRICE_PER_SEC[req.spec.model] ?? 0;
1680
+ usage.costUsd = perSec * (req.durationSec ?? 8);
1681
+ return { url: `vertex://${op.name}`, bytes: Buffer.from(videoB64, "base64"), mimeType: videoMime, usage };
1682
+ }
1683
+ return { name: "vertex", animate };
1684
+ }
1685
+
1686
+ // src/providers/deepl.ts
1687
+ var DEEPL_PRICE_PER_1K_CHARS_ESTIMATE = 0.0217;
1688
+ function deeplAdapter(config = {}) {
1689
+ const fetchImpl = config.fetch ?? fetch;
1690
+ function key() {
1691
+ const k = config.apiKey ?? process.env.DEEPL_API_KEY;
1692
+ if (!k) throw new Error("deepl adapter: API key not set (env DEEPL_API_KEY)");
1693
+ return k;
1694
+ }
1695
+ function baseUrl(apiKey) {
1696
+ return config.baseUrl ?? (apiKey.endsWith(":fx") ? "https://api-free.deepl.com" : "https://api.deepl.com");
1697
+ }
1698
+ async function translate(req) {
1699
+ const apiKey = key();
1700
+ const body = { text: [req.text], target_lang: req.to.toUpperCase() };
1701
+ if (req.from) body.source_lang = req.from.toUpperCase();
1702
+ const res = await fetchImpl(`${baseUrl(apiKey)}/v2/translate`, {
1703
+ method: "POST",
1704
+ headers: { "content-type": "application/json", authorization: `DeepL-Auth-Key ${apiKey}` },
1705
+ body: JSON.stringify(body)
1706
+ });
1707
+ if (!res.ok) {
1708
+ const errBody = await res.text().catch(() => "");
1709
+ throw new Error(`deepl translate ${res.status}: ${errBody.slice(0, 300)}`);
1710
+ }
1711
+ const data = await res.json();
1712
+ const text = data.translations?.[0]?.text;
1713
+ if (text === void 0) throw new Error("deepl translate: response contained no translation");
1714
+ const usage = freshUsage({
1715
+ provider: "deepl",
1716
+ model: req.spec.model,
1717
+ transport: "http",
1718
+ capability: "translate",
1719
+ inputTokens: 0,
1720
+ outputTokens: 0
1721
+ });
1722
+ usage.costUsd = req.text.length / 1e3 * (config.pricePer1kChars ?? DEEPL_PRICE_PER_1K_CHARS_ESTIMATE);
1723
+ return { text, usage };
1724
+ }
1725
+ return { name: "deepl", translate };
1726
+ }
1727
+
1480
1728
  // src/providers/fal.ts
1481
1729
  import { deflateRawSync, crc32 } from "zlib";
1482
1730
  var FAL_IMAGE_PRICE_ESTIMATE = {
@@ -1487,7 +1735,12 @@ var FAL_IMAGE_PRICE_ESTIMATE = {
1487
1735
  "fal-ai/flux-pro/v1.1": 0.04
1488
1736
  };
1489
1737
  var FAL_TRAIN_PRICE_ESTIMATE = 2;
1490
- var FAL_VIDEO_PRICE_PER_SEC = {};
1738
+ var FAL_VIDEO_PRICE_PER_SEC = {
1739
+ // Kling 2.5 Turbo Pro i2v — the blessed FAL_KEY-only image→video route (F014
1740
+ // contentpush spike). $0.35 for the first 5 s + $0.07/additional s = a flat
1741
+ // $0.07/s (1080p); per-second-billed, commercial-use, not deprecated.
1742
+ "fal-ai/kling-video/v2.5-turbo/pro/image-to-video": 0.07
1743
+ };
1491
1744
  var FAL_VIDEO_DEFAULT_SEC = 8;
1492
1745
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
1493
1746
  function falAdapter(config = {}) {
@@ -1907,6 +2160,8 @@ var defaultProviders = {
1907
2160
  mistral: mistralAdapter(),
1908
2161
  elevenlabs: elevenlabsAdapter(),
1909
2162
  azure: azureAdapter(),
2163
+ vertex: vertexAdapter(),
2164
+ deepl: deeplAdapter(),
1910
2165
  fal: falAdapter(),
1911
2166
  bfl: bflAdapter()
1912
2167
  };
@@ -2582,6 +2837,7 @@ function createAI(config = {}) {
2582
2837
  estOut: estIn,
2583
2838
  invoke: async (spec) => {
2584
2839
  const adapter = pickProvider(spec.provider);
2840
+ if (adapter.translate) return adapter.translate({ text: input.text, to: input.to, from: input.from, spec });
2585
2841
  if (!adapter.chat) throw new Error(`createAI: provider "${spec.provider}" does not support chat (translate routes through chat)`);
2586
2842
  return adapter.chat({ messages, spec });
2587
2843
  }
@@ -2915,8 +3171,8 @@ var stubProviders = {
2915
3171
  };
2916
3172
 
2917
3173
  // src/version.ts
2918
- var VERSION = "0.21.1";
2919
- var SDK_TAG = "@broberg/ai-sdk@0.21.1";
3174
+ var VERSION = "0.23.0";
3175
+ var SDK_TAG = "@broberg/ai-sdk@0.23.0";
2920
3176
 
2921
3177
  // src/availability/refresh.ts
2922
3178
  var NOT_REFRESHED = { refreshed: false, checked: 0, markedUnavailable: [] };
@@ -3279,6 +3535,7 @@ export {
3279
3535
  computeCost,
3280
3536
  createAI,
3281
3537
  deepinfraAdapter,
3538
+ deeplAdapter,
3282
3539
  deepseekAdapter,
3283
3540
  defaultProviders,
3284
3541
  discordSink,
@@ -3327,6 +3584,7 @@ export {
3327
3584
  upmetricsCostClient,
3328
3585
  upmetricsSink,
3329
3586
  usdFromMicro,
3587
+ vertexAdapter,
3330
3588
  visionInputSchema
3331
3589
  };
3332
3590
  //# sourceMappingURL=index.js.map