varg.ai-sdk 0.1.1 → 0.4.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/.claude/settings.local.json +1 -1
  2. package/.env.example +3 -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 +10 -3
  11. package/CONTRIBUTING.md +150 -0
  12. package/LICENSE.md +53 -0
  13. package/README.md +56 -209
  14. package/SKILLS.md +26 -10
  15. package/biome.json +7 -1
  16. package/bun.lock +1286 -0
  17. package/commitlint.config.js +22 -0
  18. package/docs/index.html +1130 -0
  19. package/docs/prompting.md +326 -0
  20. package/docs/react.md +834 -0
  21. package/docs/sdk.md +812 -0
  22. package/ffmpeg/CLAUDE.md +68 -0
  23. package/package.json +43 -10
  24. package/pipeline/cookbooks/scripts/animate-frames-parallel.ts +84 -0
  25. package/pipeline/cookbooks/scripts/combine-scenes.sh +53 -0
  26. package/pipeline/cookbooks/scripts/generate-frames-parallel.ts +99 -0
  27. package/pipeline/cookbooks/scripts/still-to-video.sh +37 -0
  28. package/pipeline/cookbooks/text-to-tiktok.md +669 -0
  29. package/pipeline/cookbooks/trendwatching.md +156 -0
  30. package/plan.md +281 -0
  31. package/scripts/.gitkeep +0 -0
  32. package/src/ai-sdk/cache.ts +142 -0
  33. package/src/ai-sdk/examples/cached-generation.ts +53 -0
  34. package/src/ai-sdk/examples/duet-scene-4.ts +53 -0
  35. package/src/ai-sdk/examples/duet-scene-5-audio.ts +32 -0
  36. package/src/ai-sdk/examples/duet-video.ts +56 -0
  37. package/src/ai-sdk/examples/editly-composition.ts +63 -0
  38. package/src/ai-sdk/examples/editly-test.ts +57 -0
  39. package/src/ai-sdk/examples/editly-video-test.ts +52 -0
  40. package/src/ai-sdk/examples/fal-lipsync.ts +43 -0
  41. package/src/ai-sdk/examples/higgsfield-image.ts +61 -0
  42. package/src/ai-sdk/examples/music-generation.ts +19 -0
  43. package/src/ai-sdk/examples/openai-sora.ts +34 -0
  44. package/src/ai-sdk/examples/replicate-bg-removal.ts +52 -0
  45. package/src/ai-sdk/examples/simpsons-scene.ts +61 -0
  46. package/src/ai-sdk/examples/talking-lion.ts +55 -0
  47. package/src/ai-sdk/examples/video-generation.ts +39 -0
  48. package/src/ai-sdk/examples/workflow-animated-girl.ts +104 -0
  49. package/src/ai-sdk/examples/workflow-before-after.ts +114 -0
  50. package/src/ai-sdk/examples/workflow-character-grid.ts +112 -0
  51. package/src/ai-sdk/examples/workflow-slideshow.ts +161 -0
  52. package/src/ai-sdk/file-cache.ts +112 -0
  53. package/src/ai-sdk/file.ts +238 -0
  54. package/src/ai-sdk/generate-element.ts +92 -0
  55. package/src/ai-sdk/generate-music.ts +46 -0
  56. package/src/ai-sdk/generate-video.ts +165 -0
  57. package/src/ai-sdk/index.ts +72 -0
  58. package/src/ai-sdk/music-model.ts +110 -0
  59. package/src/ai-sdk/providers/editly/editly.test.ts +1108 -0
  60. package/src/ai-sdk/providers/editly/ffmpeg.ts +60 -0
  61. package/src/ai-sdk/providers/editly/index.ts +817 -0
  62. package/src/ai-sdk/providers/editly/layers.ts +776 -0
  63. package/src/ai-sdk/providers/editly/plan.md +144 -0
  64. package/src/ai-sdk/providers/editly/types.ts +328 -0
  65. package/src/ai-sdk/providers/elevenlabs-provider.ts +255 -0
  66. package/src/ai-sdk/providers/fal-provider.ts +512 -0
  67. package/src/ai-sdk/providers/higgsfield.ts +379 -0
  68. package/src/ai-sdk/providers/openai.ts +251 -0
  69. package/src/ai-sdk/providers/replicate.ts +16 -0
  70. package/src/ai-sdk/video-model.ts +185 -0
  71. package/src/cli/commands/find.tsx +137 -0
  72. package/src/cli/commands/help.tsx +85 -0
  73. package/src/cli/commands/index.ts +6 -0
  74. package/src/cli/commands/list.tsx +238 -0
  75. package/src/cli/commands/render.tsx +71 -0
  76. package/src/cli/commands/run.tsx +511 -0
  77. package/src/cli/commands/which.tsx +253 -0
  78. package/src/cli/index.ts +114 -0
  79. package/src/cli/quiet.ts +44 -0
  80. package/src/cli/types.ts +32 -0
  81. package/src/cli/ui/components/Badge.tsx +29 -0
  82. package/src/cli/ui/components/DataTable.tsx +51 -0
  83. package/src/cli/ui/components/Header.tsx +23 -0
  84. package/src/cli/ui/components/HelpBlock.tsx +44 -0
  85. package/src/cli/ui/components/KeyValue.tsx +33 -0
  86. package/src/cli/ui/components/OptionRow.tsx +81 -0
  87. package/src/cli/ui/components/Separator.tsx +23 -0
  88. package/src/cli/ui/components/StatusBox.tsx +108 -0
  89. package/src/cli/ui/components/VargBox.tsx +51 -0
  90. package/src/cli/ui/components/VargProgress.tsx +36 -0
  91. package/src/cli/ui/components/VargSpinner.tsx +34 -0
  92. package/src/cli/ui/components/VargText.tsx +56 -0
  93. package/src/cli/ui/components/index.ts +19 -0
  94. package/src/cli/ui/index.ts +12 -0
  95. package/src/cli/ui/render.ts +35 -0
  96. package/src/cli/ui/theme.ts +63 -0
  97. package/src/cli/utils.ts +78 -0
  98. package/src/core/executor/executor.ts +201 -0
  99. package/src/core/executor/index.ts +13 -0
  100. package/src/core/executor/job.ts +214 -0
  101. package/src/core/executor/pipeline.ts +222 -0
  102. package/src/core/index.ts +11 -0
  103. package/src/core/registry/index.ts +9 -0
  104. package/src/core/registry/loader.ts +149 -0
  105. package/src/core/registry/registry.ts +221 -0
  106. package/src/core/registry/resolver.ts +206 -0
  107. package/src/core/schema/helpers.ts +134 -0
  108. package/src/core/schema/index.ts +8 -0
  109. package/src/core/schema/shared.ts +102 -0
  110. package/src/core/schema/types.ts +279 -0
  111. package/src/core/schema/validator.ts +92 -0
  112. package/src/definitions/actions/captions.ts +261 -0
  113. package/src/definitions/actions/edit.ts +298 -0
  114. package/src/definitions/actions/image.ts +125 -0
  115. package/src/definitions/actions/index.ts +114 -0
  116. package/src/definitions/actions/music.ts +205 -0
  117. package/src/definitions/actions/sync.ts +128 -0
  118. package/{action/transcribe/index.ts → src/definitions/actions/transcribe.ts} +58 -68
  119. package/src/definitions/actions/upload.ts +111 -0
  120. package/src/definitions/actions/video.ts +163 -0
  121. package/src/definitions/actions/voice.ts +119 -0
  122. package/src/definitions/index.ts +23 -0
  123. package/src/definitions/models/elevenlabs.ts +50 -0
  124. package/src/definitions/models/flux.ts +56 -0
  125. package/src/definitions/models/index.ts +36 -0
  126. package/src/definitions/models/kling.ts +56 -0
  127. package/src/definitions/models/llama.ts +54 -0
  128. package/src/definitions/models/nano-banana-pro.ts +102 -0
  129. package/src/definitions/models/sonauto.ts +68 -0
  130. package/src/definitions/models/soul.ts +65 -0
  131. package/src/definitions/models/wan.ts +54 -0
  132. package/src/definitions/models/whisper.ts +44 -0
  133. package/src/definitions/skills/index.ts +12 -0
  134. package/src/definitions/skills/talking-character.ts +87 -0
  135. package/src/definitions/skills/text-to-tiktok.ts +97 -0
  136. package/src/index.ts +118 -0
  137. package/src/providers/apify.ts +269 -0
  138. package/src/providers/base.ts +264 -0
  139. package/src/providers/elevenlabs.ts +217 -0
  140. package/src/providers/fal.ts +392 -0
  141. package/src/providers/ffmpeg.ts +544 -0
  142. package/src/providers/fireworks.ts +193 -0
  143. package/src/providers/groq.ts +149 -0
  144. package/src/providers/higgsfield.ts +145 -0
  145. package/src/providers/index.ts +143 -0
  146. package/src/providers/replicate.ts +147 -0
  147. package/src/providers/storage.ts +206 -0
  148. package/src/react/cli.ts +52 -0
  149. package/src/react/elements.ts +146 -0
  150. package/src/react/examples/branching.tsx +66 -0
  151. package/src/react/examples/captions-demo.tsx +37 -0
  152. package/src/react/examples/character-video.tsx +84 -0
  153. package/src/react/examples/grid.tsx +53 -0
  154. package/src/react/examples/layouts-demo.tsx +57 -0
  155. package/src/react/examples/madi.tsx +60 -0
  156. package/src/react/examples/music-test.tsx +35 -0
  157. package/src/react/examples/onlyfans-1m/workflow.tsx +88 -0
  158. package/src/react/examples/orange-portrait.tsx +41 -0
  159. package/src/react/examples/split-element-demo.tsx +60 -0
  160. package/src/react/examples/split-layout-demo.tsx +60 -0
  161. package/src/react/examples/split.tsx +41 -0
  162. package/src/react/examples/video-grid.tsx +46 -0
  163. package/src/react/index.ts +43 -0
  164. package/src/react/layouts/grid.tsx +28 -0
  165. package/src/react/layouts/index.ts +2 -0
  166. package/src/react/layouts/split.tsx +20 -0
  167. package/src/react/react.test.ts +309 -0
  168. package/src/react/render.ts +21 -0
  169. package/src/react/renderers/animate.ts +59 -0
  170. package/src/react/renderers/captions.ts +297 -0
  171. package/src/react/renderers/clip.ts +248 -0
  172. package/src/react/renderers/context.ts +17 -0
  173. package/src/react/renderers/image.ts +109 -0
  174. package/src/react/renderers/index.ts +22 -0
  175. package/src/react/renderers/music.ts +60 -0
  176. package/src/react/renderers/packshot.ts +84 -0
  177. package/src/react/renderers/progress.ts +173 -0
  178. package/src/react/renderers/render.ts +243 -0
  179. package/src/react/renderers/slider.ts +69 -0
  180. package/src/react/renderers/speech.ts +53 -0
  181. package/src/react/renderers/split.ts +91 -0
  182. package/src/react/renderers/subtitle.ts +16 -0
  183. package/src/react/renderers/swipe.ts +75 -0
  184. package/src/react/renderers/title.ts +17 -0
  185. package/src/react/renderers/utils.ts +124 -0
  186. package/src/react/renderers/video.ts +127 -0
  187. package/src/react/runtime/jsx-dev-runtime.ts +43 -0
  188. package/src/react/runtime/jsx-runtime.ts +35 -0
  189. package/src/react/types.ts +232 -0
  190. package/src/studio/index.ts +26 -0
  191. package/src/studio/scanner.ts +102 -0
  192. package/src/studio/server.ts +554 -0
  193. package/src/studio/stages.ts +251 -0
  194. package/src/studio/step-renderer.ts +279 -0
  195. package/src/studio/types.ts +60 -0
  196. package/src/studio/ui/cache.html +303 -0
  197. package/src/studio/ui/index.html +1820 -0
  198. package/src/tests/all.test.ts +509 -0
  199. package/src/tests/index.ts +33 -0
  200. package/src/tests/unit.test.ts +403 -0
  201. package/tsconfig.cli.json +8 -0
  202. package/tsconfig.json +21 -3
  203. package/TEST_RESULTS.md +0 -122
  204. package/action/captions/SKILL.md +0 -170
  205. package/action/captions/index.ts +0 -169
  206. package/action/edit/SKILL.md +0 -235
  207. package/action/edit/index.ts +0 -437
  208. package/action/image/SKILL.md +0 -140
  209. package/action/image/index.ts +0 -105
  210. package/action/sync/SKILL.md +0 -136
  211. package/action/sync/index.ts +0 -145
  212. package/action/transcribe/SKILL.md +0 -179
  213. package/action/video/SKILL.md +0 -116
  214. package/action/video/index.ts +0 -125
  215. package/action/voice/SKILL.md +0 -125
  216. package/action/voice/index.ts +0 -136
  217. package/cli/commands/find.ts +0 -58
  218. package/cli/commands/help.ts +0 -70
  219. package/cli/commands/list.ts +0 -49
  220. package/cli/commands/run.ts +0 -237
  221. package/cli/commands/which.ts +0 -66
  222. package/cli/discover.ts +0 -66
  223. package/cli/index.ts +0 -33
  224. package/cli/runner.ts +0 -65
  225. package/cli/types.ts +0 -49
  226. package/cli/ui.ts +0 -185
  227. package/index.ts +0 -75
  228. package/lib/README.md +0 -144
  229. package/lib/ai-sdk/fal.ts +0 -106
  230. package/lib/ai-sdk/replicate.ts +0 -107
  231. package/lib/elevenlabs.ts +0 -382
  232. package/lib/fal.ts +0 -467
  233. package/lib/ffmpeg.ts +0 -467
  234. package/lib/fireworks.ts +0 -235
  235. package/lib/groq.ts +0 -246
  236. package/lib/higgsfield.ts +0 -176
  237. package/lib/remotion/SKILL.md +0 -823
  238. package/lib/remotion/cli.ts +0 -115
  239. package/lib/remotion/functions.ts +0 -283
  240. package/lib/remotion/index.ts +0 -19
  241. package/lib/remotion/templates.ts +0 -73
  242. package/lib/replicate.ts +0 -304
  243. package/output.txt +0 -1
  244. package/test-import.ts +0 -7
  245. package/test-services.ts +0 -97
  246. package/utilities/s3.ts +0 -147
