@broberg/ai-sdk 0.9.1 → 0.10.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 +140 -4
- package/dist/index.js +197 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ interface TierSpec {
|
|
|
13
13
|
transport: Transport;
|
|
14
14
|
}
|
|
15
15
|
/** High-level capability a call exercises. Mirrors the capability layer (F5). */
|
|
16
|
-
type Capability = "chat" | "vision" | "video" | "translate" | "image" | "embedding" | "transcribe" | "ocr" | "moderation" | "podcast" | "tts" | "mockup" | "design" | "extract" | "classify" | "rerank";
|
|
16
|
+
type Capability = "chat" | "vision" | "video" | "translate" | "image" | "embedding" | "transcribe" | "ocr" | "moderation" | "podcast" | "tts" | "trainStyle" | "mockup" | "design" | "extract" | "classify" | "rerank";
|
|
17
17
|
type Role = "system" | "user" | "assistant" | "tool";
|
|
18
18
|
/** A piece of message content. Text everywhere; image parts feed vision. */
|
|
19
19
|
type ContentPart = {
|
|
@@ -150,16 +150,44 @@ type ChatStreamEvent = {
|
|
|
150
150
|
message: string;
|
|
151
151
|
status?: number;
|
|
152
152
|
};
|
|
153
|
+
/** A trained LoRA to merge at inference time (F021). */
|
|
154
|
+
interface LoraWeight {
|
|
155
|
+
/** URL (or fal path) to the LoRA weights. */
|
|
156
|
+
path: string;
|
|
157
|
+
/** Scales the LoRA before merging (default 1). */
|
|
158
|
+
scale?: number;
|
|
159
|
+
}
|
|
153
160
|
interface ImageRequest {
|
|
154
161
|
prompt: string;
|
|
155
162
|
spec: TierSpec;
|
|
156
163
|
width?: number;
|
|
157
164
|
height?: number;
|
|
165
|
+
/** LoRAs to merge at inference (F021) — e.g. a trained brand/style LoRA. */
|
|
166
|
+
loras?: LoraWeight[];
|
|
158
167
|
}
|
|
159
168
|
interface ImageResult {
|
|
160
169
|
url: string;
|
|
161
170
|
usage: Usage;
|
|
162
171
|
}
|
|
172
|
+
/** Style/brand LoRA training (F021) — fal fal-ai/flux-lora-fast-training. */
|
|
173
|
+
interface TrainStyleRequest {
|
|
174
|
+
/** A hosted archive URL, or an array of image URLs the SDK zips in-memory. */
|
|
175
|
+
images: string | string[];
|
|
176
|
+
spec: TierSpec;
|
|
177
|
+
/** Style LoRA (disables captioning/masks). Default true. */
|
|
178
|
+
isStyle?: boolean;
|
|
179
|
+
triggerWord?: string;
|
|
180
|
+
/** Training steps (~1000 typical). */
|
|
181
|
+
steps?: number;
|
|
182
|
+
createMasks?: boolean;
|
|
183
|
+
}
|
|
184
|
+
interface TrainStyleResult {
|
|
185
|
+
/** URL to the trained LoRA weights — pass to ai.image({ lora }). */
|
|
186
|
+
loraUrl: string;
|
|
187
|
+
/** URL to the training config file. */
|
|
188
|
+
configUrl: string;
|
|
189
|
+
usage: Usage;
|
|
190
|
+
}
|
|
163
191
|
interface EmbeddingRequest {
|
|
164
192
|
input: string[];
|
|
165
193
|
spec: TierSpec;
|
|
@@ -257,6 +285,8 @@ interface ProviderAdapter {
|
|
|
257
285
|
chatStream?(req: ChatRequest): AsyncIterable<ChatStreamEvent>;
|
|
258
286
|
vision?(req: ChatRequest): Promise<ChatResult>;
|
|
259
287
|
image?(req: ImageRequest): Promise<ImageResult>;
|
|
288
|
+
/** Train a style/brand LoRA from images (F021). fal. */
|
|
289
|
+
trainStyle?(req: TrainStyleRequest): Promise<TrainStyleResult>;
|
|
260
290
|
embedding?(req: EmbeddingRequest): Promise<EmbeddingResult>;
|
|
261
291
|
transcribe?(req: TranscribeRequest): Promise<TranscribeResult>;
|
|
262
292
|
ocr?(req: OcrRequest): Promise<OcrResult>;
|
|
@@ -886,9 +916,26 @@ declare const imageInputSchema: z.ZodObject<{
|
|
|
886
916
|
prompt: z.ZodString;
|
|
887
917
|
width: z.ZodOptional<z.ZodNumber>;
|
|
888
918
|
height: z.ZodOptional<z.ZodNumber>;
|
|
919
|
+
/** LoRAs to merge at inference (F021). */
|
|
920
|
+
loras: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
921
|
+
path: z.ZodString;
|
|
922
|
+
scale: z.ZodOptional<z.ZodNumber>;
|
|
923
|
+
}, "strip", z.ZodTypeAny, {
|
|
924
|
+
path: string;
|
|
925
|
+
scale?: number | undefined;
|
|
926
|
+
}, {
|
|
927
|
+
path: string;
|
|
928
|
+
scale?: number | undefined;
|
|
929
|
+
}>, "many">>;
|
|
930
|
+
/** Shorthand for a single LoRA — normalized to loras:[{path, scale:1}]. */
|
|
931
|
+
lora: z.ZodOptional<z.ZodString>;
|
|
889
932
|
}, "strip", z.ZodTypeAny, {
|
|
890
933
|
prompt: string;
|
|
891
934
|
purpose?: string | undefined;
|
|
935
|
+
loras?: {
|
|
936
|
+
path: string;
|
|
937
|
+
scale?: number | undefined;
|
|
938
|
+
}[] | undefined;
|
|
892
939
|
tier?: "fast" | "smart" | "powerful" | "cheap" | "vision" | "video" | "embedding" | undefined;
|
|
893
940
|
override?: {
|
|
894
941
|
provider?: string | undefined;
|
|
@@ -903,9 +950,14 @@ declare const imageInputSchema: z.ZodObject<{
|
|
|
903
950
|
labels?: Record<string, string> | undefined;
|
|
904
951
|
width?: number | undefined;
|
|
905
952
|
height?: number | undefined;
|
|
953
|
+
lora?: string | undefined;
|
|
906
954
|
}, {
|
|
907
955
|
prompt: string;
|
|
908
956
|
purpose?: string | undefined;
|
|
957
|
+
loras?: {
|
|
958
|
+
path: string;
|
|
959
|
+
scale?: number | undefined;
|
|
960
|
+
}[] | undefined;
|
|
909
961
|
tier?: "fast" | "smart" | "powerful" | "cheap" | "vision" | "video" | "embedding" | undefined;
|
|
910
962
|
override?: {
|
|
911
963
|
provider?: string | undefined;
|
|
@@ -920,6 +972,83 @@ declare const imageInputSchema: z.ZodObject<{
|
|
|
920
972
|
labels?: Record<string, string> | undefined;
|
|
921
973
|
width?: number | undefined;
|
|
922
974
|
height?: number | undefined;
|
|
975
|
+
lora?: string | undefined;
|
|
976
|
+
}>;
|
|
977
|
+
declare const trainStyleInputSchema: z.ZodObject<{
|
|
978
|
+
tier: z.ZodOptional<z.ZodEnum<["fast", "smart", "powerful", "cheap", "vision", "video", "embedding"]>>;
|
|
979
|
+
override: z.ZodOptional<z.ZodObject<{
|
|
980
|
+
provider: z.ZodOptional<z.ZodString>;
|
|
981
|
+
model: z.ZodOptional<z.ZodString>;
|
|
982
|
+
transport: z.ZodOptional<z.ZodEnum<["http", "subprocess"]>>;
|
|
983
|
+
}, "strip", z.ZodTypeAny, {
|
|
984
|
+
provider?: string | undefined;
|
|
985
|
+
model?: string | undefined;
|
|
986
|
+
transport?: "http" | "subprocess" | undefined;
|
|
987
|
+
}, {
|
|
988
|
+
provider?: string | undefined;
|
|
989
|
+
model?: string | undefined;
|
|
990
|
+
transport?: "http" | "subprocess" | undefined;
|
|
991
|
+
}>>;
|
|
992
|
+
fallback: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodEnum<["fast", "smart", "powerful", "cheap", "vision", "video", "embedding"]>, z.ZodObject<{
|
|
993
|
+
provider: z.ZodString;
|
|
994
|
+
model: z.ZodString;
|
|
995
|
+
transport: z.ZodEnum<["http", "subprocess"]>;
|
|
996
|
+
}, "strip", z.ZodTypeAny, {
|
|
997
|
+
provider: string;
|
|
998
|
+
model: string;
|
|
999
|
+
transport: "http" | "subprocess";
|
|
1000
|
+
}, {
|
|
1001
|
+
provider: string;
|
|
1002
|
+
model: string;
|
|
1003
|
+
transport: "http" | "subprocess";
|
|
1004
|
+
}>]>, "many">>;
|
|
1005
|
+
purpose: z.ZodOptional<z.ZodString>;
|
|
1006
|
+
labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
1007
|
+
/** A hosted archive URL, or an array of image URLs the SDK zips in-memory. */
|
|
1008
|
+
images: z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>;
|
|
1009
|
+
/** Style LoRA (disables captioning/masks). Default true. */
|
|
1010
|
+
isStyle: z.ZodOptional<z.ZodBoolean>;
|
|
1011
|
+
triggerWord: z.ZodOptional<z.ZodString>;
|
|
1012
|
+
steps: z.ZodOptional<z.ZodNumber>;
|
|
1013
|
+
createMasks: z.ZodOptional<z.ZodBoolean>;
|
|
1014
|
+
}, "strip", z.ZodTypeAny, {
|
|
1015
|
+
images: string | string[];
|
|
1016
|
+
purpose?: string | undefined;
|
|
1017
|
+
steps?: number | undefined;
|
|
1018
|
+
tier?: "fast" | "smart" | "powerful" | "cheap" | "vision" | "video" | "embedding" | undefined;
|
|
1019
|
+
override?: {
|
|
1020
|
+
provider?: string | undefined;
|
|
1021
|
+
model?: string | undefined;
|
|
1022
|
+
transport?: "http" | "subprocess" | undefined;
|
|
1023
|
+
} | undefined;
|
|
1024
|
+
fallback?: ("fast" | "smart" | "powerful" | "cheap" | "vision" | "video" | "embedding" | {
|
|
1025
|
+
provider: string;
|
|
1026
|
+
model: string;
|
|
1027
|
+
transport: "http" | "subprocess";
|
|
1028
|
+
})[] | undefined;
|
|
1029
|
+
labels?: Record<string, string> | undefined;
|
|
1030
|
+
isStyle?: boolean | undefined;
|
|
1031
|
+
triggerWord?: string | undefined;
|
|
1032
|
+
createMasks?: boolean | undefined;
|
|
1033
|
+
}, {
|
|
1034
|
+
images: string | string[];
|
|
1035
|
+
purpose?: string | undefined;
|
|
1036
|
+
steps?: number | undefined;
|
|
1037
|
+
tier?: "fast" | "smart" | "powerful" | "cheap" | "vision" | "video" | "embedding" | undefined;
|
|
1038
|
+
override?: {
|
|
1039
|
+
provider?: string | undefined;
|
|
1040
|
+
model?: string | undefined;
|
|
1041
|
+
transport?: "http" | "subprocess" | undefined;
|
|
1042
|
+
} | undefined;
|
|
1043
|
+
fallback?: ("fast" | "smart" | "powerful" | "cheap" | "vision" | "video" | "embedding" | {
|
|
1044
|
+
provider: string;
|
|
1045
|
+
model: string;
|
|
1046
|
+
transport: "http" | "subprocess";
|
|
1047
|
+
})[] | undefined;
|
|
1048
|
+
labels?: Record<string, string> | undefined;
|
|
1049
|
+
isStyle?: boolean | undefined;
|
|
1050
|
+
triggerWord?: string | undefined;
|
|
1051
|
+
createMasks?: boolean | undefined;
|
|
923
1052
|
}>;
|
|
924
1053
|
declare const embeddingInputSchema: z.ZodObject<{
|
|
925
1054
|
tier: z.ZodOptional<z.ZodEnum<["fast", "smart", "powerful", "cheap", "vision", "video", "embedding"]>>;
|
|
@@ -1386,6 +1515,7 @@ type VisionInput = z.infer<typeof visionInputSchema>;
|
|
|
1386
1515
|
type VideoInput = z.infer<typeof videoInputSchema>;
|
|
1387
1516
|
type TranslateInput = z.infer<typeof translateInputSchema>;
|
|
1388
1517
|
type ImageInput = z.infer<typeof imageInputSchema>;
|
|
1518
|
+
type TrainStyleInput = z.infer<typeof trainStyleInputSchema>;
|
|
1389
1519
|
type EmbeddingInput = z.infer<typeof embeddingInputSchema>;
|
|
1390
1520
|
type TranscribeInput = z.infer<typeof transcribeInputSchema>;
|
|
1391
1521
|
type OcrInput = z.infer<typeof ocrInputSchema>;
|
|
@@ -1404,6 +1534,8 @@ interface AiClient {
|
|
|
1404
1534
|
video(input: VideoInput): Promise<ChatResult>;
|
|
1405
1535
|
translate(input: TranslateInput): Promise<TranslateResult>;
|
|
1406
1536
|
image(input: ImageInput): Promise<ImageResult>;
|
|
1537
|
+
/** Train a style/brand LoRA from images (F021) → { loraUrl, configUrl }. fal. */
|
|
1538
|
+
trainStyle(input: TrainStyleInput): Promise<TrainStyleResult>;
|
|
1407
1539
|
embedding(input: EmbeddingInput): Promise<EmbeddingResult>;
|
|
1408
1540
|
transcribe(input: TranscribeInput): Promise<TranscribeResult>;
|
|
1409
1541
|
/** OCR (F016.2) — document/image → structured markdown, billed per page. Mistral. */
|
|
@@ -1505,9 +1637,13 @@ interface FalAdapterConfig {
|
|
|
1505
1637
|
queueBaseUrl?: string;
|
|
1506
1638
|
pollIntervalMs?: number;
|
|
1507
1639
|
timeoutMs?: number;
|
|
1640
|
+
/** Deadline for training jobs — they take minutes (default 600000 = 10 min). */
|
|
1641
|
+
trainTimeoutMs?: number;
|
|
1508
1642
|
fetch?: typeof fetch;
|
|
1509
1643
|
/** Override the per-image USD price (else a built-in estimate per model, 0 if unknown). */
|
|
1510
1644
|
pricePerImage?: number;
|
|
1645
|
+
/** Override the flat per-training USD price (else ~$2 estimate). */
|
|
1646
|
+
pricePerTraining?: number;
|
|
1511
1647
|
}
|
|
1512
1648
|
declare function falAdapter(config?: FalAdapterConfig): ProviderAdapter;
|
|
1513
1649
|
|
|
@@ -1544,8 +1680,8 @@ declare const falStubAdapter: ProviderAdapter;
|
|
|
1544
1680
|
* wires the live adapters. */
|
|
1545
1681
|
declare const stubProviders: Record<string, ProviderAdapter>;
|
|
1546
1682
|
|
|
1547
|
-
declare const VERSION: "0.
|
|
1548
|
-
declare const SDK_TAG: "@broberg/ai-sdk@0.
|
|
1683
|
+
declare const VERSION: "0.10.0";
|
|
1684
|
+
declare const SDK_TAG: "@broberg/ai-sdk@0.10.0";
|
|
1549
1685
|
|
|
1550
1686
|
/** Built-in defaults. Every entry is overridable via AiConfig.defaults or a
|
|
1551
1687
|
* per-call override. Model IDs are current at scaffold time; callers pin their
|
|
@@ -1738,4 +1874,4 @@ interface StreamTransportRequest extends TransportRequest {
|
|
|
1738
1874
|
*/
|
|
1739
1875
|
declare function streamTransport(req: StreamTransportRequest): AsyncIterable<string>;
|
|
1740
1876
|
|
|
1741
|
-
export { type AiClient, type AiConfig, type BatchJob, type BatchRequestItem, type BatchResultItem, 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 CostSink, type CostSummary, 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 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 RerankInput, type RerankResult, type Role, SDK_TAG, type SqliteBudgetStoreConfig, type SqliteSinkConfig, StreamHttpError, type SubprocessResponse, type Tier, type TierSpec, type Tool, type ToolCall, type TranscribeInput, type TranscribeRequest, type TranscribeResult, type TranslateInput, type TranslateResult, type Transport, type TransportRequest, type TransportResponse, type TtsInput, type TtsRequest, type UpmetricsSinkConfig, type Usage, VERSION, type VideoInput, type VisionInput, aiConfigSchema, anthropicAdapter, anthropicApiAdapter, anthropicSubprocessAdapter, chatInputSchema, computeCost, createAI, deepinfraAdapter, defaultProviders, discordSink, elevenlabsAdapter, embeddingInputSchema, falAdapter, falStubAdapter, freshUsage, fromProviderToolCall, geminiAdapter, getCostSummary, getPrice, httpTransport, imageInputSchema, makeContracts, makeOpenAICompatibleAdapter, messageSchema, mistralAdapter, multiSink, noopSink, openaiAdapter, openaiStubAdapter, openrouterAdapter, parseClaudeCliJson, parseJsonLoose, resolveTier, resolveVoice, sqliteBudgetStore, sqliteSink, streamTransport, stubProviders, subprocessTransport, tierSpecSchema, toProviderTools, toolSchema, translateInputSchema, upmetricsSink, visionInputSchema };
|
|
1877
|
+
export { type AiClient, type AiConfig, type BatchJob, type BatchRequestItem, type BatchResultItem, 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 CostSink, type CostSummary, 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 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 UpmetricsSinkConfig, type Usage, VERSION, type VideoInput, type VisionInput, aiConfigSchema, anthropicAdapter, anthropicApiAdapter, anthropicSubprocessAdapter, chatInputSchema, computeCost, createAI, deepinfraAdapter, defaultProviders, discordSink, elevenlabsAdapter, embeddingInputSchema, falAdapter, falStubAdapter, freshUsage, fromProviderToolCall, geminiAdapter, getCostSummary, getPrice, httpTransport, imageInputSchema, makeContracts, makeOpenAICompatibleAdapter, messageSchema, mistralAdapter, multiSink, noopSink, openaiAdapter, openaiStubAdapter, openrouterAdapter, parseClaudeCliJson, parseJsonLoose, resolveTier, resolveVoice, sqliteBudgetStore, sqliteSink, streamTransport, stubProviders, subprocessTransport, tierSpecSchema, toProviderTools, toolSchema, translateInputSchema, upmetricsSink, visionInputSchema };
|
package/dist/index.js
CHANGED
|
@@ -1284,12 +1284,15 @@ function elevenlabsAdapter(config = {}) {
|
|
|
1284
1284
|
}
|
|
1285
1285
|
|
|
1286
1286
|
// src/providers/fal.ts
|
|
1287
|
+
import { deflateRawSync, crc32 } from "zlib";
|
|
1287
1288
|
var FAL_IMAGE_PRICE_ESTIMATE = {
|
|
1288
1289
|
"fal-ai/flux/schnell": 3e-3,
|
|
1289
1290
|
"fal-ai/flux/dev": 0.025,
|
|
1291
|
+
"fal-ai/flux-lora": 0.025,
|
|
1290
1292
|
"fal-ai/flux-pro": 0.05,
|
|
1291
1293
|
"fal-ai/flux-pro/v1.1": 0.04
|
|
1292
1294
|
};
|
|
1295
|
+
var FAL_TRAIN_PRICE_ESTIMATE = 2;
|
|
1293
1296
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1294
1297
|
function falAdapter(config = {}) {
|
|
1295
1298
|
const doFetch = config.fetch ?? fetch;
|
|
@@ -1297,14 +1300,22 @@ function falAdapter(config = {}) {
|
|
|
1297
1300
|
const queueBase = config.queueBaseUrl ?? "https://queue.fal.run";
|
|
1298
1301
|
const pollIntervalMs = config.pollIntervalMs ?? 2e3;
|
|
1299
1302
|
const timeoutMs = config.timeoutMs ?? 6e4;
|
|
1303
|
+
const resolveKey = () => config.apiKey ?? process.env.FAL_KEY ?? process.env.FAL_API_KEY;
|
|
1304
|
+
const authHeaders = (apiKey) => ({
|
|
1305
|
+
"content-type": "application/json",
|
|
1306
|
+
Authorization: `Key ${apiKey}`
|
|
1307
|
+
});
|
|
1300
1308
|
async function image(req) {
|
|
1301
|
-
const apiKey =
|
|
1309
|
+
const apiKey = resolveKey();
|
|
1302
1310
|
if (!apiKey) throw new Error("fal adapter: FAL_KEY not set");
|
|
1303
|
-
const headers =
|
|
1311
|
+
const headers = authHeaders(apiKey);
|
|
1304
1312
|
const body = { prompt: req.prompt };
|
|
1305
1313
|
if (req.width !== void 0 && req.height !== void 0) {
|
|
1306
1314
|
body.image_size = { width: req.width, height: req.height };
|
|
1307
1315
|
}
|
|
1316
|
+
if (req.loras && req.loras.length > 0) {
|
|
1317
|
+
body.loras = req.loras.map((l) => ({ path: l.path, scale: l.scale ?? 1 }));
|
|
1318
|
+
}
|
|
1308
1319
|
const mode = config.mode ?? "sync";
|
|
1309
1320
|
const url = await (mode === "sync" ? runSync(req.spec.model, headers, body) : runQueue(req.spec.model, headers, body));
|
|
1310
1321
|
const usage = freshUsage({
|
|
@@ -1318,6 +1329,48 @@ function falAdapter(config = {}) {
|
|
|
1318
1329
|
usage.costUsd = config.pricePerImage ?? FAL_IMAGE_PRICE_ESTIMATE[req.spec.model] ?? 0;
|
|
1319
1330
|
return { url, usage };
|
|
1320
1331
|
}
|
|
1332
|
+
async function trainStyle(req) {
|
|
1333
|
+
const apiKey = resolveKey();
|
|
1334
|
+
if (!apiKey) throw new Error("fal adapter: FAL_KEY not set");
|
|
1335
|
+
const headers = authHeaders(apiKey);
|
|
1336
|
+
const body = {
|
|
1337
|
+
images_data_url: await resolveImagesDataUrl(req.images),
|
|
1338
|
+
is_style: req.isStyle ?? true
|
|
1339
|
+
};
|
|
1340
|
+
if (req.triggerWord !== void 0) body.trigger_word = req.triggerWord;
|
|
1341
|
+
if (req.steps !== void 0) body.steps = req.steps;
|
|
1342
|
+
if (req.createMasks !== void 0) body.create_masks = req.createMasks;
|
|
1343
|
+
const result = await queueResult(
|
|
1344
|
+
req.spec.model,
|
|
1345
|
+
headers,
|
|
1346
|
+
body,
|
|
1347
|
+
config.trainTimeoutMs ?? 6e5
|
|
1348
|
+
);
|
|
1349
|
+
const loraUrl = result.diffusers_lora_file?.url;
|
|
1350
|
+
if (!loraUrl) throw new Error("fal trainStyle: no diffusers_lora_file.url in result");
|
|
1351
|
+
const usage = freshUsage({
|
|
1352
|
+
provider: "fal",
|
|
1353
|
+
model: req.spec.model,
|
|
1354
|
+
transport: "http",
|
|
1355
|
+
capability: "trainStyle",
|
|
1356
|
+
inputTokens: 0,
|
|
1357
|
+
outputTokens: 0
|
|
1358
|
+
});
|
|
1359
|
+
usage.costUsd = config.pricePerTraining ?? FAL_TRAIN_PRICE_ESTIMATE;
|
|
1360
|
+
return { loraUrl, configUrl: result.config_file?.url ?? "", usage };
|
|
1361
|
+
}
|
|
1362
|
+
async function resolveImagesDataUrl(images) {
|
|
1363
|
+
if (typeof images === "string") return images;
|
|
1364
|
+
const files = await Promise.all(
|
|
1365
|
+
images.map(async (url, i) => {
|
|
1366
|
+
const res = await doFetch(url);
|
|
1367
|
+
if (!res.ok) throw new Error(`fal trainStyle: failed to fetch image ${url} (${res.status})`);
|
|
1368
|
+
return { name: fileNameFromUrl(url, i), data: new Uint8Array(await res.arrayBuffer()) };
|
|
1369
|
+
})
|
|
1370
|
+
);
|
|
1371
|
+
const zip = buildZip(files);
|
|
1372
|
+
return `data:application/zip;base64,${Buffer.from(zip).toString("base64")}`;
|
|
1373
|
+
}
|
|
1321
1374
|
async function runSync(model, headers, body) {
|
|
1322
1375
|
const res = await doFetch(`${syncBase}/${model}`, {
|
|
1323
1376
|
method: "POST",
|
|
@@ -1333,34 +1386,98 @@ function falAdapter(config = {}) {
|
|
|
1333
1386
|
return out;
|
|
1334
1387
|
}
|
|
1335
1388
|
async function runQueue(model, headers, body) {
|
|
1389
|
+
const result = await queueResult(model, headers, body, timeoutMs);
|
|
1390
|
+
const out = result.images?.[0]?.url;
|
|
1391
|
+
if (!out) throw new Error("fal queue: no image url in result");
|
|
1392
|
+
return out;
|
|
1393
|
+
}
|
|
1394
|
+
async function queueResult(model, headers, body, deadlineMs) {
|
|
1336
1395
|
const submitRes = await doFetch(`${queueBase}/${model}`, {
|
|
1337
1396
|
method: "POST",
|
|
1338
1397
|
headers,
|
|
1339
1398
|
body: JSON.stringify(body)
|
|
1340
1399
|
});
|
|
1341
1400
|
if (!submitRes.ok) {
|
|
1342
|
-
throw new Error(
|
|
1401
|
+
throw new Error(
|
|
1402
|
+
`fal queue submit ${submitRes.status}: ${(await submitRes.text().catch(() => "")).slice(0, 200)}`
|
|
1403
|
+
);
|
|
1343
1404
|
}
|
|
1344
1405
|
const submit = await submitRes.json();
|
|
1345
1406
|
const statusUrl = submit.status_url;
|
|
1346
1407
|
const responseUrl = submit.response_url;
|
|
1347
1408
|
if (!statusUrl || !responseUrl) throw new Error("fal queue: missing status/response url");
|
|
1348
|
-
const deadline = Date.now() +
|
|
1409
|
+
const deadline = Date.now() + deadlineMs;
|
|
1349
1410
|
for (; ; ) {
|
|
1350
1411
|
const statusRes = await doFetch(statusUrl, { headers });
|
|
1351
1412
|
const status = await statusRes.json();
|
|
1352
1413
|
if (status.status === "COMPLETED") break;
|
|
1353
|
-
if (status.status === "FAILED") throw new Error("fal queue:
|
|
1354
|
-
if (Date.now() >= deadline) throw new Error(`fal queue: timed out after ${
|
|
1414
|
+
if (status.status === "FAILED") throw new Error("fal queue: job FAILED");
|
|
1415
|
+
if (Date.now() >= deadline) throw new Error(`fal queue: timed out after ${deadlineMs}ms`);
|
|
1355
1416
|
await sleep(pollIntervalMs);
|
|
1356
1417
|
}
|
|
1357
1418
|
const resultRes = await doFetch(responseUrl, { headers });
|
|
1358
|
-
|
|
1359
|
-
const out = result.images?.[0]?.url;
|
|
1360
|
-
if (!out) throw new Error("fal queue: no image url in result");
|
|
1361
|
-
return out;
|
|
1419
|
+
return resultRes.json();
|
|
1362
1420
|
}
|
|
1363
|
-
return { name: "fal", image };
|
|
1421
|
+
return { name: "fal", image, trainStyle };
|
|
1422
|
+
}
|
|
1423
|
+
function fileNameFromUrl(url, i) {
|
|
1424
|
+
const base = url.split("?")[0].split("/").pop() || "";
|
|
1425
|
+
return /\.[a-z0-9]+$/i.test(base) ? base : `image_${i}.png`;
|
|
1426
|
+
}
|
|
1427
|
+
function buildZip(files) {
|
|
1428
|
+
const parts = [];
|
|
1429
|
+
const central = [];
|
|
1430
|
+
let offset = 0;
|
|
1431
|
+
for (const f of files) {
|
|
1432
|
+
const nameBuf = Buffer.from(f.name, "utf8");
|
|
1433
|
+
const data = Buffer.from(f.data);
|
|
1434
|
+
const comp = deflateRawSync(data);
|
|
1435
|
+
const crc = crc32(data) >>> 0;
|
|
1436
|
+
const lfh = Buffer.alloc(30);
|
|
1437
|
+
lfh.writeUInt32LE(67324752, 0);
|
|
1438
|
+
lfh.writeUInt16LE(20, 4);
|
|
1439
|
+
lfh.writeUInt16LE(0, 6);
|
|
1440
|
+
lfh.writeUInt16LE(8, 8);
|
|
1441
|
+
lfh.writeUInt16LE(0, 10);
|
|
1442
|
+
lfh.writeUInt16LE(33, 12);
|
|
1443
|
+
lfh.writeUInt32LE(crc, 14);
|
|
1444
|
+
lfh.writeUInt32LE(comp.length, 18);
|
|
1445
|
+
lfh.writeUInt32LE(data.length, 22);
|
|
1446
|
+
lfh.writeUInt16LE(nameBuf.length, 26);
|
|
1447
|
+
lfh.writeUInt16LE(0, 28);
|
|
1448
|
+
parts.push(lfh, nameBuf, comp);
|
|
1449
|
+
const cdh = Buffer.alloc(46);
|
|
1450
|
+
cdh.writeUInt32LE(33639248, 0);
|
|
1451
|
+
cdh.writeUInt16LE(20, 4);
|
|
1452
|
+
cdh.writeUInt16LE(20, 6);
|
|
1453
|
+
cdh.writeUInt16LE(0, 8);
|
|
1454
|
+
cdh.writeUInt16LE(8, 10);
|
|
1455
|
+
cdh.writeUInt16LE(0, 12);
|
|
1456
|
+
cdh.writeUInt16LE(33, 14);
|
|
1457
|
+
cdh.writeUInt32LE(crc, 16);
|
|
1458
|
+
cdh.writeUInt32LE(comp.length, 20);
|
|
1459
|
+
cdh.writeUInt32LE(data.length, 24);
|
|
1460
|
+
cdh.writeUInt16LE(nameBuf.length, 28);
|
|
1461
|
+
cdh.writeUInt16LE(0, 30);
|
|
1462
|
+
cdh.writeUInt16LE(0, 32);
|
|
1463
|
+
cdh.writeUInt16LE(0, 34);
|
|
1464
|
+
cdh.writeUInt16LE(0, 36);
|
|
1465
|
+
cdh.writeUInt32LE(0, 38);
|
|
1466
|
+
cdh.writeUInt32LE(offset, 42);
|
|
1467
|
+
central.push(cdh, nameBuf);
|
|
1468
|
+
offset += lfh.length + nameBuf.length + comp.length;
|
|
1469
|
+
}
|
|
1470
|
+
const cd = Buffer.concat(central);
|
|
1471
|
+
const eocd = Buffer.alloc(22);
|
|
1472
|
+
eocd.writeUInt32LE(101010256, 0);
|
|
1473
|
+
eocd.writeUInt16LE(0, 4);
|
|
1474
|
+
eocd.writeUInt16LE(0, 6);
|
|
1475
|
+
eocd.writeUInt16LE(files.length, 8);
|
|
1476
|
+
eocd.writeUInt16LE(files.length, 10);
|
|
1477
|
+
eocd.writeUInt32LE(cd.length, 12);
|
|
1478
|
+
eocd.writeUInt32LE(offset, 16);
|
|
1479
|
+
eocd.writeUInt16LE(0, 20);
|
|
1480
|
+
return Buffer.concat([...parts, cd, eocd]);
|
|
1364
1481
|
}
|
|
1365
1482
|
|
|
1366
1483
|
// src/providers/registry.ts
|
|
@@ -1665,10 +1782,28 @@ var translateInputSchema = z.object({
|
|
|
1665
1782
|
from: z.string().optional(),
|
|
1666
1783
|
...callOptions
|
|
1667
1784
|
});
|
|
1785
|
+
var loraWeightSchema = z.object({
|
|
1786
|
+
path: z.string(),
|
|
1787
|
+
scale: z.number().optional()
|
|
1788
|
+
});
|
|
1668
1789
|
var imageInputSchema = z.object({
|
|
1669
1790
|
prompt: z.string(),
|
|
1670
1791
|
width: z.number().int().positive().optional(),
|
|
1671
1792
|
height: z.number().int().positive().optional(),
|
|
1793
|
+
/** LoRAs to merge at inference (F021). */
|
|
1794
|
+
loras: z.array(loraWeightSchema).optional(),
|
|
1795
|
+
/** Shorthand for a single LoRA — normalized to loras:[{path, scale:1}]. */
|
|
1796
|
+
lora: z.string().optional(),
|
|
1797
|
+
...callOptions
|
|
1798
|
+
});
|
|
1799
|
+
var trainStyleInputSchema = z.object({
|
|
1800
|
+
/** A hosted archive URL, or an array of image URLs the SDK zips in-memory. */
|
|
1801
|
+
images: z.union([z.string(), z.array(z.string())]),
|
|
1802
|
+
/** Style LoRA (disables captioning/masks). Default true. */
|
|
1803
|
+
isStyle: z.boolean().optional(),
|
|
1804
|
+
triggerWord: z.string().optional(),
|
|
1805
|
+
steps: z.number().int().positive().optional(),
|
|
1806
|
+
createMasks: z.boolean().optional(),
|
|
1672
1807
|
...callOptions
|
|
1673
1808
|
});
|
|
1674
1809
|
var embeddingInputSchema = z.object({
|
|
@@ -1724,6 +1859,16 @@ var DEFAULT_IMAGE_SPEC = {
|
|
|
1724
1859
|
model: "fal-ai/flux/schnell",
|
|
1725
1860
|
transport: "http"
|
|
1726
1861
|
};
|
|
1862
|
+
var DEFAULT_LORA_IMAGE_SPEC = {
|
|
1863
|
+
provider: "fal",
|
|
1864
|
+
model: "fal-ai/flux-lora",
|
|
1865
|
+
transport: "http"
|
|
1866
|
+
};
|
|
1867
|
+
var DEFAULT_TRAINSTYLE_SPEC = {
|
|
1868
|
+
provider: "fal",
|
|
1869
|
+
model: "fal-ai/flux-lora-fast-training",
|
|
1870
|
+
transport: "http"
|
|
1871
|
+
};
|
|
1727
1872
|
var DEFAULT_OCR_SPEC = { provider: "mistral", model: "mistral-ocr-latest", transport: "http" };
|
|
1728
1873
|
var DEFAULT_MODERATION_SPEC = { provider: "mistral", model: "mistral-moderation-latest", transport: "http" };
|
|
1729
1874
|
var DEFAULT_PODCAST_SPEC = { provider: "elevenlabs", model: "eleven_v3", transport: "http" };
|
|
@@ -1956,8 +2101,13 @@ function createAI(config = {}) {
|
|
|
1956
2101
|
},
|
|
1957
2102
|
async image(input) {
|
|
1958
2103
|
input = imageInputSchema.parse(input);
|
|
2104
|
+
const loras = [
|
|
2105
|
+
...input.loras ?? [],
|
|
2106
|
+
...input.lora ? [{ path: input.lora }] : []
|
|
2107
|
+
];
|
|
2108
|
+
const base = loras.length > 0 ? DEFAULT_LORA_IMAGE_SPEC : DEFAULT_IMAGE_SPEC;
|
|
1959
2109
|
return runCapability({
|
|
1960
|
-
primary: { ...
|
|
2110
|
+
primary: { ...base, ...input.override },
|
|
1961
2111
|
fallback: input.fallback,
|
|
1962
2112
|
capability: "image",
|
|
1963
2113
|
purpose: input.purpose,
|
|
@@ -1968,7 +2118,39 @@ function createAI(config = {}) {
|
|
|
1968
2118
|
invoke: async (spec) => {
|
|
1969
2119
|
const adapter = pickProvider(spec.provider);
|
|
1970
2120
|
if (!adapter.image) throw new Error(`createAI: provider "${spec.provider}" does not support image`);
|
|
1971
|
-
return adapter.image({
|
|
2121
|
+
return adapter.image({
|
|
2122
|
+
prompt: input.prompt,
|
|
2123
|
+
spec,
|
|
2124
|
+
width: input.width,
|
|
2125
|
+
height: input.height,
|
|
2126
|
+
loras: loras.length ? loras : void 0
|
|
2127
|
+
});
|
|
2128
|
+
}
|
|
2129
|
+
});
|
|
2130
|
+
},
|
|
2131
|
+
async trainStyle(input) {
|
|
2132
|
+
input = trainStyleInputSchema.parse(input);
|
|
2133
|
+
return runCapability({
|
|
2134
|
+
primary: { ...DEFAULT_TRAINSTYLE_SPEC, ...input.override },
|
|
2135
|
+
fallback: input.fallback,
|
|
2136
|
+
capability: "trainStyle",
|
|
2137
|
+
purpose: input.purpose,
|
|
2138
|
+
labels: input.labels,
|
|
2139
|
+
estIn: 0,
|
|
2140
|
+
// training is priced flat by fal, not token-based
|
|
2141
|
+
estOut: 0,
|
|
2142
|
+
invoke: async (spec) => {
|
|
2143
|
+
const adapter = pickProvider(spec.provider);
|
|
2144
|
+
if (!adapter.trainStyle)
|
|
2145
|
+
throw new Error(`createAI: provider "${spec.provider}" does not support trainStyle`);
|
|
2146
|
+
return adapter.trainStyle({
|
|
2147
|
+
images: input.images,
|
|
2148
|
+
spec,
|
|
2149
|
+
isStyle: input.isStyle,
|
|
2150
|
+
triggerWord: input.triggerWord,
|
|
2151
|
+
steps: input.steps,
|
|
2152
|
+
createMasks: input.createMasks
|
|
2153
|
+
});
|
|
1972
2154
|
}
|
|
1973
2155
|
});
|
|
1974
2156
|
},
|
|
@@ -2196,8 +2378,8 @@ var stubProviders = {
|
|
|
2196
2378
|
};
|
|
2197
2379
|
|
|
2198
2380
|
// src/version.ts
|
|
2199
|
-
var VERSION = "0.
|
|
2200
|
-
var SDK_TAG = "@broberg/ai-sdk@0.
|
|
2381
|
+
var VERSION = "0.10.0";
|
|
2382
|
+
var SDK_TAG = "@broberg/ai-sdk@0.10.0";
|
|
2201
2383
|
|
|
2202
2384
|
// src/cost/budget-store.ts
|
|
2203
2385
|
function sqliteBudgetStore(config) {
|