@broberg/ai-sdk 0.16.0 → 0.17.1
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 +123 -9
- package/dist/index.js +180 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ interface TierSpec {
|
|
|
14
14
|
transport: Transport;
|
|
15
15
|
}
|
|
16
16
|
/** High-level capability a call exercises. Mirrors the capability layer (F5). */
|
|
17
|
-
type Capability = "chat" | "vision" | "video" | "translate" | "image" | "embedding" | "transcribe" | "ocr" | "moderation" | "podcast" | "tts" | "trainStyle" | "mockup" | "design" | "extract" | "classify" | "rerank";
|
|
17
|
+
type Capability = "chat" | "vision" | "video" | "translate" | "image" | "animate" | "embedding" | "transcribe" | "ocr" | "moderation" | "podcast" | "tts" | "trainStyle" | "mockup" | "design" | "extract" | "classify" | "rerank";
|
|
18
18
|
type Role = "system" | "user" | "assistant" | "tool";
|
|
19
19
|
/** A piece of message content. Text everywhere; image parts feed vision. */
|
|
20
20
|
type ContentPart = {
|
|
@@ -188,6 +188,30 @@ interface ImageResult {
|
|
|
188
188
|
url: string;
|
|
189
189
|
usage: Usage;
|
|
190
190
|
}
|
|
191
|
+
/** Image-to-video generation (F024) — animate a still into a short clip. */
|
|
192
|
+
interface AnimateRequest {
|
|
193
|
+
/** Input image: a URL (passed through) or raw bytes (uploaded to fal storage). */
|
|
194
|
+
image: string | Uint8Array;
|
|
195
|
+
/** Motion/scene prompt, e.g. "the subject turns and smiles". */
|
|
196
|
+
prompt?: string;
|
|
197
|
+
/** Clip length in seconds (provider-dependent; Veo ≤ 8s). */
|
|
198
|
+
durationSec?: number;
|
|
199
|
+
/** e.g. "720p" / "1080p" (provider-dependent). */
|
|
200
|
+
resolution?: string;
|
|
201
|
+
spec: TierSpec;
|
|
202
|
+
}
|
|
203
|
+
interface AnimateResult {
|
|
204
|
+
/** URL to the generated video. For fal this is a public hosted URL; for the
|
|
205
|
+
* Gemini/Veo route it is Google's file URI (needs the API key to fetch — the
|
|
206
|
+
* bytes are also returned below, already downloaded). */
|
|
207
|
+
url: string;
|
|
208
|
+
/** The downloaded video bytes — set by providers whose result URL is auth-gated
|
|
209
|
+
* or short-lived (Gemini/Veo). Absent for providers that return a public URL (fal). */
|
|
210
|
+
bytes?: Uint8Array;
|
|
211
|
+
/** MIME type of `bytes` (e.g. "video/mp4"). */
|
|
212
|
+
mimeType?: string;
|
|
213
|
+
usage: Usage;
|
|
214
|
+
}
|
|
191
215
|
/** Style/brand LoRA training (F021) — fal fal-ai/flux-lora-fast-training. */
|
|
192
216
|
interface TrainStyleRequest {
|
|
193
217
|
/** A hosted archive URL, or an array of image URLs the SDK zips in-memory. */
|
|
@@ -304,6 +328,8 @@ interface ProviderAdapter {
|
|
|
304
328
|
chatStream?(req: ChatRequest): AsyncIterable<ChatStreamEvent>;
|
|
305
329
|
vision?(req: ChatRequest): Promise<ChatResult>;
|
|
306
330
|
image?(req: ImageRequest): Promise<ImageResult>;
|
|
331
|
+
/** Image-to-video generation (F024) — animate a still into a short clip. fal. */
|
|
332
|
+
animate?(req: AnimateRequest): Promise<AnimateResult>;
|
|
307
333
|
/** Train a style/brand LoRA from images (F021). fal. */
|
|
308
334
|
trainStyle?(req: TrainStyleRequest): Promise<TrainStyleResult>;
|
|
309
335
|
embedding?(req: EmbeddingRequest): Promise<EmbeddingResult>;
|
|
@@ -417,12 +443,12 @@ declare const toolSchema: z.ZodObject<{
|
|
|
417
443
|
parameters: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
418
444
|
}, "strip", z.ZodTypeAny, {
|
|
419
445
|
name: string;
|
|
420
|
-
description: string;
|
|
421
446
|
parameters: Record<string, unknown>;
|
|
447
|
+
description: string;
|
|
422
448
|
}, {
|
|
423
449
|
name: string;
|
|
424
|
-
description: string;
|
|
425
450
|
parameters: Record<string, unknown>;
|
|
451
|
+
description: string;
|
|
426
452
|
}>;
|
|
427
453
|
declare const messageSchema: z.ZodObject<{
|
|
428
454
|
role: z.ZodEnum<["system", "user", "assistant", "tool"]>;
|
|
@@ -604,12 +630,12 @@ declare const chatInputSchema: z.ZodObject<{
|
|
|
604
630
|
parameters: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
605
631
|
}, "strip", z.ZodTypeAny, {
|
|
606
632
|
name: string;
|
|
607
|
-
description: string;
|
|
608
633
|
parameters: Record<string, unknown>;
|
|
634
|
+
description: string;
|
|
609
635
|
}, {
|
|
610
636
|
name: string;
|
|
611
|
-
description: string;
|
|
612
637
|
parameters: Record<string, unknown>;
|
|
638
|
+
description: string;
|
|
613
639
|
}>, "many">>;
|
|
614
640
|
maxTokens: z.ZodOptional<z.ZodNumber>;
|
|
615
641
|
temperature: z.ZodOptional<z.ZodNumber>;
|
|
@@ -637,8 +663,8 @@ declare const chatInputSchema: z.ZodObject<{
|
|
|
637
663
|
}[] | undefined;
|
|
638
664
|
tools?: {
|
|
639
665
|
name: string;
|
|
640
|
-
description: string;
|
|
641
666
|
parameters: Record<string, unknown>;
|
|
667
|
+
description: string;
|
|
642
668
|
}[] | undefined;
|
|
643
669
|
temperature?: number | undefined;
|
|
644
670
|
purpose?: string | undefined;
|
|
@@ -678,8 +704,8 @@ declare const chatInputSchema: z.ZodObject<{
|
|
|
678
704
|
}[] | undefined;
|
|
679
705
|
tools?: {
|
|
680
706
|
name: string;
|
|
681
|
-
description: string;
|
|
682
707
|
parameters: Record<string, unknown>;
|
|
708
|
+
description: string;
|
|
683
709
|
}[] | undefined;
|
|
684
710
|
temperature?: number | undefined;
|
|
685
711
|
purpose?: string | undefined;
|
|
@@ -1032,6 +1058,81 @@ declare const imageInputSchema: z.ZodObject<{
|
|
|
1032
1058
|
safetyTolerance?: number | undefined;
|
|
1033
1059
|
retryOnBlack?: boolean | undefined;
|
|
1034
1060
|
}>;
|
|
1061
|
+
declare const animateInputSchema: z.ZodObject<{
|
|
1062
|
+
tier: z.ZodOptional<z.ZodEnum<["fast", "smart", "powerful", "cheap", "vision", "video", "embedding"]>>;
|
|
1063
|
+
override: z.ZodOptional<z.ZodObject<{
|
|
1064
|
+
provider: z.ZodOptional<z.ZodString>;
|
|
1065
|
+
model: z.ZodOptional<z.ZodString>;
|
|
1066
|
+
transport: z.ZodOptional<z.ZodEnum<["http", "subprocess"]>>;
|
|
1067
|
+
}, "strip", z.ZodTypeAny, {
|
|
1068
|
+
provider?: string | undefined;
|
|
1069
|
+
model?: string | undefined;
|
|
1070
|
+
transport?: "http" | "subprocess" | undefined;
|
|
1071
|
+
}, {
|
|
1072
|
+
provider?: string | undefined;
|
|
1073
|
+
model?: string | undefined;
|
|
1074
|
+
transport?: "http" | "subprocess" | undefined;
|
|
1075
|
+
}>>;
|
|
1076
|
+
fallback: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodEnum<["fast", "smart", "powerful", "cheap", "vision", "video", "embedding"]>, z.ZodObject<{
|
|
1077
|
+
provider: z.ZodString;
|
|
1078
|
+
model: z.ZodString;
|
|
1079
|
+
transport: z.ZodEnum<["http", "subprocess"]>;
|
|
1080
|
+
}, "strip", z.ZodTypeAny, {
|
|
1081
|
+
provider: string;
|
|
1082
|
+
model: string;
|
|
1083
|
+
transport: "http" | "subprocess";
|
|
1084
|
+
}, {
|
|
1085
|
+
provider: string;
|
|
1086
|
+
model: string;
|
|
1087
|
+
transport: "http" | "subprocess";
|
|
1088
|
+
}>]>, "many">>;
|
|
1089
|
+
purpose: z.ZodOptional<z.ZodString>;
|
|
1090
|
+
labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
1091
|
+
/** Input image: a URL or raw bytes (uploaded to fal storage). */
|
|
1092
|
+
image: z.ZodUnion<[z.ZodString, z.ZodType<Uint8Array<ArrayBuffer>, z.ZodTypeDef, Uint8Array<ArrayBuffer>>]>;
|
|
1093
|
+
/** Motion/scene prompt. */
|
|
1094
|
+
prompt: z.ZodOptional<z.ZodString>;
|
|
1095
|
+
/** Clip length in seconds (provider-dependent; Veo ≤ 8s). */
|
|
1096
|
+
durationSec: z.ZodOptional<z.ZodNumber>;
|
|
1097
|
+
/** e.g. "720p" / "1080p" (provider-dependent). */
|
|
1098
|
+
resolution: z.ZodOptional<z.ZodString>;
|
|
1099
|
+
}, "strip", z.ZodTypeAny, {
|
|
1100
|
+
image: string | Uint8Array<ArrayBuffer>;
|
|
1101
|
+
prompt?: string | undefined;
|
|
1102
|
+
resolution?: string | undefined;
|
|
1103
|
+
purpose?: string | undefined;
|
|
1104
|
+
tier?: "fast" | "smart" | "powerful" | "cheap" | "vision" | "video" | "embedding" | undefined;
|
|
1105
|
+
override?: {
|
|
1106
|
+
provider?: string | undefined;
|
|
1107
|
+
model?: string | undefined;
|
|
1108
|
+
transport?: "http" | "subprocess" | undefined;
|
|
1109
|
+
} | undefined;
|
|
1110
|
+
fallback?: ("fast" | "smart" | "powerful" | "cheap" | "vision" | "video" | "embedding" | {
|
|
1111
|
+
provider: string;
|
|
1112
|
+
model: string;
|
|
1113
|
+
transport: "http" | "subprocess";
|
|
1114
|
+
})[] | undefined;
|
|
1115
|
+
labels?: Record<string, string> | undefined;
|
|
1116
|
+
durationSec?: number | undefined;
|
|
1117
|
+
}, {
|
|
1118
|
+
image: string | Uint8Array<ArrayBuffer>;
|
|
1119
|
+
prompt?: string | undefined;
|
|
1120
|
+
resolution?: string | undefined;
|
|
1121
|
+
purpose?: string | undefined;
|
|
1122
|
+
tier?: "fast" | "smart" | "powerful" | "cheap" | "vision" | "video" | "embedding" | undefined;
|
|
1123
|
+
override?: {
|
|
1124
|
+
provider?: string | undefined;
|
|
1125
|
+
model?: string | undefined;
|
|
1126
|
+
transport?: "http" | "subprocess" | undefined;
|
|
1127
|
+
} | undefined;
|
|
1128
|
+
fallback?: ("fast" | "smart" | "powerful" | "cheap" | "vision" | "video" | "embedding" | {
|
|
1129
|
+
provider: string;
|
|
1130
|
+
model: string;
|
|
1131
|
+
transport: "http" | "subprocess";
|
|
1132
|
+
})[] | undefined;
|
|
1133
|
+
labels?: Record<string, string> | undefined;
|
|
1134
|
+
durationSec?: number | undefined;
|
|
1135
|
+
}>;
|
|
1035
1136
|
declare const trainStyleInputSchema: z.ZodObject<{
|
|
1036
1137
|
tier: z.ZodOptional<z.ZodEnum<["fast", "smart", "powerful", "cheap", "vision", "video", "embedding"]>>;
|
|
1037
1138
|
override: z.ZodOptional<z.ZodObject<{
|
|
@@ -1591,6 +1692,7 @@ type VisionInput = z.infer<typeof visionInputSchema>;
|
|
|
1591
1692
|
type VideoInput = z.infer<typeof videoInputSchema>;
|
|
1592
1693
|
type TranslateInput = z.infer<typeof translateInputSchema>;
|
|
1593
1694
|
type ImageInput = z.infer<typeof imageInputSchema>;
|
|
1695
|
+
type AnimateInput = z.infer<typeof animateInputSchema>;
|
|
1594
1696
|
type TrainStyleInput = z.infer<typeof trainStyleInputSchema>;
|
|
1595
1697
|
type EmbeddingInput = z.infer<typeof embeddingInputSchema>;
|
|
1596
1698
|
type TranscribeInput = z.infer<typeof transcribeInputSchema>;
|
|
@@ -1610,6 +1712,8 @@ interface AiClient {
|
|
|
1610
1712
|
video(input: VideoInput): Promise<ChatResult>;
|
|
1611
1713
|
translate(input: TranslateInput): Promise<TranslateResult>;
|
|
1612
1714
|
image(input: ImageInput): Promise<ImageResult>;
|
|
1715
|
+
/** Image-to-video (F024) — animate a still into a short clip → { url, usage }. fal. */
|
|
1716
|
+
animate(input: AnimateInput): Promise<AnimateResult>;
|
|
1613
1717
|
/** Train a style/brand LoRA from images (F021) → { loraUrl, configUrl }. fal. */
|
|
1614
1718
|
trainStyle(input: TrainStyleInput): Promise<TrainStyleResult>;
|
|
1615
1719
|
embedding(input: EmbeddingInput): Promise<EmbeddingResult>;
|
|
@@ -1665,6 +1769,12 @@ declare function geminiAdapter(config?: {
|
|
|
1665
1769
|
baseUrl?: string;
|
|
1666
1770
|
fetch?: typeof fetch;
|
|
1667
1771
|
pricePerImage?: number;
|
|
1772
|
+
/** Override the per-SECOND Veo video price (else estimate per model, 0 if unknown). */
|
|
1773
|
+
pricePerSecond?: number;
|
|
1774
|
+
/** Poll interval for Veo long-running operations (default 5s). */
|
|
1775
|
+
pollIntervalMs?: number;
|
|
1776
|
+
/** Deadline for a Veo job (default 300000 = 5 min). */
|
|
1777
|
+
videoTimeoutMs?: number;
|
|
1668
1778
|
}): ProviderAdapter;
|
|
1669
1779
|
|
|
1670
1780
|
declare function deepinfraAdapter(config?: {
|
|
@@ -1715,11 +1825,15 @@ interface FalAdapterConfig {
|
|
|
1715
1825
|
timeoutMs?: number;
|
|
1716
1826
|
/** Deadline for training jobs — they take minutes (default 600000 = 10 min). */
|
|
1717
1827
|
trainTimeoutMs?: number;
|
|
1828
|
+
/** Deadline for video jobs — they take minutes (default 600000 = 10 min). */
|
|
1829
|
+
videoTimeoutMs?: number;
|
|
1718
1830
|
fetch?: typeof fetch;
|
|
1719
1831
|
/** Override the per-image USD price (else a built-in estimate per model, 0 if unknown). */
|
|
1720
1832
|
pricePerImage?: number;
|
|
1721
1833
|
/** Override the flat per-training USD price (else ~$2 estimate). */
|
|
1722
1834
|
pricePerTraining?: number;
|
|
1835
|
+
/** Override the per-SECOND video USD price (else a built-in estimate per model, 0 if unknown). */
|
|
1836
|
+
pricePerSecond?: number;
|
|
1723
1837
|
}
|
|
1724
1838
|
declare function falAdapter(config?: FalAdapterConfig): ProviderAdapter;
|
|
1725
1839
|
|
|
@@ -1781,8 +1895,8 @@ declare const falStubAdapter: ProviderAdapter;
|
|
|
1781
1895
|
* wires the live adapters. */
|
|
1782
1896
|
declare const stubProviders: Record<string, ProviderAdapter>;
|
|
1783
1897
|
|
|
1784
|
-
declare const VERSION: "0.
|
|
1785
|
-
declare const SDK_TAG: "@broberg/ai-sdk@0.
|
|
1898
|
+
declare const VERSION: "0.17.1";
|
|
1899
|
+
declare const SDK_TAG: "@broberg/ai-sdk@0.17.1";
|
|
1786
1900
|
|
|
1787
1901
|
/** Built-in defaults. Every entry is overridable via AiConfig.defaults or a
|
|
1788
1902
|
* per-call override. Model IDs are current at scaffold time; callers pin their
|
package/dist/index.js
CHANGED
|
@@ -870,6 +870,16 @@ function partsFrom(content) {
|
|
|
870
870
|
return { inlineData: { mimeType: p.mimeType ?? "image/png", data } };
|
|
871
871
|
});
|
|
872
872
|
}
|
|
873
|
+
var VEO_PRICE_PER_SEC = {
|
|
874
|
+
"veo-3.1-generate-preview": 0.4,
|
|
875
|
+
// standard; 4K = 0.60
|
|
876
|
+
"veo-3.1-fast-generate-preview": 0.1,
|
|
877
|
+
// 720p; 1080p = 0.12, 4K = 0.30
|
|
878
|
+
"veo-3.1-lite-generate-preview": 0.05,
|
|
879
|
+
// 720p; 1080p = 0.08
|
|
880
|
+
"veo-3.0-generate-001": 0.4,
|
|
881
|
+
"veo-3.0-fast-generate-001": 0.1
|
|
882
|
+
};
|
|
873
883
|
function geminiAdapter(config = {}) {
|
|
874
884
|
const baseUrl = config.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta";
|
|
875
885
|
function resolveKey() {
|
|
@@ -1025,7 +1035,82 @@ function geminiAdapter(config = {}) {
|
|
|
1025
1035
|
usage.costUsd = config.pricePerImage ?? GEMINI_IMAGE_PRICE_PER_IMAGE[req.spec.model] ?? 0;
|
|
1026
1036
|
return { url, usage };
|
|
1027
1037
|
}
|
|
1028
|
-
|
|
1038
|
+
async function animate(req) {
|
|
1039
|
+
const apiKey = resolveKey();
|
|
1040
|
+
const fetchImpl = config.fetch ?? fetch;
|
|
1041
|
+
const pollIntervalMs = config.pollIntervalMs ?? 5e3;
|
|
1042
|
+
const deadline = Date.now() + (config.videoTimeoutMs ?? 3e5);
|
|
1043
|
+
const { data, mimeType } = await toInlineImage(req.image, fetchImpl);
|
|
1044
|
+
const parameters = {};
|
|
1045
|
+
if (req.durationSec !== void 0) parameters.durationSeconds = req.durationSec;
|
|
1046
|
+
if (req.resolution !== void 0) parameters.resolution = req.resolution;
|
|
1047
|
+
const body = {
|
|
1048
|
+
instances: [{ prompt: req.prompt ?? "", image: { bytesBase64Encoded: data, mimeType } }],
|
|
1049
|
+
...Object.keys(parameters).length ? { parameters } : {}
|
|
1050
|
+
};
|
|
1051
|
+
const submit = await fetchImpl(
|
|
1052
|
+
`${baseUrl}/models/${req.spec.model}:predictLongRunning?key=${encodeURIComponent(apiKey)}`,
|
|
1053
|
+
{ method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) }
|
|
1054
|
+
);
|
|
1055
|
+
if (!submit.ok) {
|
|
1056
|
+
throw new Error(`gemini animate ${submit.status}: ${(await submit.text().catch(() => "")).slice(0, 300)}`);
|
|
1057
|
+
}
|
|
1058
|
+
const op = await submit.json();
|
|
1059
|
+
if (!op.name) throw new Error("gemini animate: no operation name in submit response");
|
|
1060
|
+
let videoUri;
|
|
1061
|
+
for (; ; ) {
|
|
1062
|
+
const poll = await fetchImpl(`${baseUrl}/${op.name}?key=${encodeURIComponent(apiKey)}`, {
|
|
1063
|
+
headers: { "content-type": "application/json" }
|
|
1064
|
+
});
|
|
1065
|
+
if (!poll.ok) throw new Error(`gemini animate poll ${poll.status}`);
|
|
1066
|
+
const opData = await poll.json();
|
|
1067
|
+
if (opData.error) throw new Error(`gemini animate: ${opData.error.message ?? "operation error"}`);
|
|
1068
|
+
if (opData.done) {
|
|
1069
|
+
videoUri = opData.response?.generateVideoResponse?.generatedSamples?.[0]?.video?.uri;
|
|
1070
|
+
if (!videoUri) throw new Error(`gemini animate: done but no video uri: ${JSON.stringify(opData.response).slice(0, 300)}`);
|
|
1071
|
+
break;
|
|
1072
|
+
}
|
|
1073
|
+
if (Date.now() >= deadline) throw new Error("gemini animate: timed out");
|
|
1074
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
1075
|
+
}
|
|
1076
|
+
const dl = await fetchImpl(videoUri.includes("key=") ? videoUri : `${videoUri}${videoUri.includes("?") ? "&" : "?"}key=${encodeURIComponent(apiKey)}`);
|
|
1077
|
+
if (!dl.ok) throw new Error(`gemini animate download ${dl.status}`);
|
|
1078
|
+
const bytes = new Uint8Array(await dl.arrayBuffer());
|
|
1079
|
+
const usage = freshUsage({
|
|
1080
|
+
provider: "gemini",
|
|
1081
|
+
model: req.spec.model,
|
|
1082
|
+
transport: "http",
|
|
1083
|
+
capability: "animate",
|
|
1084
|
+
inputTokens: 0,
|
|
1085
|
+
outputTokens: 0
|
|
1086
|
+
});
|
|
1087
|
+
const perSec = config.pricePerSecond ?? VEO_PRICE_PER_SEC[req.spec.model] ?? 0;
|
|
1088
|
+
usage.costUsd = perSec * (req.durationSec ?? 8);
|
|
1089
|
+
return { url: videoUri, bytes, mimeType: "video/mp4", usage };
|
|
1090
|
+
}
|
|
1091
|
+
return { name: "gemini", chat, chatStream, image, animate, vision: chat };
|
|
1092
|
+
}
|
|
1093
|
+
async function toInlineImage(image, fetchImpl) {
|
|
1094
|
+
if (typeof image !== "string") {
|
|
1095
|
+
return { data: Buffer.from(image).toString("base64"), mimeType: sniffMime(image) };
|
|
1096
|
+
}
|
|
1097
|
+
if (/^https?:\/\//i.test(image)) {
|
|
1098
|
+
const res = await fetchImpl(image);
|
|
1099
|
+
if (!res.ok) throw new Error(`gemini animate: failed to fetch image (${res.status})`);
|
|
1100
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
1101
|
+
const mimeType2 = res.headers.get("content-type") ?? sniffMime(bytes);
|
|
1102
|
+
return { data: Buffer.from(bytes).toString("base64"), mimeType: mimeType2 };
|
|
1103
|
+
}
|
|
1104
|
+
const comma = image.startsWith("data:") ? image.indexOf(",") : -1;
|
|
1105
|
+
const b64 = comma >= 0 ? image.slice(comma + 1) : image;
|
|
1106
|
+
const mimeType = image.startsWith("data:") ? image.slice(5, image.indexOf(";")) : "image/png";
|
|
1107
|
+
return { data: b64, mimeType };
|
|
1108
|
+
}
|
|
1109
|
+
function sniffMime(b) {
|
|
1110
|
+
if (b[0] === 137 && b[1] === 80) return "image/png";
|
|
1111
|
+
if (b[0] === 71 && b[1] === 73) return "image/gif";
|
|
1112
|
+
if (b[0] === 82 && b[1] === 73 && b[8] === 87) return "image/webp";
|
|
1113
|
+
return "image/jpeg";
|
|
1029
1114
|
}
|
|
1030
1115
|
function mapGeminiFinish(reason) {
|
|
1031
1116
|
switch (reason) {
|
|
@@ -1320,6 +1405,8 @@ var FAL_IMAGE_PRICE_ESTIMATE = {
|
|
|
1320
1405
|
"fal-ai/flux-pro/v1.1": 0.04
|
|
1321
1406
|
};
|
|
1322
1407
|
var FAL_TRAIN_PRICE_ESTIMATE = 2;
|
|
1408
|
+
var FAL_VIDEO_PRICE_PER_SEC = {};
|
|
1409
|
+
var FAL_VIDEO_DEFAULT_SEC = 8;
|
|
1323
1410
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1324
1411
|
function falAdapter(config = {}) {
|
|
1325
1412
|
const doFetch = config.fetch ?? fetch;
|
|
@@ -1362,6 +1449,36 @@ function falAdapter(config = {}) {
|
|
|
1362
1449
|
usage.costUsd = (config.pricePerImage ?? FAL_IMAGE_PRICE_ESTIMATE[req.spec.model] ?? 0) * calls;
|
|
1363
1450
|
return { url, usage };
|
|
1364
1451
|
}
|
|
1452
|
+
async function animate(req) {
|
|
1453
|
+
const apiKey = resolveKey();
|
|
1454
|
+
if (!apiKey) throw new Error("fal adapter: FAL_KEY not set");
|
|
1455
|
+
const headers = authHeaders(apiKey);
|
|
1456
|
+
const imageUrl = typeof req.image === "string" && /^https?:\/\//i.test(req.image) ? req.image : await uploadToFalStorage(toBytes(req.image), sniffImageType(req.image), "input", apiKey);
|
|
1457
|
+
const body = { image_url: imageUrl };
|
|
1458
|
+
if (req.prompt !== void 0) body.prompt = req.prompt;
|
|
1459
|
+
if (req.durationSec !== void 0) body.duration = req.durationSec;
|
|
1460
|
+
if (req.resolution !== void 0) body.resolution = req.resolution;
|
|
1461
|
+
const result = await queueResult(req.spec.model, headers, body, config.videoTimeoutMs ?? 6e5);
|
|
1462
|
+
const url = extractVideoUrl(result);
|
|
1463
|
+
if (!url) {
|
|
1464
|
+
throw new Error(
|
|
1465
|
+
`fal animate: no video url in result \u2014 keys [${Object.keys(
|
|
1466
|
+
result ?? {}
|
|
1467
|
+
).join(", ")}]: ${JSON.stringify(result).slice(0, 500)}`
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1470
|
+
const usage = freshUsage({
|
|
1471
|
+
provider: "fal",
|
|
1472
|
+
model: req.spec.model,
|
|
1473
|
+
transport: "http",
|
|
1474
|
+
capability: "animate",
|
|
1475
|
+
inputTokens: 0,
|
|
1476
|
+
outputTokens: 0
|
|
1477
|
+
});
|
|
1478
|
+
const perSec = config.pricePerSecond ?? FAL_VIDEO_PRICE_PER_SEC[req.spec.model] ?? 0;
|
|
1479
|
+
usage.costUsd = perSec * (req.durationSec ?? FAL_VIDEO_DEFAULT_SEC);
|
|
1480
|
+
return { url, usage };
|
|
1481
|
+
}
|
|
1365
1482
|
async function trainStyle(req) {
|
|
1366
1483
|
const apiKey = resolveKey();
|
|
1367
1484
|
if (!apiKey) throw new Error("fal adapter: FAL_KEY not set");
|
|
@@ -1481,7 +1598,24 @@ function falAdapter(config = {}) {
|
|
|
1481
1598
|
}
|
|
1482
1599
|
return resultRes.json();
|
|
1483
1600
|
}
|
|
1484
|
-
return { name: "fal", image, trainStyle };
|
|
1601
|
+
return { name: "fal", image, animate, trainStyle };
|
|
1602
|
+
}
|
|
1603
|
+
function toBytes(img) {
|
|
1604
|
+
if (typeof img !== "string") return img;
|
|
1605
|
+
const b64 = img.startsWith("data:") ? img.slice(img.indexOf(",") + 1) : img;
|
|
1606
|
+
return new Uint8Array(Buffer.from(b64, "base64"));
|
|
1607
|
+
}
|
|
1608
|
+
function sniffImageType(img) {
|
|
1609
|
+
const b = toBytes(img);
|
|
1610
|
+
if (b[0] === 137 && b[1] === 80) return "image/png";
|
|
1611
|
+
if (b[0] === 71 && b[1] === 73) return "image/gif";
|
|
1612
|
+
if (b[0] === 82 && b[1] === 73 && b[8] === 87) return "image/webp";
|
|
1613
|
+
return "image/jpeg";
|
|
1614
|
+
}
|
|
1615
|
+
function extractVideoUrl(result) {
|
|
1616
|
+
const r = result;
|
|
1617
|
+
const root = r?.data ?? r?.response ?? r?.output ?? r;
|
|
1618
|
+
return urlOf(root?.video) ?? urlOf(root?.videos?.[0]) ?? deepFindUrl(root, (u) => /\.(mp4|webm|mov)(\?|$)/i.test(u));
|
|
1485
1619
|
}
|
|
1486
1620
|
function urlOf(v) {
|
|
1487
1621
|
if (typeof v === "string") return v;
|
|
@@ -2019,6 +2153,17 @@ var imageInputSchema = z.object({
|
|
|
2019
2153
|
retryOnBlack: z.boolean().optional(),
|
|
2020
2154
|
...callOptions
|
|
2021
2155
|
});
|
|
2156
|
+
var animateInputSchema = z.object({
|
|
2157
|
+
/** Input image: a URL or raw bytes (uploaded to fal storage). */
|
|
2158
|
+
image: z.union([z.string(), z.instanceof(Uint8Array)]),
|
|
2159
|
+
/** Motion/scene prompt. */
|
|
2160
|
+
prompt: z.string().optional(),
|
|
2161
|
+
/** Clip length in seconds (provider-dependent; Veo ≤ 8s). */
|
|
2162
|
+
durationSec: z.number().positive().optional(),
|
|
2163
|
+
/** e.g. "720p" / "1080p" (provider-dependent). */
|
|
2164
|
+
resolution: z.string().optional(),
|
|
2165
|
+
...callOptions
|
|
2166
|
+
});
|
|
2022
2167
|
var trainStyleInputSchema = z.object({
|
|
2023
2168
|
/** A hosted archive URL, or an array of image URLs the SDK zips in-memory. */
|
|
2024
2169
|
images: z.union([z.string(), z.array(z.string())]),
|
|
@@ -2097,6 +2242,12 @@ var DEFAULT_TRAINSTYLE_SPEC = {
|
|
|
2097
2242
|
model: "fal-ai/flux-lora-fast-training",
|
|
2098
2243
|
transport: "http"
|
|
2099
2244
|
};
|
|
2245
|
+
var DEFAULT_ANIMATE_SPEC = {
|
|
2246
|
+
provider: "gemini",
|
|
2247
|
+
model: "veo-3.1-generate-preview",
|
|
2248
|
+
transport: "http"
|
|
2249
|
+
};
|
|
2250
|
+
var ANIMATE_AUDIO_DIRECTIVE = "No spoken dialogue, no talking, no voiceover. Include natural ambient background sounds that match the environment.";
|
|
2100
2251
|
var DEFAULT_BFL_FINETUNE_SPEC = {
|
|
2101
2252
|
provider: "bfl",
|
|
2102
2253
|
model: "flux-pro-1.1-ultra-finetuned",
|
|
@@ -2383,6 +2534,31 @@ function createAI(config = {}) {
|
|
|
2383
2534
|
}
|
|
2384
2535
|
});
|
|
2385
2536
|
},
|
|
2537
|
+
async animate(input) {
|
|
2538
|
+
input = animateInputSchema.parse(input);
|
|
2539
|
+
const prompt = input.prompt?.trim() ? `${input.prompt.trim()} ${ANIMATE_AUDIO_DIRECTIVE}` : ANIMATE_AUDIO_DIRECTIVE;
|
|
2540
|
+
return runCapability({
|
|
2541
|
+
primary: { ...DEFAULT_ANIMATE_SPEC, ...input.override },
|
|
2542
|
+
fallback: input.fallback,
|
|
2543
|
+
capability: "animate",
|
|
2544
|
+
purpose: input.purpose,
|
|
2545
|
+
labels: input.labels,
|
|
2546
|
+
estIn: 0,
|
|
2547
|
+
// video cost is per-second, not token-based
|
|
2548
|
+
estOut: 0,
|
|
2549
|
+
invoke: async (spec) => {
|
|
2550
|
+
const adapter = pickProvider(spec.provider);
|
|
2551
|
+
if (!adapter.animate) throw new Error(`createAI: provider "${spec.provider}" does not support animate`);
|
|
2552
|
+
return adapter.animate({
|
|
2553
|
+
image: input.image,
|
|
2554
|
+
prompt,
|
|
2555
|
+
durationSec: input.durationSec,
|
|
2556
|
+
resolution: input.resolution,
|
|
2557
|
+
spec
|
|
2558
|
+
});
|
|
2559
|
+
}
|
|
2560
|
+
});
|
|
2561
|
+
},
|
|
2386
2562
|
async trainStyle(input) {
|
|
2387
2563
|
input = trainStyleInputSchema.parse(input);
|
|
2388
2564
|
return runCapability({
|
|
@@ -2633,8 +2809,8 @@ var stubProviders = {
|
|
|
2633
2809
|
};
|
|
2634
2810
|
|
|
2635
2811
|
// src/version.ts
|
|
2636
|
-
var VERSION = "0.
|
|
2637
|
-
var SDK_TAG = "@broberg/ai-sdk@0.
|
|
2812
|
+
var VERSION = "0.17.1";
|
|
2813
|
+
var SDK_TAG = "@broberg/ai-sdk@0.17.1";
|
|
2638
2814
|
|
|
2639
2815
|
// src/availability/refresh.ts
|
|
2640
2816
|
var NOT_REFRESHED = { refreshed: false, checked: 0, markedUnavailable: [] };
|