@@ -0,0 +1,269 @@
1
+ /**
2
+ * Apify provider for running actors and web scraping
3
+ * Supports TikTok scraping, web scraping, and other Apify actors
4
+ */
5
+
6
+ import { ApifyClient } from "apify-client";
7
+ import type { JobStatusUpdate, ProviderConfig } from "../core/schema/types";
8
+ import { BaseProvider } from "./base";
9
+
10
+ export interface ApifyProviderConfig extends ProviderConfig {
11
+ /** Apify API token (defaults to APIFY_TOKEN env var) */
12
+ token?: string;
13
+ }
14
+
15
+ export interface RunActorOptions {
16
+ /** Actor ID in format "username/actor-name" */
17
+ actorId: string;
18
+ /** Input parameters for the actor */
19
+ input?: Record<string, unknown>;
20
+ /** Whether to wait for the actor to finish (default: true) */
21
+ waitForFinish?: boolean;
22
+ }
23
+
24
+ export interface ApifyRunResult {
25
+ /** Run ID */
26
+ runId: string;
27
+ /** Dataset ID containing results */
28
+ datasetId: string;
29
+ /** Run status */
30
+ status: string;
31
+ /** Result items (if waitForFinish was true) */
32
+ items?: unknown[];
33
+ }
34
+
35
+ export class ApifyProvider extends BaseProvider {
36
+ readonly name = "apify";
37
+ private client: ApifyClient;
38
+
39
+ constructor(config?: ApifyProviderConfig) {
40
+ super(config);
41
+ this.client = new ApifyClient({
42
+ token: config?.token || process.env.APIFY_TOKEN,
43
+ });
44
+ }
45
+
46
+ /**
47
+ * Submit an actor run job
48
+ * @param model - Actor ID (e.g., "clockworks/tiktok-scraper")
49
+ * @param inputs - Actor input parameters
50
+ */
51
+ async submit(
52
+ model: string,
53
+ inputs: Record<string, unknown>,
54
+ _config?: ProviderConfig,
55
+ ): Promise<string> {
56
+ console.log(`[apify] submitting actor: ${model}`);
57
+
58
+ const actor = this.client.actor(model);
59
+ const run = await actor.start(inputs);
60
+
61
+ console.log(`[apify] run started: ${run.id}`);
62
+ return run.id;
63
+ }
64
+
65
+ /**
66
+ * Get the status of an actor run
67
+ */
68
+ async getStatus(jobId: string): Promise<JobStatusUpdate> {
69
+ const run = await this.client.run(jobId).get();
70
+
71
+ if (!run) {
72
+ return { status: "failed", error: "Run not found" };
73
+ }
74
+
75
+ const statusMap: Record<string, JobStatusUpdate["status"]> = {
76
+ READY: "queued",
77
+ RUNNING: "processing",
78
+ SUCCEEDED: "completed",
79
+ FAILED: "failed",
80
+ ABORTED: "cancelled",
81
+ ABORTING: "cancelled",
82
+ TIMED_OUT: "failed",
83
+ };
84
+
85
+ return {
86
+ status: statusMap[run.status] ?? "processing",
87
+ output: run.defaultDatasetId,
88
+ error: run.status === "FAILED" ? "Actor run failed" : undefined,
89
+ };
90
+ }
91
+
92
+ /**
93
+ * Get the results of a completed actor run
94
+ */
95
+ async getResult(jobId: string): Promise<unknown> {
96
+ const run = await this.client.run(jobId).get();
97
+
98
+ if (!run?.defaultDatasetId) {
99
+ throw new Error("Run not found or has no dataset");
100
+ }
101
+
102
+ const { items } = await this.client
103
+ .dataset(run.defaultDatasetId)
104
+ .listItems();
105
+ return items;
106
+ }
107
+
108
+ /**
109
+ * Cancel a running actor
110
+ */
111
+ override async cancel(jobId: string): Promise<void> {
112
+ await this.client.run(jobId).abort();
113
+ console.log(`[apify] run aborted: ${jobId}`);
114
+ }
115
+
116
+ // ============================================================================
117
+ // High-level convenience methods
118
+ // ============================================================================
119
+
120
+ /**
121
+ * Run an actor and optionally wait for results
122
+ */
123
+ async runActor(options: RunActorOptions): Promise<ApifyRunResult> {
124
+ console.log(`[apify] running actor: ${options.actorId}`);
125
+
126
+ const actor = this.client.actor(options.actorId);
127
+
128
+ if (options.waitForFinish !== false) {
129
+ // call() waits for the run to finish
130
+ const run = await actor.call(options.input);
131
+ console.log(`[apify] run completed: ${run.id}`);
132
+
133
+ // Get results from dataset
134
+ const { items } = await this.client
135
+ .dataset(run.defaultDatasetId)
136
+ .listItems();
137
+ console.log(`[apify] retrieved ${items.length} items`);
138
+
139
+ return {
140
+ runId: run.id,
141
+ datasetId: run.defaultDatasetId,
142
+ status: run.status,
143
+ items,
144
+ };
145
+ }
146
+
147
+ // start() returns immediately
148
+ const run = await actor.start(options.input);
149
+ console.log(`[apify] run started: ${run.id}`);
150
+
151
+ return {
152
+ runId: run.id,
153
+ datasetId: run.defaultDatasetId,
154
+ status: run.status,
155
+ };
156
+ }
157
+
158
+ /**
159
+ * Get items from a dataset
160
+ */
161
+ async getDataset(datasetId: string): Promise<unknown[]> {
162
+ console.log(`[apify] fetching dataset: ${datasetId}`);
163
+ const { items } = await this.client.dataset(datasetId).listItems();
164
+ console.log(`[apify] retrieved ${items.length} items`);
165
+ return items;
166
+ }
167
+
168
+ /**
169
+ * Get run info
170
+ */
171
+ async getRunInfo(runId: string) {
172
+ console.log(`[apify] fetching run info: ${runId}`);
173
+ const run = await this.client.run(runId).get();
174
+ return run;
175
+ }
176
+
177
+ /**
178
+ * Wait for a run to finish
179
+ */
180
+ async waitForRun(runId: string) {
181
+ console.log(`[apify] waiting for run: ${runId}`);
182
+ const run = await this.client.run(runId).waitForFinish();
183
+ console.log(`[apify] run finished with status: ${run?.status}`);
184
+ return run;
185
+ }
186
+
187
+ /**
188
+ * Get value from key-value store
189
+ */
190
+ async getKeyValueStoreValue(storeId: string, key: string) {
191
+ console.log(`[apify] fetching key "${key}" from store: ${storeId}`);
192
+ const value = await this.client.keyValueStore(storeId).getRecord(key);
193
+ return value;
194
+ }
195
+
196
+ /**
197
+ * Download videos from scraped results using yt-dlp
198
+ */
199
+ async downloadVideos(
200
+ items: Array<{ webVideoUrl?: string }>,
201
+ outputDir = "output/videos",
202
+ ): Promise<string[]> {
203
+ const urls = items
204
+ .map((item) => item.webVideoUrl)
205
+ .filter((url): url is string => !!url);
206
+
207
+ console.log(`[apify] downloading ${urls.length} videos`);
208
+
209
+ // Create download dir if needed
210
+ await Bun.$`mkdir -p ${outputDir}`;
211
+
212
+ const downloaded: string[] = [];
213
+
214
+ for (const url of urls) {
215
+ console.log(`[apify] downloading: ${url}`);
216
+ try {
217
+ await Bun.$`yt-dlp -o "${outputDir}/%(id)s.%(ext)s" ${url}`;
218
+ downloaded.push(url);
219
+ console.log(`[apify] downloaded successfully`);
220
+ } catch (err) {
221
+ console.error(`[apify] failed to download ${url}:`, err);
222
+ }
223
+ }
224
+
225
+ console.log(`[apify] all downloads complete. saved to: ${outputDir}`);
226
+ return downloaded;
227
+ }
228
+ }
229
+
230
+ // Popular actors registry
231
+ export const ACTORS = {
232
+ TIKTOK: {
233
+ SCRAPER: "clockworks/tiktok-scraper",
234
+ HASHTAG: "clockworks/tiktok-hashtag-scraper",
235
+ PROFILE: "clockworks/tiktok-profile-scraper",
236
+ },
237
+ WEB: {
238
+ SCRAPER: "apify/web-scraper",
239
+ CHEERIO: "apify/cheerio-scraper",
240
+ PUPPETEER: "apify/puppeteer-scraper",
241
+ },
242
+ SOCIAL: {
243
+ INSTAGRAM: "apify/instagram-scraper",
244
+ TWITTER: "apify/twitter-scraper",
245
+ YOUTUBE: "bernardo/youtube-scraper",
246
+ },
247
+ };
248
+
249
+ // Export singleton instance
250
+ export const apifyProvider = new ApifyProvider();
251
+
252
+ // Export convenience functions
253
+ export const runActor = (options: RunActorOptions) =>
254
+ apifyProvider.runActor(options);
255
+
256
+ export const getDataset = (datasetId: string) =>
257
+ apifyProvider.getDataset(datasetId);
258
+
259
+ export const getRunInfo = (runId: string) => apifyProvider.getRunInfo(runId);
260
+
261
+ export const waitForRun = (runId: string) => apifyProvider.waitForRun(runId);
262
+
263
+ export const getKeyValueStoreValue = (storeId: string, key: string) =>
264
+ apifyProvider.getKeyValueStoreValue(storeId, key);
265
+
266
+ export const downloadVideos = (
267
+ items: Array<{ webVideoUrl?: string }>,
268
+ outputDir?: string,
269
+ ) => apifyProvider.downloadVideos(items, outputDir);
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Base provider interface and abstract class
3
+ * All providers implement this interface for consistent execution
4
+ */
5
+
6
+ import type {
7
+ JobStatusUpdate,
8
+ Provider,
9
+ ProviderConfig,
10
+ } from "../core/schema/types";
11
+
12
+ /**
13
+ * Abstract base class for providers
14
+ * Provides common functionality and enforces the Provider interface
15
+ */
16
+ export abstract class BaseProvider implements Provider {
17
+ abstract readonly name: string;
18
+
19
+ protected config: ProviderConfig;
20
+
21
+ constructor(config: ProviderConfig = {}) {
22
+ this.config = {
23
+ timeout: 300000, // 5 minutes default
24
+ retries: 3,
25
+ ...config,
26
+ };
27
+ }
28
+
29
+ /**
30
+ * Submit a job to the provider
31
+ */
32
+ abstract submit(
33
+ model: string,
34
+ inputs: Record<string, unknown>,
35
+ config?: ProviderConfig,
36
+ ): Promise<string>;
37
+
38
+ /**
39
+ * Get the current status of a job
40
+ */
41
+ abstract getStatus(jobId: string): Promise<JobStatusUpdate>;
42
+
43
+ /**
44
+ * Get the result of a completed job
45
+ */
46
+ abstract getResult(jobId: string): Promise<unknown>;
47
+
48
+ /**
49
+ * Optional: Cancel a running job
50
+ */
51
+ async cancel?(jobId: string): Promise<void>;
52
+
53
+ /**
54
+ * Optional: Upload a file to the provider's storage
55
+ */
56
+ async uploadFile?(
57
+ file: File | Blob | ArrayBuffer,
58
+ filename?: string,
59
+ ): Promise<string>;
60
+
61
+ /**
62
+ * Helper: Wait for a job to complete with polling
63
+ */
64
+ protected async waitForCompletion(
65
+ jobId: string,
66
+ options: {
67
+ maxWait?: number;
68
+ pollInterval?: number;
69
+ onProgress?: (progress: number, logs?: string[]) => void;
70
+ } = {},
71
+ ): Promise<unknown> {
72
+ const { maxWait = this.config.timeout ?? 300000, pollInterval = 2000 } =
73
+ options;
74
+ const startTime = Date.now();
75
+ let currentInterval = pollInterval;
76
+
77
+ while (Date.now() - startTime < maxWait) {
78
+ const status = await this.getStatus(jobId);
79
+
80
+ if (options.onProgress && status.progress !== undefined) {
81
+ options.onProgress(status.progress, status.logs);
82
+ }
83
+
84
+ if (status.status === "completed") {
85
+ return status.output ?? (await this.getResult(jobId));
86
+ }
87
+
88
+ if (status.status === "failed") {
89
+ throw new Error(status.error ?? "Job failed");
90
+ }
91
+
92
+ if (status.status === "cancelled") {
93
+ throw new Error("Job was cancelled");
94
+ }
95
+
96
+ // Exponential backoff with cap
97
+ await this.sleep(currentInterval);
98
+ currentInterval = Math.min(currentInterval * 1.5, 10000);
99
+ }
100
+
101
+ throw new Error(`Job ${jobId} timed out after ${maxWait}ms`);
102
+ }
103
+
104
+ /**
105
+ * Helper: Sleep for a duration
106
+ */
107
+ protected sleep(ms: number): Promise<void> {
108
+ return new Promise((resolve) => setTimeout(resolve, ms));
109
+ }
110
+
111
+ /**
112
+ * Helper: Retry a function with exponential backoff
113
+ */
114
+ protected async retry<T>(
115
+ fn: () => Promise<T>,
116
+ options: { retries?: number; delay?: number } = {},
117
+ ): Promise<T> {
118
+ const { retries = this.config.retries ?? 3, delay = 1000 } = options;
119
+ let lastError: Error | undefined;
120
+
121
+ for (let attempt = 0; attempt <= retries; attempt++) {
122
+ try {
123
+ return await fn();
124
+ } catch (error) {
125
+ lastError = error instanceof Error ? error : new Error(String(error));
126
+ if (attempt < retries) {
127
+ await this.sleep(delay * 2 ** attempt);
128
+ }
129
+ }
130
+ }
131
+
132
+ throw lastError;
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Provider registry for managing multiple providers
138
+ */
139
+ export class ProviderRegistry {
140
+ private providers = new Map<string, Provider>();
141
+
142
+ /**
143
+ * Register a provider
144
+ */
145
+ register(provider: Provider): void {
146
+ this.providers.set(provider.name, provider);
147
+ }
148
+
149
+ /**
150
+ * Get a provider by name
151
+ */
152
+ get(name: string): Provider | undefined {
153
+ return this.providers.get(name);
154
+ }
155
+
156
+ /**
157
+ * Check if a provider exists
158
+ */
159
+ has(name: string): boolean {
160
+ return this.providers.has(name);
161
+ }
162
+
163
+ /**
164
+ * Get all registered providers
165
+ */
166
+ all(): Provider[] {
167
+ return Array.from(this.providers.values());
168
+ }
169
+
170
+ /**
171
+ * Get provider names
172
+ */
173
+ names(): string[] {
174
+ return Array.from(this.providers.keys());
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Global provider registry instance
180
+ */
181
+ export const providers = new ProviderRegistry();
182
+
183
+ /**
184
+ * Helper type for provider-specific result structures
185
+ */
186
+ export interface ProviderResult<T = unknown> {
187
+ data: T;
188
+ requestId?: string;
189
+ timing?: {
190
+ queueTime?: number;
191
+ processingTime?: number;
192
+ totalTime?: number;
193
+ };
194
+ }
195
+
196
+ /**
197
+ * Helper: Ensure a path is a URL (upload local files if needed)
198
+ */
199
+ export async function ensureUrl(
200
+ pathOrUrl: string,
201
+ uploader: (file: ArrayBuffer) => Promise<string>,
202
+ ): Promise<string> {
203
+ // Already a URL
204
+ if (pathOrUrl.startsWith("http://") || pathOrUrl.startsWith("https://")) {
205
+ return pathOrUrl;
206
+ }
207
+
208
+ // Local file - read and upload
209
+ const file = Bun.file(pathOrUrl);
210
+ if (!(await file.exists())) {
211
+ throw new Error(`Local file not found: ${pathOrUrl}`);
212
+ }
213
+
214
+ const buffer = await file.arrayBuffer();
215
+ return uploader(buffer);
216
+ }
217
+
218
+ /**
219
+ * Helper: Download a URL to a local file
220
+ */
221
+ export async function downloadToFile(
222
+ url: string,
223
+ outputPath: string,
224
+ ): Promise<string> {
225
+ const response = await fetch(url);
226
+ if (!response.ok) {
227
+ throw new Error(`Failed to download: ${response.statusText}`);
228
+ }
229
+
230
+ const buffer = await response.arrayBuffer();
231
+ await Bun.write(outputPath, buffer);
232
+ return outputPath;
233
+ }
234
+
235
+ /**
236
+ * Helper: Get file extension from URL or content type
237
+ */
238
+ export function getExtension(url: string, contentType?: string): string {
239
+ // Try to get from URL
240
+ const urlPath = new URL(url).pathname;
241
+ const urlExt = urlPath.split(".").pop();
242
+ if (urlExt && urlExt.length <= 4) {
243
+ return urlExt;
244
+ }
245
+
246
+ // Try content type
247
+ if (contentType) {
248
+ const typeMap: Record<string, string> = {
249
+ "video/mp4": "mp4",
250
+ "video/webm": "webm",
251
+ "video/quicktime": "mov",
252
+ "image/jpeg": "jpg",
253
+ "image/png": "png",
254
+ "image/webp": "webp",
255
+ "image/gif": "gif",
256
+ "audio/mpeg": "mp3",
257
+ "audio/wav": "wav",
258
+ "audio/ogg": "ogg",
259
+ };
260
+ return typeMap[contentType] ?? "bin";
261
+ }
262
+
263
+ return "bin";
264
+ }