vargai 0.3.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.
Files changed (154) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/.env.example +27 -0
  3. package/.github/workflows/ci.yml +23 -0
  4. package/.husky/README.md +102 -0
  5. package/.husky/commit-msg +6 -0
  6. package/.husky/pre-commit +9 -0
  7. package/.husky/pre-push +6 -0
  8. package/.size-limit.json +8 -0
  9. package/.test-hooks.ts +5 -0
  10. package/CLAUDE.md +125 -0
  11. package/CONTRIBUTING.md +150 -0
  12. package/LICENSE.md +53 -0
  13. package/README.md +78 -0
  14. package/SKILLS.md +173 -0
  15. package/STRUCTURE.md +92 -0
  16. package/biome.json +34 -0
  17. package/bun.lock +1254 -0
  18. package/commitlint.config.js +22 -0
  19. package/docs/plan.md +66 -0
  20. package/docs/todo.md +14 -0
  21. package/docs/varg-sdk.md +812 -0
  22. package/ffmpeg/CLAUDE.md +68 -0
  23. package/package.json +69 -0
  24. package/pipeline/cookbooks/SKILL.md +285 -0
  25. package/pipeline/cookbooks/remotion-video.md +585 -0
  26. package/pipeline/cookbooks/round-video-character.md +337 -0
  27. package/pipeline/cookbooks/scripts/animate-frames-parallel.ts +84 -0
  28. package/pipeline/cookbooks/scripts/combine-scenes.sh +53 -0
  29. package/pipeline/cookbooks/scripts/generate-frames-parallel.ts +99 -0
  30. package/pipeline/cookbooks/scripts/still-to-video.sh +37 -0
  31. package/pipeline/cookbooks/talking-character.md +59 -0
  32. package/pipeline/cookbooks/text-to-tiktok.md +669 -0
  33. package/pipeline/cookbooks/trendwatching.md +156 -0
  34. package/plan.md +281 -0
  35. package/scripts/.gitkeep +0 -0
  36. package/src/ai-sdk/cache.ts +142 -0
  37. package/src/ai-sdk/examples/cached-generation.ts +53 -0
  38. package/src/ai-sdk/examples/duet-scene-4.ts +53 -0
  39. package/src/ai-sdk/examples/duet-scene-5-audio.ts +32 -0
  40. package/src/ai-sdk/examples/duet-video.ts +56 -0
  41. package/src/ai-sdk/examples/editly-composition.ts +63 -0
  42. package/src/ai-sdk/examples/editly-test.ts +57 -0
  43. package/src/ai-sdk/examples/editly-video-test.ts +52 -0
  44. package/src/ai-sdk/examples/fal-lipsync.ts +43 -0
  45. package/src/ai-sdk/examples/higgsfield-image.ts +61 -0
  46. package/src/ai-sdk/examples/music-generation.ts +19 -0
  47. package/src/ai-sdk/examples/openai-sora.ts +34 -0
  48. package/src/ai-sdk/examples/replicate-bg-removal.ts +52 -0
  49. package/src/ai-sdk/examples/simpsons-scene.ts +61 -0
  50. package/src/ai-sdk/examples/talking-lion.ts +55 -0
  51. package/src/ai-sdk/examples/video-generation.ts +39 -0
  52. package/src/ai-sdk/examples/workflow-animated-girl.ts +104 -0
  53. package/src/ai-sdk/examples/workflow-before-after.ts +114 -0
  54. package/src/ai-sdk/examples/workflow-character-grid.ts +112 -0
  55. package/src/ai-sdk/examples/workflow-slideshow.ts +161 -0
  56. package/src/ai-sdk/file-cache.ts +112 -0
  57. package/src/ai-sdk/file.ts +238 -0
  58. package/src/ai-sdk/generate-element.ts +92 -0
  59. package/src/ai-sdk/generate-music.ts +46 -0
  60. package/src/ai-sdk/generate-video.ts +165 -0
  61. package/src/ai-sdk/index.ts +72 -0
  62. package/src/ai-sdk/music-model.ts +110 -0
  63. package/src/ai-sdk/providers/editly/editly.test.ts +1108 -0
  64. package/src/ai-sdk/providers/editly/ffmpeg.ts +60 -0
  65. package/src/ai-sdk/providers/editly/index.ts +817 -0
  66. package/src/ai-sdk/providers/editly/layers.ts +772 -0
  67. package/src/ai-sdk/providers/editly/plan.md +144 -0
  68. package/src/ai-sdk/providers/editly/types.ts +328 -0
  69. package/src/ai-sdk/providers/elevenlabs-provider.ts +255 -0
  70. package/src/ai-sdk/providers/fal-provider.ts +512 -0
  71. package/src/ai-sdk/providers/higgsfield.ts +379 -0
  72. package/src/ai-sdk/providers/openai.ts +251 -0
  73. package/src/ai-sdk/providers/replicate.ts +16 -0
  74. package/src/ai-sdk/video-model.ts +185 -0
  75. package/src/cli/commands/find.tsx +137 -0
  76. package/src/cli/commands/help.tsx +85 -0
  77. package/src/cli/commands/index.ts +9 -0
  78. package/src/cli/commands/list.tsx +238 -0
  79. package/src/cli/commands/run.tsx +511 -0
  80. package/src/cli/commands/which.tsx +253 -0
  81. package/src/cli/index.ts +112 -0
  82. package/src/cli/quiet.ts +44 -0
  83. package/src/cli/types.ts +32 -0
  84. package/src/cli/ui/components/Badge.tsx +29 -0
  85. package/src/cli/ui/components/DataTable.tsx +51 -0
  86. package/src/cli/ui/components/Header.tsx +23 -0
  87. package/src/cli/ui/components/HelpBlock.tsx +44 -0
  88. package/src/cli/ui/components/KeyValue.tsx +33 -0
  89. package/src/cli/ui/components/OptionRow.tsx +81 -0
  90. package/src/cli/ui/components/Separator.tsx +23 -0
  91. package/src/cli/ui/components/StatusBox.tsx +108 -0
  92. package/src/cli/ui/components/VargBox.tsx +51 -0
  93. package/src/cli/ui/components/VargProgress.tsx +36 -0
  94. package/src/cli/ui/components/VargSpinner.tsx +34 -0
  95. package/src/cli/ui/components/VargText.tsx +56 -0
  96. package/src/cli/ui/components/index.ts +19 -0
  97. package/src/cli/ui/index.ts +12 -0
  98. package/src/cli/ui/render.ts +35 -0
  99. package/src/cli/ui/theme.ts +63 -0
  100. package/src/cli/utils.ts +78 -0
  101. package/src/core/executor/executor.ts +201 -0
  102. package/src/core/executor/index.ts +13 -0
  103. package/src/core/executor/job.ts +214 -0
  104. package/src/core/executor/pipeline.ts +222 -0
  105. package/src/core/index.ts +11 -0
  106. package/src/core/registry/index.ts +9 -0
  107. package/src/core/registry/loader.ts +149 -0
  108. package/src/core/registry/registry.ts +221 -0
  109. package/src/core/registry/resolver.ts +206 -0
  110. package/src/core/schema/helpers.ts +134 -0
  111. package/src/core/schema/index.ts +8 -0
  112. package/src/core/schema/shared.ts +102 -0
  113. package/src/core/schema/types.ts +279 -0
  114. package/src/core/schema/validator.ts +92 -0
  115. package/src/definitions/actions/captions.ts +261 -0
  116. package/src/definitions/actions/edit.ts +298 -0
  117. package/src/definitions/actions/image.ts +125 -0
  118. package/src/definitions/actions/index.ts +114 -0
  119. package/src/definitions/actions/music.ts +205 -0
  120. package/src/definitions/actions/sync.ts +128 -0
  121. package/src/definitions/actions/transcribe.ts +200 -0
  122. package/src/definitions/actions/upload.ts +111 -0
  123. package/src/definitions/actions/video.ts +163 -0
  124. package/src/definitions/actions/voice.ts +119 -0
  125. package/src/definitions/index.ts +23 -0
  126. package/src/definitions/models/elevenlabs.ts +50 -0
  127. package/src/definitions/models/flux.ts +56 -0
  128. package/src/definitions/models/index.ts +36 -0
  129. package/src/definitions/models/kling.ts +56 -0
  130. package/src/definitions/models/llama.ts +54 -0
  131. package/src/definitions/models/nano-banana-pro.ts +102 -0
  132. package/src/definitions/models/sonauto.ts +68 -0
  133. package/src/definitions/models/soul.ts +65 -0
  134. package/src/definitions/models/wan.ts +54 -0
  135. package/src/definitions/models/whisper.ts +44 -0
  136. package/src/definitions/skills/index.ts +12 -0
  137. package/src/definitions/skills/talking-character.ts +87 -0
  138. package/src/definitions/skills/text-to-tiktok.ts +97 -0
  139. package/src/index.ts +118 -0
  140. package/src/providers/apify.ts +269 -0
  141. package/src/providers/base.ts +264 -0
  142. package/src/providers/elevenlabs.ts +217 -0
  143. package/src/providers/fal.ts +392 -0
  144. package/src/providers/ffmpeg.ts +544 -0
  145. package/src/providers/fireworks.ts +193 -0
  146. package/src/providers/groq.ts +149 -0
  147. package/src/providers/higgsfield.ts +145 -0
  148. package/src/providers/index.ts +143 -0
  149. package/src/providers/replicate.ts +147 -0
  150. package/src/providers/storage.ts +206 -0
  151. package/src/tests/all.test.ts +509 -0
  152. package/src/tests/index.ts +33 -0
  153. package/src/tests/unit.test.ts +403 -0
  154. package/tsconfig.json +45 -0
