@broberg/ai-sdk 0.36.5 → 0.36.7

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
@@ -17,7 +17,7 @@ export { AvailabilitySource, AvailabilityStatus, ModelStatus, ModelUnavailableEr
17
17
  type Region = "eu" | "us" | "cn" | "unknown";
18
18
  /** Region of the endpoint a call will actually hit. Never throws — a residency
19
19
  * reading must not be able to break a call that already succeeded. */
20
- declare function regionOfHost(url: string | undefined): Region;
20
+ declare function regionOfHost(hostOrUrl: string | undefined): Region;
21
21
  /** Classify a provider REGION STRING — a Vertex/GCP location like `europe-west1`, or
22
22
  * an Azure region like `westeurope`. Anything unrecognised is `"unknown"`: a region
23
23
  * name we have never seen is exactly the case where guessing is most tempting and
@@ -273,7 +273,9 @@ interface ImageResult {
273
273
  }
274
274
  /** Image-to-video generation (F024) — animate a still into a short clip. */
275
275
  interface AnimateRequest {
276
- /** Input image: a URL (passed through) or raw bytes (uploaded to fal storage). */
276
+ /** Input image: a URL (passed through) or raw bytes. Where bytes are uploaded is
277
+ * the ADAPTER's business — fal uses fal storage, the Gemini/Veo default inlines
278
+ * them — so do not read a storage provider out of this field. */
277
279
  image: string | Uint8Array;
278
280
  /** Motion/scene prompt, e.g. "the subject turns and smiles". */
279
281
  prompt?: string;
@@ -446,7 +448,11 @@ interface ProviderAdapter {
446
448
  translate?(req: TranslateRequest): Promise<TranslateResult>;
447
449
  vision?(req: ChatRequest): Promise<ChatResult>;
448
450
  image?(req: ImageRequest): Promise<ImageResult>;
449
- /** Image-to-video generation (F024) — animate a still into a short clip. fal. */
451
+ /** Image-to-video generation (F024) — animate a still into a short clip. gemini
452
+ * (Veo 3.1) by default; override to fal for Kling/Seedance. Name only the DEFAULT
453
+ * route here — provider-doc-drift.test.ts fails if this and DEFAULT_ANIMATE_SPEC
454
+ * disagree, because this sentence is the editor tooltip a consumer reads when
455
+ * deciding which API key to buy. */
450
456
  animate?(req: AnimateRequest): Promise<AnimateResult>;
451
457
  /** Train a style/brand LoRA from images (F021). fal. */
452
458
  trainStyle?(req: TrainStyleRequest): Promise<TrainStyleResult>;
@@ -2200,8 +2206,8 @@ declare const falStubAdapter: ProviderAdapter;
2200
2206
  * wires the live adapters. */
2201
2207
  declare const stubProviders: Record<string, ProviderAdapter>;
2202
2208
 
2203
- declare const VERSION: "0.36.5";
2204
- declare const SDK_TAG: "@broberg/ai-sdk@0.36.5";
2209
+ declare const VERSION: "0.36.7";
2210
+ declare const SDK_TAG: "@broberg/ai-sdk@0.36.7";
2205
2211
 
2206
2212
  /** Built-in defaults. Every entry is overridable via AiConfig.defaults or a
2207
2213
  * per-call override.
package/dist/index.js CHANGED
@@ -241,14 +241,20 @@ var HOST_REGION = {
241
241
  "openrouter.ai": "unknown",
242
242
  "router.requesty.ai": "unknown"
243
243
  };
244
- function regionOfHost(url) {
245
- if (!url) return "unknown";
244
+ function regionOfHost(hostOrUrl) {
245
+ if (!hostOrUrl) return "unknown";
246
+ const trimmed = hostOrUrl.trim();
247
+ if (!trimmed) return "unknown";
248
+ let host = "";
246
249
  try {
247
- const host = new URL(url).host.toLowerCase();
248
- return Object.hasOwn(HOST_REGION, host) ? HOST_REGION[host] : "unknown";
250
+ host = new URL(trimmed).host;
249
251
  } catch {
250
- return "unknown";
251
252
  }
253
+ if (!host) {
254
+ host = trimmed.split("/")[0].split("@").pop().split(":")[0];
255
+ }
256
+ host = host.toLowerCase();
257
+ return Object.hasOwn(HOST_REGION, host) ? HOST_REGION[host] : "unknown";
252
258
  }
253
259
  var FIXED_PROVIDER_REGION = {
254
260
  // NB: mistral and deepl are deliberately ABSENT — both take a config.baseUrl, so
@@ -303,6 +309,7 @@ function classifyRegionName(name) {
303
309
  if (!name) return "unknown";
304
310
  const n = name.trim().toLowerCase();
305
311
  if (!n) return "unknown";
312
+ if (n === "eu" || n === "us" || n === "cn" || n === "unknown") return n;
306
313
  if (n.startsWith("europe-")) return "eu";
307
314
  if (n.startsWith("us-")) return "us";
308
315
  if (EU_REGION_NAMES.has(n)) return "eu";
@@ -952,7 +959,7 @@ function geminiAdapter(config = {}) {
952
959
  const baseUrl = config.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta";
953
960
  function resolveKey() {
954
961
  const apiKey = config.apiKey ?? process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY;
955
- if (!apiKey) throw new Error("gemini adapter: API key not set (env GOOGLE_API_KEY)");
962
+ if (!apiKey) throw new Error("gemini adapter: API key not set (env GOOGLE_API_KEY or GEMINI_API_KEY)");
956
963
  return apiKey;
957
964
  }
958
965
  function buildBody(req) {
@@ -1991,7 +1998,7 @@ function falAdapter(config = {}) {
1991
1998
  });
1992
1999
  async function image(req) {
1993
2000
  const apiKey = resolveKey();
1994
- if (!apiKey) throw new Error("fal adapter: FAL_KEY not set");
2001
+ if (!apiKey) throw new Error("fal adapter: API key not set (env FAL_KEY or FAL_API_KEY)");
1995
2002
  const headers = authHeaders(apiKey);
1996
2003
  const body = { prompt: req.prompt };
1997
2004
  if (req.width !== void 0 && req.height !== void 0) {
@@ -2021,7 +2028,7 @@ function falAdapter(config = {}) {
2021
2028
  }
2022
2029
  async function animate(req) {
2023
2030
  const apiKey = resolveKey();
2024
- if (!apiKey) throw new Error("fal adapter: FAL_KEY not set");
2031
+ if (!apiKey) throw new Error("fal adapter: API key not set (env FAL_KEY or FAL_API_KEY)");
2025
2032
  const headers = authHeaders(apiKey);
2026
2033
  const imageUrl = typeof req.image === "string" && /^https?:\/\//i.test(req.image) ? req.image : await uploadToFalStorage(toBytes(req.image), sniffImageType(req.image), "input", apiKey);
2027
2034
  const body = { image_url: imageUrl };
@@ -2051,7 +2058,7 @@ function falAdapter(config = {}) {
2051
2058
  }
2052
2059
  async function trainStyle(req) {
2053
2060
  const apiKey = resolveKey();
2054
- if (!apiKey) throw new Error("fal adapter: FAL_KEY not set");
2061
+ if (!apiKey) throw new Error("fal adapter: API key not set (env FAL_KEY or FAL_API_KEY)");
2055
2062
  const headers = authHeaders(apiKey);
2056
2063
  const body = {
2057
2064
  images_data_url: await resolveImagesUrl(req.images, apiKey),
@@ -2986,8 +2993,8 @@ var aiConfigSchema = z.object({
2986
2993
  });
2987
2994
 
2988
2995
  // src/version.ts
2989
- var VERSION = "0.36.5";
2990
- var SDK_TAG = "@broberg/ai-sdk@0.36.5";
2996
+ var VERSION = "0.36.7";
2997
+ var SDK_TAG = "@broberg/ai-sdk@0.36.7";
2991
2998
 
2992
2999
  // src/cost/sinks/upmetrics.ts
2993
3000
  function upmetricsSink(config) {
@@ -3083,6 +3090,11 @@ var DEFAULT_ANIMATE_SPEC = {
3083
3090
  transport: "http"
3084
3091
  };
3085
3092
  var ANIMATE_AUDIO_DIRECTIVE = "No spoken dialogue, no talking, no voiceover. Include natural ambient background sounds that match the environment.";
3093
+ function routeHasAudio(spec) {
3094
+ const m = spec.model.toLowerCase();
3095
+ if (m.includes("kling") || m.includes("seedance") || m.includes("wan-")) return false;
3096
+ return true;
3097
+ }
3086
3098
  var DEFAULT_BFL_FINETUNE_SPEC = {
3087
3099
  provider: "bfl",
3088
3100
  model: "flux-pro-1.1-ultra-finetuned",
@@ -3398,7 +3410,7 @@ function createAI(config = {}) {
3398
3410
  },
3399
3411
  async animate(input) {
3400
3412
  input = animateInputSchema.parse(input);
3401
- const prompt = input.prompt?.trim() ? `${input.prompt.trim()} ${ANIMATE_AUDIO_DIRECTIVE}` : ANIMATE_AUDIO_DIRECTIVE;
3413
+ const basePrompt = input.prompt?.trim() ?? "";
3402
3414
  return runCapability({
3403
3415
  primary: withOverride(DEFAULT_ANIMATE_SPEC, input.override, "animate"),
3404
3416
  fallback: input.fallback,
@@ -3411,9 +3423,10 @@ function createAI(config = {}) {
3411
3423
  invoke: async (spec) => {
3412
3424
  const adapter = pickProvider(spec.provider);
3413
3425
  if (!adapter.animate) throw new Error(`createAI: provider "${spec.provider}" does not support animate`);
3426
+ const prompt = routeHasAudio(spec) ? `${basePrompt} ${ANIMATE_AUDIO_DIRECTIVE}`.trim() : basePrompt;
3414
3427
  return adapter.animate({
3415
3428
  image: input.image,
3416
- prompt,
3429
+ prompt: prompt || void 0,
3417
3430
  durationSec: input.durationSec,
3418
3431
  resolution: input.resolution,
3419
3432
  spec