@broberg/ai-sdk 0.9.0 → 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 +204 -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
|
@@ -253,6 +253,13 @@ var PRICING = {
|
|
|
253
253
|
outputPer1M: 1.2,
|
|
254
254
|
version: "2026-06-04"
|
|
255
255
|
},
|
|
256
|
+
// DeepSeek V4 (CN-hosted — NOT GDPR-safe; non-personal-data workloads only).
|
|
257
|
+
// On 2026-05-22 DeepSeek made the "75% off" promo the permanent official price.
|
|
258
|
+
// V4-Pro $0.435/$0.87 is ~34x cheaper than GPT-5.5 on output; flash is cheaper
|
|
259
|
+
// still. Numbers match OpenRouter /api/v1/models 1:1 (no router markup). A strong
|
|
260
|
+
// cheap route for fleet background work once `claude -p` is API-billed (15 Jun).
|
|
261
|
+
"openrouter:deepseek/deepseek-v4-pro": { inputPer1M: 0.435, outputPer1M: 0.87, version: "2026-05-22-deepseek-official" },
|
|
262
|
+
"openrouter:deepseek/deepseek-v4-flash": { inputPer1M: 0.0983, outputPer1M: 0.1966, version: "2026-05-22-deepseek-official" },
|
|
256
263
|
// Google Gemini (direct). Provider key is "gemini" — matches the adapter's
|
|
257
264
|
// usage.provider + the override.provider callers pass. (Image-gen models are
|
|
258
265
|
// priced per-image in the adapter, not here.)
|
|
@@ -1277,12 +1284,15 @@ function elevenlabsAdapter(config = {}) {
|
|
|
1277
1284
|
}
|
|
1278
1285
|
|
|
1279
1286
|
// src/providers/fal.ts
|
|
1287
|
+
import { deflateRawSync, crc32 } from "zlib";
|
|
1280
1288
|
var FAL_IMAGE_PRICE_ESTIMATE = {
|
|
1281
1289
|
"fal-ai/flux/schnell": 3e-3,
|
|
1282
1290
|
"fal-ai/flux/dev": 0.025,
|
|
1291
|
+
"fal-ai/flux-lora": 0.025,
|
|
1283
1292
|
"fal-ai/flux-pro": 0.05,
|
|
1284
1293
|
"fal-ai/flux-pro/v1.1": 0.04
|
|
1285
1294
|
};
|
|
1295
|
+
var FAL_TRAIN_PRICE_ESTIMATE = 2;
|
|
1286
1296
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1287
1297
|
function falAdapter(config = {}) {
|
|
1288
1298
|
const doFetch = config.fetch ?? fetch;
|
|
@@ -1290,14 +1300,22 @@ function falAdapter(config = {}) {
|
|
|
1290
1300
|
const queueBase = config.queueBaseUrl ?? "https://queue.fal.run";
|
|
1291
1301
|
const pollIntervalMs = config.pollIntervalMs ?? 2e3;
|
|
1292
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
|
+
});
|
|
1293
1308
|
async function image(req) {
|
|
1294
|
-
const apiKey =
|
|
1309
|
+
const apiKey = resolveKey();
|
|
1295
1310
|
if (!apiKey) throw new Error("fal adapter: FAL_KEY not set");
|
|
1296
|
-
const headers =
|
|
1311
|
+
const headers = authHeaders(apiKey);
|
|
1297
1312
|
const body = { prompt: req.prompt };
|
|
1298
1313
|
if (req.width !== void 0 && req.height !== void 0) {
|
|
1299
1314
|
body.image_size = { width: req.width, height: req.height };
|
|
1300
1315
|
}
|
|
1316
|
+
if (req.loras && req.loras.length > 0) {
|
|
1317
|
+
body.loras = req.loras.map((l) => ({ path: l.path, scale: l.scale ?? 1 }));
|
|
1318
|
+
}
|
|
1301
1319
|
const mode = config.mode ?? "sync";
|
|
1302
1320
|
const url = await (mode === "sync" ? runSync(req.spec.model, headers, body) : runQueue(req.spec.model, headers, body));
|
|
1303
1321
|
const usage = freshUsage({
|
|
@@ -1311,6 +1329,48 @@ function falAdapter(config = {}) {
|
|
|
1311
1329
|
usage.costUsd = config.pricePerImage ?? FAL_IMAGE_PRICE_ESTIMATE[req.spec.model] ?? 0;
|
|
1312
1330
|
return { url, usage };
|
|
1313
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
|
+
}
|
|
1314
1374
|
async function runSync(model, headers, body) {
|
|
1315
1375
|
const res = await doFetch(`${syncBase}/${model}`, {
|
|
1316
1376
|
method: "POST",
|
|
@@ -1326,34 +1386,98 @@ function falAdapter(config = {}) {
|
|
|
1326
1386
|
return out;
|
|
1327
1387
|
}
|
|
1328
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) {
|
|
1329
1395
|
const submitRes = await doFetch(`${queueBase}/${model}`, {
|
|
1330
1396
|
method: "POST",
|
|
1331
1397
|
headers,
|
|
1332
1398
|
body: JSON.stringify(body)
|
|
1333
1399
|
});
|
|
1334
1400
|
if (!submitRes.ok) {
|
|
1335
|
-
throw new Error(
|
|
1401
|
+
throw new Error(
|
|
1402
|
+
`fal queue submit ${submitRes.status}: ${(await submitRes.text().catch(() => "")).slice(0, 200)}`
|
|
1403
|
+
);
|
|
1336
1404
|
}
|
|
1337
1405
|
const submit = await submitRes.json();
|
|
1338
1406
|
const statusUrl = submit.status_url;
|
|
1339
1407
|
const responseUrl = submit.response_url;
|
|
1340
1408
|
if (!statusUrl || !responseUrl) throw new Error("fal queue: missing status/response url");
|
|
1341
|
-
const deadline = Date.now() +
|
|
1409
|
+
const deadline = Date.now() + deadlineMs;
|
|
1342
1410
|
for (; ; ) {
|
|
1343
1411
|
const statusRes = await doFetch(statusUrl, { headers });
|
|
1344
1412
|
const status = await statusRes.json();
|
|
1345
1413
|
if (status.status === "COMPLETED") break;
|
|
1346
|
-
if (status.status === "FAILED") throw new Error("fal queue:
|
|
1347
|
-
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`);
|
|
1348
1416
|
await sleep(pollIntervalMs);
|
|
1349
1417
|
}
|
|
1350
1418
|
const resultRes = await doFetch(responseUrl, { headers });
|
|
1351
|
-
|
|
1352
|
-
const out = result.images?.[0]?.url;
|
|
1353
|
-
if (!out) throw new Error("fal queue: no image url in result");
|
|
1354
|
-
return out;
|
|
1419
|
+
return resultRes.json();
|
|
1355
1420
|
}
|
|
1356
|
-
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]);
|
|
1357
1481
|
}
|
|
1358
1482
|
|
|
1359
1483
|
// src/providers/registry.ts
|
|
@@ -1658,10 +1782,28 @@ var translateInputSchema = z.object({
|
|
|
1658
1782
|
from: z.string().optional(),
|
|
1659
1783
|
...callOptions
|
|
1660
1784
|
});
|
|
1785
|
+
var loraWeightSchema = z.object({
|
|
1786
|
+
path: z.string(),
|
|
1787
|
+
scale: z.number().optional()
|
|
1788
|
+
});
|
|
1661
1789
|
var imageInputSchema = z.object({
|
|
1662
1790
|
prompt: z.string(),
|
|
1663
1791
|
width: z.number().int().positive().optional(),
|
|
1664
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(),
|
|
1665
1807
|
...callOptions
|
|
1666
1808
|
});
|
|
1667
1809
|
var embeddingInputSchema = z.object({
|
|
@@ -1717,6 +1859,16 @@ var DEFAULT_IMAGE_SPEC = {
|
|
|
1717
1859
|
model: "fal-ai/flux/schnell",
|
|
1718
1860
|
transport: "http"
|
|
1719
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
|
+
};
|
|
1720
1872
|
var DEFAULT_OCR_SPEC = { provider: "mistral", model: "mistral-ocr-latest", transport: "http" };
|
|
1721
1873
|
var DEFAULT_MODERATION_SPEC = { provider: "mistral", model: "mistral-moderation-latest", transport: "http" };
|
|
1722
1874
|
var DEFAULT_PODCAST_SPEC = { provider: "elevenlabs", model: "eleven_v3", transport: "http" };
|
|
@@ -1949,8 +2101,13 @@ function createAI(config = {}) {
|
|
|
1949
2101
|
},
|
|
1950
2102
|
async image(input) {
|
|
1951
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;
|
|
1952
2109
|
return runCapability({
|
|
1953
|
-
primary: { ...
|
|
2110
|
+
primary: { ...base, ...input.override },
|
|
1954
2111
|
fallback: input.fallback,
|
|
1955
2112
|
capability: "image",
|
|
1956
2113
|
purpose: input.purpose,
|
|
@@ -1961,7 +2118,39 @@ function createAI(config = {}) {
|
|
|
1961
2118
|
invoke: async (spec) => {
|
|
1962
2119
|
const adapter = pickProvider(spec.provider);
|
|
1963
2120
|
if (!adapter.image) throw new Error(`createAI: provider "${spec.provider}" does not support image`);
|
|
1964
|
-
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
|
+
});
|
|
1965
2154
|
}
|
|
1966
2155
|
});
|
|
1967
2156
|
},
|
|
@@ -2189,8 +2378,8 @@ var stubProviders = {
|
|
|
2189
2378
|
};
|
|
2190
2379
|
|
|
2191
2380
|
// src/version.ts
|
|
2192
|
-
var VERSION = "0.
|
|
2193
|
-
var SDK_TAG = "@broberg/ai-sdk@0.
|
|
2381
|
+
var VERSION = "0.10.0";
|
|
2382
|
+
var SDK_TAG = "@broberg/ai-sdk@0.10.0";
|
|
2194
2383
|
|
|
2195
2384
|
// src/cost/budget-store.ts
|
|
2196
2385
|
function sqliteBudgetStore(config) {
|