@ai-sdk/prodia 2.0.0-beta.4 → 2.0.0-beta.53

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.
@@ -0,0 +1,266 @@
1
+ import type {
2
+ Experimental_VideoModelV4,
3
+ SharedV4Warning,
4
+ } from '@ai-sdk/provider';
5
+ import {
6
+ combineHeaders,
7
+ convertBase64ToUint8Array,
8
+ downloadBlob,
9
+ parseJSON,
10
+ parseProviderOptions,
11
+ postFormDataToApi,
12
+ postToApi,
13
+ resolve,
14
+ zodSchema,
15
+ } from '@ai-sdk/provider-utils';
16
+ import {
17
+ buildProdiaProviderMetadata,
18
+ parseMultipart,
19
+ prodiaFailedResponseHandler,
20
+ prodiaJobResultSchema,
21
+ type ProdiaJobResult,
22
+ type ProdiaModelConfig,
23
+ } from './prodia-api';
24
+ import { prodiaVideoModelOptionsSchema } from './prodia-video-model-options';
25
+ import type { ProdiaVideoModelId } from './prodia-video-model-settings';
26
+
27
+ export class ProdiaVideoModel implements Experimental_VideoModelV4 {
28
+ readonly specificationVersion = 'v4';
29
+ readonly maxVideosPerCall = 1;
30
+
31
+ get provider(): string {
32
+ return this.config.provider;
33
+ }
34
+
35
+ constructor(
36
+ readonly modelId: ProdiaVideoModelId,
37
+ private readonly config: ProdiaModelConfig,
38
+ ) {}
39
+
40
+ async doGenerate(
41
+ options: Parameters<Experimental_VideoModelV4['doGenerate']>[0],
42
+ ): Promise<Awaited<ReturnType<Experimental_VideoModelV4['doGenerate']>>> {
43
+ const warnings: Array<SharedV4Warning> = [];
44
+
45
+ const prodiaOptions = await parseProviderOptions({
46
+ provider: 'prodia',
47
+ providerOptions: options.providerOptions,
48
+ schema: prodiaVideoModelOptionsSchema,
49
+ });
50
+
51
+ const jobConfig: Record<string, unknown> = {};
52
+
53
+ if (options.prompt !== undefined) {
54
+ jobConfig.prompt = options.prompt;
55
+ }
56
+ if (options.seed !== undefined) {
57
+ jobConfig.seed = options.seed;
58
+ }
59
+ if (prodiaOptions?.resolution !== undefined) {
60
+ jobConfig.resolution = prodiaOptions.resolution;
61
+ }
62
+
63
+ const body = {
64
+ type: this.modelId,
65
+ config: jobConfig,
66
+ };
67
+
68
+ const currentDate = this.config._internal?.currentDate?.() ?? new Date();
69
+ const combinedHeaders = combineHeaders(
70
+ await resolve(this.config.headers),
71
+ options.headers,
72
+ );
73
+
74
+ let multipartResult: {
75
+ jobResult: ProdiaJobResult;
76
+ videoBytes: Uint8Array;
77
+ videoMediaType: string;
78
+ };
79
+ let responseHeaders: Record<string, string> | undefined;
80
+
81
+ if (options.image) {
82
+ // img2vid: multipart form-data request
83
+ const imageData = await resolveVideoFileData(
84
+ options.image,
85
+ options.abortSignal,
86
+ );
87
+ const formData = new FormData();
88
+ formData.append(
89
+ 'job',
90
+ new Blob([JSON.stringify(body)], { type: 'application/json' }),
91
+ 'job.json',
92
+ );
93
+ formData.append(
94
+ 'input',
95
+ new Blob([imageData.bytes], { type: imageData.mediaType }),
96
+ 'input' + getExtension(imageData.mediaType),
97
+ );
98
+
99
+ const result = await postFormDataToApi({
100
+ url: `${this.config.baseURL}/job?price=true`,
101
+ headers: {
102
+ ...combinedHeaders,
103
+ Accept: 'multipart/form-data; video/mp4',
104
+ },
105
+ formData,
106
+ failedResponseHandler: prodiaFailedResponseHandler,
107
+ successfulResponseHandler: createVideoMultipartResponseHandler(),
108
+ abortSignal: options.abortSignal,
109
+ fetch: this.config.fetch,
110
+ });
111
+
112
+ multipartResult = result.value;
113
+ responseHeaders = result.responseHeaders;
114
+ } else {
115
+ // txt2vid: JSON request
116
+ const result = await postToApi({
117
+ url: `${this.config.baseURL}/job?price=true`,
118
+ headers: {
119
+ ...combinedHeaders,
120
+ Accept: 'multipart/form-data; video/mp4',
121
+ 'Content-Type': 'application/json',
122
+ },
123
+ body: {
124
+ content: JSON.stringify(body),
125
+ values: body,
126
+ },
127
+ failedResponseHandler: prodiaFailedResponseHandler,
128
+ successfulResponseHandler: createVideoMultipartResponseHandler(),
129
+ abortSignal: options.abortSignal,
130
+ fetch: this.config.fetch,
131
+ });
132
+
133
+ multipartResult = result.value;
134
+ responseHeaders = result.responseHeaders;
135
+ }
136
+
137
+ const { jobResult, videoBytes, videoMediaType } = multipartResult;
138
+
139
+ return {
140
+ videos: [
141
+ {
142
+ type: 'binary',
143
+ data: videoBytes,
144
+ mediaType: videoMediaType,
145
+ },
146
+ ],
147
+ warnings,
148
+ providerMetadata: {
149
+ prodia: {
150
+ videos: [buildProdiaProviderMetadata(jobResult)],
151
+ },
152
+ },
153
+ response: {
154
+ modelId: this.modelId,
155
+ timestamp: currentDate,
156
+ headers: responseHeaders,
157
+ },
158
+ };
159
+ }
160
+ }
161
+
162
+ interface VideoMultipartResult {
163
+ jobResult: ProdiaJobResult;
164
+ videoBytes: Uint8Array;
165
+ videoMediaType: string;
166
+ }
167
+
168
+ function createVideoMultipartResponseHandler() {
169
+ return async ({
170
+ response,
171
+ }: {
172
+ response: Response;
173
+ }): Promise<{
174
+ value: VideoMultipartResult;
175
+ responseHeaders: Record<string, string>;
176
+ }> => {
177
+ const contentType = response.headers.get('content-type') ?? '';
178
+ const responseHeaders: Record<string, string> = {};
179
+ response.headers.forEach((value, key) => {
180
+ responseHeaders[key] = value;
181
+ });
182
+
183
+ const boundaryMatch = contentType.match(/boundary=([^\s;]+)/);
184
+ if (!boundaryMatch) {
185
+ throw new Error(
186
+ `Prodia response missing multipart boundary in content-type: ${contentType}`,
187
+ );
188
+ }
189
+ const boundary = boundaryMatch[1];
190
+
191
+ const arrayBuffer = await response.arrayBuffer();
192
+ const bytes = new Uint8Array(arrayBuffer);
193
+
194
+ const parts = parseMultipart(bytes, boundary);
195
+
196
+ let jobResult: ProdiaJobResult | undefined;
197
+ let videoBytes: Uint8Array | undefined;
198
+ let videoMediaType = 'video/mp4';
199
+
200
+ for (const part of parts) {
201
+ const contentDisposition = part.headers['content-disposition'] ?? '';
202
+ const partContentType = part.headers['content-type'] ?? '';
203
+
204
+ if (contentDisposition.includes('name="job"')) {
205
+ const jsonStr = new TextDecoder().decode(part.body);
206
+ jobResult = await parseJSON({
207
+ text: jsonStr,
208
+ schema: zodSchema(prodiaJobResultSchema),
209
+ });
210
+ } else if (contentDisposition.includes('name="output"')) {
211
+ videoBytes = part.body;
212
+ if (partContentType.startsWith('video/')) {
213
+ videoMediaType = partContentType;
214
+ }
215
+ } else if (partContentType.startsWith('video/')) {
216
+ videoBytes = part.body;
217
+ videoMediaType = partContentType;
218
+ }
219
+ }
220
+
221
+ if (!jobResult) {
222
+ throw new Error('Prodia multipart response missing job part');
223
+ }
224
+ if (!videoBytes) {
225
+ throw new Error('Prodia multipart response missing output video');
226
+ }
227
+
228
+ return {
229
+ value: { jobResult, videoBytes, videoMediaType },
230
+ responseHeaders,
231
+ };
232
+ };
233
+ }
234
+
235
+ async function resolveVideoFileData(
236
+ file: NonNullable<
237
+ Parameters<Experimental_VideoModelV4['doGenerate']>[0]['image']
238
+ >,
239
+ abortSignal?: AbortSignal,
240
+ ): Promise<{ bytes: Uint8Array; mediaType: string }> {
241
+ if (file.type === 'file') {
242
+ const data =
243
+ typeof file.data === 'string'
244
+ ? convertBase64ToUint8Array(file.data)
245
+ : file.data;
246
+ return { bytes: data, mediaType: file.mediaType };
247
+ }
248
+ // URL type - download via downloadBlob so the user-supplied URL is routed
249
+ // through the SSRF guard (validateDownloadUrl) instead of being fetched
250
+ // directly, preventing requests to private/internal addresses.
251
+ const blob = await downloadBlob(file.url, { abortSignal });
252
+ const arrayBuffer = await blob.arrayBuffer();
253
+ const mediaType = blob.type || 'application/octet-stream';
254
+ return { bytes: new Uint8Array(arrayBuffer), mediaType };
255
+ }
256
+
257
+ function getExtension(mediaType: string): string {
258
+ const map: Record<string, string> = {
259
+ 'image/png': '.png',
260
+ 'image/jpeg': '.jpg',
261
+ 'image/webp': '.webp',
262
+ 'video/mp4': '.mp4',
263
+ 'video/webm': '.webm',
264
+ };
265
+ return map[mediaType] ?? '';
266
+ }
package/dist/index.d.mts DELETED
@@ -1,58 +0,0 @@
1
- import * as _ai_sdk_provider_utils from '@ai-sdk/provider-utils';
2
- import { InferSchema, FetchFunction } from '@ai-sdk/provider-utils';
3
- import { ProviderV4, ImageModelV4 } from '@ai-sdk/provider';
4
-
5
- /**
6
- * Prodia job types for image generation.
7
- */
8
- type ProdiaImageModelId = 'inference.flux-fast.schnell.txt2img.v2' | 'inference.flux.schnell.txt2img.v2' | (string & {});
9
-
10
- declare const prodiaImageModelOptionsSchema: _ai_sdk_provider_utils.LazySchema<{
11
- steps?: number | undefined;
12
- width?: number | undefined;
13
- height?: number | undefined;
14
- stylePreset?: "3d-model" | "analog-film" | "anime" | "cinematic" | "comic-book" | "digital-art" | "enhance" | "fantasy-art" | "isometric" | "line-art" | "low-poly" | "neon-punk" | "origami" | "photographic" | "pixel-art" | "texture" | "craft-clay" | undefined;
15
- loras?: string[] | undefined;
16
- progressive?: boolean | undefined;
17
- }>;
18
- type ProdiaImageModelOptions = InferSchema<typeof prodiaImageModelOptionsSchema>;
19
-
20
- interface ProdiaProviderSettings {
21
- /**
22
- * Prodia API key. Default value is taken from the `PRODIA_TOKEN` environment variable.
23
- */
24
- apiKey?: string;
25
- /**
26
- * Base URL for the API calls. Defaults to `https://inference.prodia.com/v2`.
27
- */
28
- baseURL?: string;
29
- /**
30
- * Custom headers to include in the requests.
31
- */
32
- headers?: Record<string, string>;
33
- /**
34
- * Custom fetch implementation. You can use it as a middleware to intercept
35
- * requests, or to provide a custom fetch implementation for e.g. testing.
36
- */
37
- fetch?: FetchFunction;
38
- }
39
- interface ProdiaProvider extends ProviderV4 {
40
- /**
41
- * Creates a model for image generation.
42
- */
43
- image(modelId: ProdiaImageModelId): ImageModelV4;
44
- /**
45
- * Creates a model for image generation.
46
- */
47
- imageModel(modelId: ProdiaImageModelId): ImageModelV4;
48
- /**
49
- * @deprecated Use `embeddingModel` instead.
50
- */
51
- textEmbeddingModel(modelId: string): never;
52
- }
53
- declare function createProdia(options?: ProdiaProviderSettings): ProdiaProvider;
54
- declare const prodia: ProdiaProvider;
55
-
56
- declare const VERSION: string;
57
-
58
- export { type ProdiaImageModelId, type ProdiaImageModelOptions, type ProdiaImageModelOptions as ProdiaImageProviderOptions, type ProdiaProvider, type ProdiaProviderSettings, VERSION, createProdia, prodia };