@@ -0,0 +1,379 @@
1
+ import type {
2
+ ImageModelV3,
3
+ ImageModelV3CallOptions,
4
+ SharedV3Warning,
5
+ } from "@ai-sdk/provider";
6
+
7
+ const IMAGE_MODELS = ["soul"] as const;
8
+ type ImageModelId = (typeof IMAGE_MODELS)[number];
9
+
10
+ /**
11
+ * Available Soul styles for Higgsfield image generation.
12
+ * Use with `higgsfield.imageModel("soul", { styleId: higgsfield.styles.REALISTIC })`
13
+ */
14
+ export const SOUL_STYLES = {
15
+ CREATURES: "b3c8075a-cb4c-42de-b8b3-7099dd2df672",
16
+ MEDIEVAL: "1fc861ed-5923-41a6-9963-b9f04681dddd",
17
+ SPOTLIGHT: "40ff999c-f576-443c-b5b3-c7d1391a666e",
18
+ GIANT_PEOPLE: "a5f63c3b-70eb-4979-af5e-98c7ee1e18e8",
19
+ RED_BALLOON: "3de71b9e-3973-4828-b246-a34c606e25a7",
20
+ GREEN_EDITORIAL: "91abc4fe-1cf8-4a77-8ade-d36d46699014",
21
+ SUBWAY: "d2e8ba04-9935-4dee-8bc4-39ac789746fc",
22
+ LIBRARY: "6fb3e1f5-d721-4523-ac38-9902f2b2b850",
23
+ REALISTIC: "1cb4b936-77bf-4f9a-9039-f3d349a4cdbe",
24
+ DIGITALCAM: "ca4e6ad3-3e93-4e03-81a0-d1722d2c128b",
25
+ GRILLZ_SELFIE: "255f4045-d68b-42b1-9e4c-f49d3263a9d7",
26
+ BLEACHED_BROWS: "cc099663-9621-422e-8626-c8ee68953a0c",
27
+ SITTING_ON_THE_STREET: "7696fd45-6e67-47d7-b800-096ce21cd449",
28
+ CROSSING_THE_STREET: "d3e2b71d-b24b-462e-bd96-12f7a22b5142",
29
+ ANGEL_WINGS: "4c24b43b-1984-407a-a0ae-c514f29b7e66",
30
+ DUPLICATE: "88126a43-86fb-4047-a2d6-c9146d6ca6ce",
31
+ QUIET_LUXURY: "ff1ad8a2-94e7-4e70-a12f-e992ca9a0d36",
32
+ FIREPROOF: "373420f7-489e-4a5d-930e-cc4ecfcc23cc",
33
+ ELEVATOR_MIRROR: "524be50a-4388-4ff5-a843-a73d2dd7ef87",
34
+ CAM_360: "294bb3ee-eaef-4d2a-93e3-164268803db4",
35
+ GLITCH: "62355e77-7096-45ae-9bea-e7c5b88c3b70",
36
+ FASHION_SHOW: "86fc814e-856a-4af0-98b0-d4da75d0030b",
37
+ PIXELETED_FACE: "34c50302-83ff-487d-b3a9-e35e501d80a7",
38
+ SUNBATHING: "bc00b419-f8ca-4887-a990-e2760c3cb761",
39
+ PAPER_FACE: "7fa63380-64b7-48b1-b684-4c9ef37560a9",
40
+ GRAIN_90S: "f5c094c7-4671-4d86-90d2-369c8fdbd7a5",
41
+ GEOMINIMAL: "372cc37b-9add-4952-a415-53db3998139f",
42
+ FOGGY_MORNING: "0fe8ad66-ff61-411f-9186-b392e140b18c",
43
+ OVEREXPOSED: "d8a35238-ba42-48a0-a76a-186a97734b9d",
44
+ SUNSET_BEACH: "26241c54-ed78-4ea7-b1bf-d881737c9feb",
45
+ GIANT_ACCESSORY: "70fbb531-5ee2-492e-8c53-5dbd6923e8c2",
46
+ RING_SELFIE: "9de8ed26-c8dd-413c-a5e3-47eec97bc243",
47
+ STREET_VIEW: "a13917c7-02a4-450f-b007-e72d53151980",
48
+ EDITORIAL_90S: "710f9073-f580-48dc-b5c3-9bbc7cbb7f37",
49
+ RHYME_BLUES: "c7ea4e7a-c40c-498d-948c-1f6919631f60",
50
+ CAM_2000S: "181b3796-008a-403b-b31e-a9b760219f17",
51
+ CCTV: "07a85fb3-4407-4122-a4eb-42124e57734c",
52
+ OUTFIT_05: "71fecd8c-6696-42df-b5eb-f69e4150ca01",
53
+ AMALFI_SUMMER: "dab472a6-23f4-4cf8-98fe-f3e256f1b549",
54
+ BIMBOCORE: "f96913e8-2fcf-4358-8545-75dd6c34c518",
55
+ SELFIE_05: "8dd89de9-1cff-402e-88a8-580c29d91473",
56
+ SAND: "ba3d7634-447e-455c-98e3-63705d5403b8",
57
+ VINTAGE_PHOTOBOOTH: "83caff04-691c-468c-b4a0-fd6bbabe062b",
58
+ AFTERPARTY_CAM: "5765d07d-1525-4d4d-ae06-9091e2bdac2d",
59
+ BABYDOLL_MAKEUP: "b7c621b5-9d3c-46a3-8efb-4cdfbc592271",
60
+ THROUGH_THE_GLASS: "1900111a-4ce8-42a7-9394-7367f0e0385c",
61
+ GALLERY: "36061eb7-4907-4cba-afb1-47afcf699873",
62
+ EATING_FOOD: "7df83cc9-1e13-4bd0-b6ff-1e6a456b9e5a",
63
+ SWORDS_HILL: "5b6f467e-f509-4afe-a8db-4c07a6f3770d",
64
+ OFFICE_BEACH: "e454956b-caf2-4913-a398-dbc03f1cbedf",
65
+ HELP_ITS_TOO_BIG: "5ad23bca-4a4b-4316-8c59-b80d7709d8ee",
66
+ JAPANDI: "0089e17c-d0f0-4d0c-b522-6d25c88a29fc",
67
+ IPHONE: "1b798b54-03da-446a-93bf-12fcba1050d7",
68
+ GORPCORE: "96758335-d1d1-42b7-9c21-5ac38c433485",
69
+ INDIE_SLEAZE: "5a72fec7-a12e-43db-8ef3-1d193b4f7ab4",
70
+ FAIRYCORE: "7f21e7bd-4df6-4cef-a9a9-9746bceaea1d",
71
+ TUMBLR: "0367d609-dfa1-4a81-a983-b2b19ecd6480",
72
+ AVANT_GARDE: "0c636e12-3411-4a65-8d86-67858caf2fa7",
73
+ HAIRCLIPS: "ea6f4dc0-d6dd-4bdf-a8cf-94ed1db91ab2",
74
+ BIRTHDAY_MESS: "2d47f079-c021-4b8e-b2c0-3b927a80fc31",
75
+ CLOUDED_DREAM: "493bda5b-bb4b-46fe-9343-7d5e414534ef",
76
+ Y2K_POSTERS: "cbefda85-0f76-49bd-82d7-9bcd65be00ca",
77
+ TOKYO_DRIFT: "ce9a88c2-c962-45e2-abaa-c8979d48f8d5",
78
+ OBJECT_MAKEUP: "b7908955-2868-4e35-87a0-35e50cb92e5d",
79
+ GRAFFITI: "0b4dac9a-f73a-4e5b-a5a7-1a40ee40d6ac",
80
+ SUNBURNT: "e439bd89-8176-4729-b6a4-a9977120507d",
81
+ HALLWAY_NOIR: "a643a36a-85e6-4e3d-80db-13e4997203cc",
82
+ FASHION_2000S: "facaafeb-4ab5-4384-92a1-b4086180e9ac",
83
+ NIGHT_BEACH: "62ba1751-63af-4648-a11c-711ac64e216a",
84
+ MOVIE: "811de7ab-7aaf-4a6b-b352-cdea6c34c8f1",
85
+ LONG_LEGS: "12eda704-18e5-4783-aa0f-deba5296cc83",
86
+ APHEX_TWIN: "5659c554-9367-4a27-8c66-333c072cbbc2",
87
+ GENERAL: "464ea177-8d40-4940-8d9d-b438bab269c7",
88
+ NAIL_CHECK: "4b66c2db-8166-4293-b1aa-5269c9effb07",
89
+ COQUETTE_CORE: "bd78cfc6-9b92-4889-9347-f21dbf0a269c",
90
+ MIXED_MEDIA: "2fcf02e2-919a-4642-8b31-d58bde5e6bd9",
91
+ SELFCARE: "d24c016c-9fb1-47d0-9909-19f57a2830d4",
92
+ GRUNGE: "ad9de607-3941-4540-81ea-ba978ef1550b",
93
+ DOUBLE_TAKE: "2a1898d0-548f-4433-8503-5721157b93a1",
94
+ ROOM_505: "673cf0d4-c193-4fa2-8ad3-b4db4611e3ae",
95
+ FLIGHT_MODE: "3f90dc5b-f474-4259-95c4-d29fbd6be645",
96
+ ESCALATOR: "bab6e4bd-9093-4bb5-a371-01ef6cbd58ad",
97
+ BURGUNDY_SUIT: "84c23cef-7eda-4f8f-9931-e3e6af8192d9",
98
+ FISHEYE: "cc4e7248-dcfe-4c93-b264-2ab418a7556b",
99
+ SHOE_CHECK: "30458874-d9c0-4d5a-b2b7-597e0eee2404",
100
+ RAINY_DAY: "53bdadfa-8eb6-4eaa-8923-ebece4faa91c",
101
+ MT_FUJI: "de0118ba-7c27-49f7-9841-38abe2aae8e1",
102
+ SEA_BREEZE: "71ac929c-4002-4640-9b65-cb06402844c6",
103
+ INVERTETHEREAL: "82edba1e-b093-4484-a25e-9276e0454999",
104
+ Y2K: "6b9e6b4d-325a-4a78-a0fb-a00ddf612380",
105
+ TOKYO_STREETSTYLE: "99de6fc5-1177-49b9-b2e9-19e17d95bcaf",
106
+ CHROME_EXIT: "5e01339d-745e-4bba-96f7-e8ce4af79f17",
107
+ NIGHT_RIDER: "1fba888b-9ab0-447f-a6a4-9ce5251ec2a6",
108
+ ARTWORK: "b9e2d7dc-78e6-4f7d-95dd-b62690e7b200",
109
+ GLAZED_DOLL_SKIN_MAKEUP: "a2a42ada-75cc-42a9-be12-cb16c1dec2a8",
110
+ MOUNT_VIEW: "3c975998-cb5b-4980-80fc-7977e4c60972",
111
+ BLADE_RUNNER_2049: "53959c8a-4323-4b78-9888-e9f6fb0f6b98",
112
+ BLACKOUT_FIT: "f8dac072-d11f-438a-b283-fd52fe8aa744",
113
+ BIKE_MAFIA: "90df2935-3ded-477f-8253-1d67dd939cbe",
114
+ STATIC_GLOW: "fb9cee2b-632f-4fd4-ae4f-4664deecc0f4",
115
+ NICOTINE_GLOW: "5dbb6a20-0541-4f06-8352-a2408d8781dc",
116
+ BRICK_SHADE: "f3968a4f-a125-4c16-8673-45ce9874520e",
117
+ DMV: "923e4fb0-d4ea-480c-876d-ac7cad862b9d",
118
+ FISH_EYE_TWIN: "d4775423-d214-4862-b061-47baa1978208",
119
+ ITS_FRENCH: "79bfaa63-4e12-4ea2-8ada-7d4406eecece",
120
+ COCKTAIL: "aed71142-673c-4908-a6a7-326d5252eb06",
121
+ } as const;
122
+
123
+ export interface HiggsfieldImageModelSettings {
124
+ styleId?: string;
125
+ quality?: "720p" | "1080p";
126
+ enhancePrompt?: boolean;
127
+ }
128
+
129
+ // Maps aspect ratio to width_and_height for Soul API
130
+ const ASPECT_RATIO_TO_SIZE: Record<string, string> = {
131
+ "1:1": "1536x1536",
132
+ "16:9": "2048x1152",
133
+ "9:16": "1152x2048",
134
+ "4:3": "2048x1536",
135
+ "3:4": "1536x2048",
136
+ };
137
+
138
+ class HiggsfieldImageModel implements ImageModelV3 {
139
+ readonly specificationVersion = "v3" as const;
140
+ readonly provider = "higgsfield";
141
+ readonly modelId: string;
142
+ readonly maxImagesPerCall = 4;
143
+
144
+ private apiKey: string;
145
+ private apiSecret: string;
146
+ private baseURL: string;
147
+ private modelSettings: HiggsfieldImageModelSettings;
148
+
149
+ get settings(): HiggsfieldImageModelSettings {
150
+ return this.modelSettings;
151
+ }
152
+
153
+ constructor(
154
+ modelId: string,
155
+ options: {
156
+ apiKey?: string;
157
+ apiSecret?: string;
158
+ baseURL?: string;
159
+ } & HiggsfieldImageModelSettings = {},
160
+ ) {
161
+ this.modelId = modelId;
162
+ this.apiKey = options.apiKey ?? process.env.HIGGSFIELD_API_KEY ?? "";
163
+ this.apiSecret = options.apiSecret ?? process.env.HIGGSFIELD_SECRET ?? "";
164
+ this.baseURL = options.baseURL ?? "https://platform.higgsfield.ai";
165
+ this.modelSettings = {
166
+ styleId: options.styleId,
167
+ quality: options.quality,
168
+ enhancePrompt: options.enhancePrompt,
169
+ };
170
+ }
171
+
172
+ async doGenerate(options: ImageModelV3CallOptions) {
173
+ const { prompt, n, aspectRatio, providerOptions, abortSignal } = options;
174
+ const warnings: SharedV3Warning[] = [];
175
+
176
+ // Map aspect ratio to width_and_height
177
+ const widthAndHeight = aspectRatio
178
+ ? (ASPECT_RATIO_TO_SIZE[aspectRatio] ?? "1536x1536")
179
+ : "1536x1536";
180
+
181
+ if (aspectRatio && !ASPECT_RATIO_TO_SIZE[aspectRatio]) {
182
+ warnings.push({
183
+ type: "unsupported",
184
+ feature: "aspectRatio",
185
+ details: `Aspect ratio ${aspectRatio} not supported. Using 1:1. Supported: 1:1, 16:9, 9:16, 4:3, 3:4.`,
186
+ });
187
+ }
188
+
189
+ // Build params object - matching working implementation
190
+ const params: Record<string, unknown> = {
191
+ prompt,
192
+ width_and_height: widthAndHeight,
193
+ enhance_prompt: this.modelSettings.enhancePrompt ?? false,
194
+ quality: this.modelSettings.quality ?? "1080p",
195
+ batch_size: n && n <= 4 ? n : 1,
196
+ };
197
+
198
+ // Add optional parameters only if provided
199
+ if (this.modelSettings.styleId) {
200
+ params.style_id = this.modelSettings.styleId;
201
+ }
202
+
203
+ // Merge provider options
204
+ const higgsfieldOptions = providerOptions?.higgsfield as
205
+ | Record<string, unknown>
206
+ | undefined;
207
+ if (higgsfieldOptions) {
208
+ for (const [key, value] of Object.entries(higgsfieldOptions)) {
209
+ if (value !== undefined && value !== null) {
210
+ params[key] = value;
211
+ }
212
+ }
213
+ }
214
+
215
+ // Request body wrapped in params
216
+ const requestBody = { params };
217
+
218
+ // Make request to /v1/text2image/soul
219
+ const response = await fetch(`${this.baseURL}/v1/text2image/soul`, {
220
+ method: "POST",
221
+ headers: {
222
+ "hf-api-key": this.apiKey,
223
+ "hf-secret": this.apiSecret,
224
+ "Content-Type": "application/json",
225
+ Accept: "application/json",
226
+ },
227
+ body: JSON.stringify(requestBody),
228
+ signal: abortSignal,
229
+ });
230
+
231
+ if (!response.ok) {
232
+ const errorText = await response.text();
233
+ throw new Error(
234
+ `Higgsfield Soul API error (${response.status}): ${errorText}`,
235
+ );
236
+ }
237
+
238
+ const data = (await response.json()) as { id?: string };
239
+ const jobId = data?.id;
240
+
241
+ if (!jobId) {
242
+ throw new Error("No job ID returned from Higgsfield Soul API");
243
+ }
244
+
245
+ // Poll for results
246
+ const imageUrl = await this.pollForResult(jobId, abortSignal);
247
+
248
+ // Download image
249
+ const imageResponse = await fetch(imageUrl, { signal: abortSignal });
250
+ const imageBuffer = new Uint8Array(await imageResponse.arrayBuffer());
251
+
252
+ return {
253
+ images: [imageBuffer],
254
+ warnings,
255
+ response: {
256
+ timestamp: new Date(),
257
+ modelId: this.modelId,
258
+ headers: undefined,
259
+ },
260
+ };
261
+ }
262
+
263
+ private async pollForResult(
264
+ jobId: string,
265
+ abortSignal?: AbortSignal,
266
+ ): Promise<string> {
267
+ const maxAttempts = 60;
268
+ const pollInterval = 5000; // 5 seconds
269
+
270
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
271
+ const response = await fetch(`${this.baseURL}/v1/job-sets/${jobId}`, {
272
+ method: "GET",
273
+ headers: {
274
+ "hf-api-key": this.apiKey,
275
+ "hf-secret": this.apiSecret,
276
+ Accept: "application/json",
277
+ },
278
+ signal: abortSignal,
279
+ });
280
+
281
+ if (!response.ok) {
282
+ const errorText = await response.text();
283
+ throw new Error(
284
+ `Higgsfield polling error (${response.status}): ${errorText}`,
285
+ );
286
+ }
287
+
288
+ const jobSet = (await response.json()) as {
289
+ jobs?: Array<{
290
+ status?: string;
291
+ results?: {
292
+ min?: { url?: string };
293
+ raw?: { url?: string };
294
+ };
295
+ }>;
296
+ };
297
+
298
+ // Check if jobs array exists and has at least one job
299
+ if (
300
+ !jobSet?.jobs ||
301
+ !Array.isArray(jobSet.jobs) ||
302
+ jobSet.jobs.length === 0
303
+ ) {
304
+ throw new Error("No jobs found in Higgsfield JobSet response");
305
+ }
306
+
307
+ const job = jobSet.jobs[0];
308
+ const jobStatus = job?.status;
309
+
310
+ if (jobStatus === "completed") {
311
+ const results = job?.results;
312
+
313
+ if (results) {
314
+ // Try to get URL from results object
315
+ const imageUrl = results.min?.url ?? results.raw?.url;
316
+
317
+ if (imageUrl) {
318
+ return imageUrl;
319
+ }
320
+ }
321
+
322
+ throw new Error("No result URL found in completed Higgsfield job");
323
+ }
324
+
325
+ if (jobStatus === "failed") {
326
+ throw new Error("Higgsfield job failed");
327
+ }
328
+
329
+ if (jobStatus === "nsfw") {
330
+ throw new Error("Higgsfield job rejected due to NSFW content");
331
+ }
332
+
333
+ // Still processing, wait before next poll
334
+ if (attempt < maxAttempts - 1) {
335
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
336
+ }
337
+ }
338
+
339
+ throw new Error("Higgsfield generation timed out after 5 minutes");
340
+ }
341
+ }
342
+
343
+ export interface HiggsfieldProviderSettings {
344
+ apiKey?: string;
345
+ apiSecret?: string;
346
+ baseURL?: string;
347
+ defaultModelSettings?: HiggsfieldImageModelSettings;
348
+ }
349
+
350
+ export interface HiggsfieldProvider {
351
+ imageModel(
352
+ modelId: ImageModelId | (string & {}),
353
+ settings?: HiggsfieldImageModelSettings,
354
+ ): ImageModelV3;
355
+ /** Available Soul styles for image generation */
356
+ styles: typeof SOUL_STYLES;
357
+ }
358
+
359
+ export function createHiggsfield(
360
+ settings: HiggsfieldProviderSettings = {},
361
+ ): HiggsfieldProvider {
362
+ return {
363
+ imageModel(
364
+ modelId: string,
365
+ modelSettings?: HiggsfieldImageModelSettings,
366
+ ): ImageModelV3 {
367
+ return new HiggsfieldImageModel(modelId, {
368
+ apiKey: settings.apiKey,
369
+ apiSecret: settings.apiSecret,
370
+ baseURL: settings.baseURL,
371
+ ...settings.defaultModelSettings,
372
+ ...modelSettings,
373
+ });
374
+ },
375
+ styles: SOUL_STYLES,
376
+ };
377
+ }
378
+
379
+ export const higgsfield = createHiggsfield();
@@ -0,0 +1,251 @@
1
+ import {
2
+ createOpenAI as createOpenAIBase,
3
+ type OpenAIProvider as OpenAIProviderBase,
4
+ type OpenAIProviderSettings,
5
+ } from "@ai-sdk/openai";
6
+ import type { SharedV3Warning } from "@ai-sdk/provider";
7
+ import type { VideoModelV3, VideoModelV3CallOptions } from "../video-model";
8
+
9
+ // re-export base types
10
+ export type { OpenAIProviderSettings };
11
+
12
+ const VIDEO_MODELS = ["sora-2", "sora-2-pro"] as const;
13
+ type VideoModelId = (typeof VIDEO_MODELS)[number];
14
+
15
+ const SIZE_MAP: Record<string, string> = {
16
+ "9:16": "720x1280",
17
+ "16:9": "1280x720",
18
+ };
19
+
20
+ class OpenAIVideoModel implements VideoModelV3 {
21
+ readonly specificationVersion = "v3" as const;
22
+ readonly provider = "openai";
23
+ readonly modelId: string;
24
+ readonly maxVideosPerCall = 1;
25
+
26
+ private apiKey: string;
27
+ private baseURL: string;
28
+
29
+ constructor(
30
+ modelId: string,
31
+ options: { apiKey?: string; baseURL?: string } = {},
32
+ ) {
33
+ this.modelId = modelId;
34
+ this.apiKey = options.apiKey ?? process.env.OPENAI_API_KEY ?? "";
35
+ this.baseURL = options.baseURL ?? "https://api.openai.com/v1";
36
+ }
37
+
38
+ async doGenerate(options: VideoModelV3CallOptions) {
39
+ const {
40
+ prompt,
41
+ duration,
42
+ aspectRatio,
43
+ files,
44
+ providerOptions,
45
+ abortSignal,
46
+ } = options;
47
+ const warnings: SharedV3Warning[] = [];
48
+
49
+ // build form data
50
+ const formData = new FormData();
51
+ formData.append("model", this.modelId);
52
+ formData.append("prompt", prompt);
53
+
54
+ // duration: sora accepts 4, 8, 12 seconds
55
+ const seconds = duration ?? 4;
56
+ const validSeconds = [4, 8, 12].includes(seconds) ? seconds : 4;
57
+ if (duration && ![4, 8, 12].includes(duration)) {
58
+ warnings.push({
59
+ type: "unsupported",
60
+ feature: "duration",
61
+ details: `Duration ${duration}s not supported. Using ${validSeconds}s. Sora supports: 4, 8, 12 seconds.`,
62
+ });
63
+ }
64
+ formData.append("seconds", String(validSeconds));
65
+
66
+ // size from aspect ratio
67
+ if (aspectRatio) {
68
+ const size = SIZE_MAP[aspectRatio];
69
+ if (size) {
70
+ formData.append("size", size);
71
+ } else {
72
+ warnings.push({
73
+ type: "unsupported",
74
+ feature: "aspectRatio",
75
+ details: `Aspect ratio ${aspectRatio} not directly supported. Use 9:16, 16:9, or 1:1.`,
76
+ });
77
+ }
78
+ }
79
+
80
+ // image input for i2v
81
+ if (files && files.length > 0) {
82
+ const imageFile = files.find((f) => {
83
+ if (f.type === "file") return f.mediaType?.startsWith("image/");
84
+ return /\.(jpg|jpeg|png|webp)$/i.test(f.url);
85
+ });
86
+
87
+ if (imageFile) {
88
+ let blob: Blob;
89
+ if (imageFile.type === "file") {
90
+ const data =
91
+ typeof imageFile.data === "string"
92
+ ? Uint8Array.from(atob(imageFile.data), (c) => c.charCodeAt(0))
93
+ : imageFile.data;
94
+ blob = new Blob([data], { type: imageFile.mediaType });
95
+ } else {
96
+ const response = await fetch(imageFile.url, { signal: abortSignal });
97
+ blob = await response.blob();
98
+ }
99
+ formData.append("input_reference", blob, "input.png");
100
+ }
101
+ }
102
+
103
+ // provider options passthrough
104
+ const openaiOptions = providerOptions?.openai as
105
+ | Record<string, unknown>
106
+ | undefined;
107
+ if (openaiOptions) {
108
+ for (const [key, value] of Object.entries(openaiOptions)) {
109
+ if (
110
+ value !== undefined &&
111
+ !["model", "prompt", "seconds", "size", "input_reference"].includes(
112
+ key,
113
+ )
114
+ ) {
115
+ formData.append(key, String(value));
116
+ }
117
+ }
118
+ }
119
+
120
+ // unsupported options
121
+ if (options.seed !== undefined) {
122
+ warnings.push({
123
+ type: "unsupported",
124
+ feature: "seed",
125
+ details: "Seed is not supported by OpenAI Sora",
126
+ });
127
+ }
128
+ if (options.fps !== undefined) {
129
+ warnings.push({
130
+ type: "unsupported",
131
+ feature: "fps",
132
+ details: "FPS is not configurable for Sora",
133
+ });
134
+ }
135
+ if (options.resolution !== undefined) {
136
+ warnings.push({
137
+ type: "unsupported",
138
+ feature: "resolution",
139
+ details: "Use aspectRatio instead. Sora supports 9:16, 16:9, 1:1.",
140
+ });
141
+ }
142
+
143
+ // create video job
144
+ const createResponse = await fetch(`${this.baseURL}/videos`, {
145
+ method: "POST",
146
+ headers: {
147
+ Authorization: `Bearer ${this.apiKey}`,
148
+ },
149
+ body: formData,
150
+ signal: abortSignal,
151
+ });
152
+
153
+ if (!createResponse.ok) {
154
+ const error = await createResponse.text();
155
+ throw new Error(`OpenAI video creation failed: ${error}`);
156
+ }
157
+
158
+ const job = (await createResponse.json()) as {
159
+ id: string;
160
+ status: string;
161
+ progress?: number;
162
+ };
163
+
164
+ // poll for completion
165
+ let status = job.status;
166
+ const videoId = job.id;
167
+
168
+ while (status === "queued" || status === "in_progress") {
169
+ await new Promise((resolve) => setTimeout(resolve, 2000));
170
+
171
+ const statusResponse = await fetch(`${this.baseURL}/videos/${videoId}`, {
172
+ headers: {
173
+ Authorization: `Bearer ${this.apiKey}`,
174
+ },
175
+ signal: abortSignal,
176
+ });
177
+
178
+ if (!statusResponse.ok) {
179
+ throw new Error(
180
+ `Failed to check video status: ${await statusResponse.text()}`,
181
+ );
182
+ }
183
+
184
+ const statusData = (await statusResponse.json()) as {
185
+ status: string;
186
+ progress?: number;
187
+ };
188
+ status = statusData.status;
189
+ }
190
+
191
+ if (status === "failed") {
192
+ throw new Error("OpenAI video generation failed");
193
+ }
194
+
195
+ // download completed video
196
+ const contentResponse = await fetch(
197
+ `${this.baseURL}/videos/${videoId}/content`,
198
+ {
199
+ headers: {
200
+ Authorization: `Bearer ${this.apiKey}`,
201
+ },
202
+ signal: abortSignal,
203
+ },
204
+ );
205
+
206
+ if (!contentResponse.ok) {
207
+ throw new Error(
208
+ `Failed to download video: ${await contentResponse.text()}`,
209
+ );
210
+ }
211
+
212
+ const videoBuffer = await contentResponse.arrayBuffer();
213
+
214
+ return {
215
+ videos: [new Uint8Array(videoBuffer)],
216
+ warnings,
217
+ response: {
218
+ timestamp: new Date(),
219
+ modelId: this.modelId,
220
+ headers: undefined,
221
+ },
222
+ };
223
+ }
224
+ }
225
+
226
+ export interface OpenAIProvider extends OpenAIProviderBase {
227
+ videoModel(modelId: VideoModelId): VideoModelV3;
228
+ }
229
+
230
+ export function createOpenAI(
231
+ settings: OpenAIProviderSettings = {},
232
+ ): OpenAIProvider {
233
+ const base = createOpenAIBase(settings);
234
+
235
+ // create a callable function that also has all the methods
236
+ const provider = ((modelId: string) => base(modelId)) as OpenAIProvider;
237
+
238
+ // copy all properties from base
239
+ Object.assign(provider, base);
240
+
241
+ // add videoModel method
242
+ provider.videoModel = (modelId: VideoModelId): VideoModelV3 =>
243
+ new OpenAIVideoModel(modelId, {
244
+ apiKey: settings.apiKey,
245
+ baseURL: settings.baseURL,
246
+ });
247
+
248
+ return provider;
249
+ }
250
+
251
+ export const openai = createOpenAI();
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Replicate provider - re-exports @ai-sdk/replicate
3
+ *
4
+ * For background removal, use prompt.images:
5
+ *
6
+ * const { image } = await generateImage({
7
+ * model: replicate.image("851-labs/background-remover"),
8
+ * prompt: { images: [imageData] },
9
+ * });
10
+ */
11
+ export {
12
+ createReplicate,
13
+ type ReplicateProvider,
14
+ type ReplicateProviderSettings,
15
+ replicate,
16
+ } from "@ai-sdk/replicate";