@ai-sdk/topaz 2.0.0 → 3.0.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.
package/dist/index.js CHANGED
@@ -1,1348 +1,1154 @@
1
- // src/topaz-provider.ts
2
- import {
3
- NoSuchModelError
4
- } from "@ai-sdk/provider";
5
- import {
6
- loadApiKey,
7
- withoutTrailingSlash,
8
- withUserAgentSuffix as withUserAgentSuffix2
9
- } from "@ai-sdk/provider-utils";
10
-
11
- // src/topaz-image-model.ts
12
- import {
13
- AISDKError,
14
- InvalidArgumentError,
15
- InvalidResponseDataError
16
- } from "@ai-sdk/provider";
17
- import {
18
- combineHeaders,
19
- convertBase64ToUint8Array,
20
- createBinaryResponseHandler,
21
- createJsonResponseHandler,
22
- delay,
23
- mediaTypeToExtension,
24
- parseProviderOptions,
25
- postFormDataToApi,
26
- removeUndefinedEntries,
27
- resolve
28
- } from "@ai-sdk/provider-utils";
29
- import { z as z3 } from "zod/v4";
30
-
31
- // src/topaz-error.ts
32
- import { createJsonErrorResponseHandler } from "@ai-sdk/provider-utils";
1
+ import { AISDKError, APICallError, InvalidArgumentError, InvalidResponseDataError, NoSuchModelError } from "@ai-sdk/provider";
2
+ import { WORKFLOW_DESERIALIZE, WORKFLOW_SERIALIZE, combineHeaders, convertBase64ToUint8Array, createBinaryResponseHandler, createJsonErrorResponseHandler, createJsonResponseHandler, delay, getFromApi, isSameOrigin, lazySchema, loadApiKey, mediaTypeToExtension, parseProviderOptions, postFormDataToApi, postJsonToApi, removeUndefinedEntries, resolve, serializeModelOptions, validateDownloadUrl, withUserAgentSuffix, withoutTrailingSlash, zodSchema } from "@ai-sdk/provider-utils";
33
3
  import { z } from "zod/v4";
34
- var topazErrorDataSchema = z.object({
35
- detail: z.union([
36
- z.string(),
37
- z.array(z.object({ msg: z.string().nullish() }).loose())
38
- ]).nullish(),
39
- message: z.string().nullish(),
40
- error: z.string().nullish(),
41
- errorCode: z.string().nullish(),
42
- errors: z.array(z.object({ msg: z.string().nullish() }).loose()).nullish()
4
+ //#region src/topaz-error.ts
5
+ /**
6
+ * The image API reports errors as `{ "message": ... }` and the video API as
7
+ * `{ "message": ..., "errorCode": ... }`. `detail` (a message or FastAPI's
8
+ * array of validation issues) and `error` are accepted as well because a few
9
+ * endpoints use them instead.
10
+ */
11
+ const topazErrorDataSchema = z.object({
12
+ detail: z.union([z.string(), z.array(z.object({ msg: z.string().nullish() }).loose())]).nullish(),
13
+ message: z.string().nullish(),
14
+ error: z.string().nullish(),
15
+ errorCode: z.string().nullish(),
16
+ errors: z.array(z.object({ msg: z.string().nullish() }).loose()).nullish()
43
17
  });
44
18
  function topazErrorToMessage(data) {
45
- var _a;
46
- const message = baseErrorMessage(data);
47
- const withCode = data.errorCode != null ? `${message} (${data.errorCode})` : message;
48
- const issues = ((_a = data.errors) != null ? _a : []).map((issue) => issue.msg).filter((msg) => msg != null);
49
- return issues.length > 0 ? `${withCode}: ${issues.join("; ")}` : withCode;
19
+ const message = baseErrorMessage(data);
20
+ const withCode = data.errorCode != null ? `${message} (${data.errorCode})` : message;
21
+ const issues = (data.errors ?? []).map((issue) => issue.msg).filter((msg) => msg != null);
22
+ return issues.length > 0 ? `${withCode}: ${issues.join("; ")}` : withCode;
50
23
  }
51
24
  function baseErrorMessage(data) {
52
- var _a, _b;
53
- const { detail } = data;
54
- if (typeof detail === "string") {
55
- return detail;
56
- }
57
- if (Array.isArray(detail)) {
58
- const messages = detail.map((issue) => issue.msg).filter((msg) => msg != null);
59
- if (messages.length > 0) {
60
- return messages.join("; ");
61
- }
62
- }
63
- return (_b = (_a = data.message) != null ? _a : data.error) != null ? _b : "Unknown Topaz API error";
25
+ const { detail } = data;
26
+ if (typeof detail === "string") return detail;
27
+ if (Array.isArray(detail)) {
28
+ const messages = detail.map((issue) => issue.msg).filter((msg) => msg != null);
29
+ if (messages.length > 0) return messages.join("; ");
30
+ }
31
+ return data.message ?? data.error ?? "Unknown Topaz API error";
64
32
  }
65
- var topazFailedResponseHandler = createJsonErrorResponseHandler({
66
- errorSchema: topazErrorDataSchema,
67
- errorToMessage: topazErrorToMessage
33
+ const topazFailedResponseHandler = createJsonErrorResponseHandler({
34
+ errorSchema: topazErrorDataSchema,
35
+ errorToMessage: topazErrorToMessage
68
36
  });
69
-
70
- // src/topaz-get-from-api.ts
71
- import {
72
- fetchWithValidatedEndpoint,
73
- fetchWithValidatedRedirects,
74
- getFromApi,
75
- isSameOrigin
76
- } from "@ai-sdk/provider-utils";
77
- async function topazGetFromApi({
78
- validateUrl,
79
- credentialedOrigin,
80
- trustedOrigin,
81
- ...options
82
- }) {
83
- const validatedFetch = (url, init) => {
84
- var _a;
85
- return fetchWithValidatedRedirects({
86
- url: url.toString(),
87
- headers: init == null ? void 0 : init.headers,
88
- abortSignal: (_a = init == null ? void 0 : init.signal) != null ? _a : void 0,
89
- trustedOrigin,
90
- fetch: (hopUrl, hopInit) => fetchWithValidatedEndpoint({
91
- url: hopUrl.toString(),
92
- init: {
93
- ...hopInit,
94
- headers: credentialedOrigin != null && isSameOrigin(hopUrl.toString(), credentialedOrigin) ? hopInit == null ? void 0 : hopInit.headers : void 0
95
- },
96
- trustedOrigin,
97
- redirect: "manual",
98
- fetch: options.fetch
99
- })
100
- });
101
- };
102
- const requestOptions = {
103
- ...options,
104
- validateUrl: false,
105
- fetch: validateUrl ? validatedFetch : options.fetch
106
- };
107
- return getFromApi(requestOptions);
108
- }
109
-
110
- // src/topaz-image-model-options.ts
111
- import {
112
- lazySchema,
113
- zodSchema
114
- } from "@ai-sdk/provider-utils";
115
- import { z as z2 } from "zod/v4";
116
- var topazImageModelOptionsSchema = lazySchema(
117
- () => zodSchema(
118
- z2.object({
119
- /**
120
- * How aggressively the model enhances the image. Defaults to `high`.
121
- */
122
- enhancementStrength: z2.enum(["low", "medium", "high"]).optional(),
123
- /**
124
- * Whether to add grain to the output. Defaults to `false`.
125
- */
126
- grain: z2.boolean().optional(),
127
- /**
128
- * Grain intensity, 0.0 to 1.0. Defaults to 0.5.
129
- */
130
- grainDensity: z2.number().min(0).max(1).optional(),
131
- /**
132
- * Grain model. Defaults to `silver`.
133
- */
134
- grainModel: z2.enum(["silver", "gaussian", "grey"]).optional(),
135
- /**
136
- * Grain particle size, 1 to 5. Defaults to 1.
137
- */
138
- grainSize: z2.number().min(1).max(5).optional(),
139
- /**
140
- * Grain effect strength, 0.0 to 1.0. Defaults to 0.5.
141
- */
142
- grainStrength: z2.number().min(0).max(1).optional(),
143
- /**
144
- * Width of the input image in pixels. Topaz infers this from the upload when
145
- * omitted.
146
- */
147
- inputWidth: z2.number().int().positive().optional(),
148
- /**
149
- * Height of the input image in pixels. Topaz infers this from the upload
150
- * when omitted.
151
- */
152
- inputHeight: z2.number().int().positive().optional(),
153
- /**
154
- * Width of the output image in pixels, 1 to 32000. Takes precedence over the
155
- * width derived from the `size` call option.
156
- */
157
- outputWidth: z2.number().int().min(1).max(32e3).optional(),
158
- /**
159
- * Height of the output image in pixels, 1 to 32000. Takes precedence over
160
- * the height derived from the `size` call option.
161
- */
162
- outputHeight: z2.number().int().min(1).max(32e3).optional(),
163
- /**
164
- * Output image format. Defaults to the Topaz API default.
165
- */
166
- outputFormat: z2.enum(["jpeg", "jpg", "png", "tiff", "tif"]).optional(),
167
- /**
168
- * Whether to crop the output to fill the requested dimensions. Defaults to
169
- * `false`.
170
- */
171
- cropToFill: z2.boolean().optional(),
172
- /**
173
- * URL to receive job-status webhooks.
174
- */
175
- webhookUrl: z2.string().optional(),
176
- /**
177
- * How often to poll the Topaz status endpoint, in milliseconds. Defaults to
178
- * 2000.
179
- */
180
- pollIntervalMillis: z2.number().int().positive().optional(),
181
- /**
182
- * How long to wait for the job to finish before failing, in milliseconds.
183
- * Defaults to 600000 (10 minutes).
184
- */
185
- pollTimeoutMillis: z2.number().int().positive().optional()
186
- })
187
- )
188
- );
189
-
190
- // src/topaz-image-settings.ts
191
- var topazImageApiModelIds = {
192
- "wonder-3.5": "Wonder 3.5"
193
- };
37
+ //#endregion
38
+ //#region src/topaz-image-model-options.ts
39
+ /**
40
+ * Provider options for Topaz generative image models (Wonder 3.5).
41
+ *
42
+ * The documented request schema fields are sent in snake_case
43
+ * (`output_width`, `output_format`, ...); model-specific settings are sent in
44
+ * camelCase, matching the Topaz model reference.
45
+ *
46
+ * @see https://developer.topazlabs.com/image-models/wonder/wonder-3.5-new
47
+ */
48
+ const topazImageModelOptionsSchema = lazySchema(() => zodSchema(z.object({
49
+ /**
50
+ * How aggressively the model enhances the image. Defaults to `high`.
51
+ */
52
+ enhancementStrength: z.enum([
53
+ "low",
54
+ "medium",
55
+ "high"
56
+ ]).optional(),
57
+ /**
58
+ * Whether to add grain to the output. Defaults to `false`.
59
+ */
60
+ grain: z.boolean().optional(),
61
+ /**
62
+ * Grain intensity, 0.0 to 1.0. Defaults to 0.5.
63
+ */
64
+ grainDensity: z.number().min(0).max(1).optional(),
65
+ /**
66
+ * Grain model. Defaults to `silver`.
67
+ */
68
+ grainModel: z.enum([
69
+ "silver",
70
+ "gaussian",
71
+ "grey"
72
+ ]).optional(),
73
+ /**
74
+ * Grain particle size, 1 to 5. Defaults to 1.
75
+ */
76
+ grainSize: z.number().min(1).max(5).optional(),
77
+ /**
78
+ * Grain effect strength, 0.0 to 1.0. Defaults to 0.5.
79
+ */
80
+ grainStrength: z.number().min(0).max(1).optional(),
81
+ /**
82
+ * Width of the input image in pixels. Topaz infers this from the upload when
83
+ * omitted.
84
+ */
85
+ inputWidth: z.number().int().positive().optional(),
86
+ /**
87
+ * Height of the input image in pixels. Topaz infers this from the upload
88
+ * when omitted.
89
+ */
90
+ inputHeight: z.number().int().positive().optional(),
91
+ /**
92
+ * Width of the output image in pixels, 1 to 32000. Takes precedence over the
93
+ * width derived from the `size` call option.
94
+ */
95
+ outputWidth: z.number().int().min(1).max(32e3).optional(),
96
+ /**
97
+ * Height of the output image in pixels, 1 to 32000. Takes precedence over
98
+ * the height derived from the `size` call option.
99
+ */
100
+ outputHeight: z.number().int().min(1).max(32e3).optional(),
101
+ /**
102
+ * Output image format. Defaults to the Topaz API default.
103
+ */
104
+ outputFormat: z.enum([
105
+ "jpeg",
106
+ "jpg",
107
+ "png",
108
+ "tiff",
109
+ "tif"
110
+ ]).optional(),
111
+ /**
112
+ * Whether to crop the output to fill the requested dimensions. Defaults to
113
+ * `false`.
114
+ */
115
+ cropToFill: z.boolean().optional(),
116
+ /**
117
+ * URL to receive job-status webhooks.
118
+ */
119
+ webhookUrl: z.string().optional(),
120
+ /**
121
+ * How often to poll the Topaz status endpoint, in milliseconds. Defaults to
122
+ * 2000.
123
+ */
124
+ pollIntervalMillis: z.number().int().positive().optional(),
125
+ /**
126
+ * How long to wait for the job to finish before failing, in milliseconds.
127
+ * Defaults to 600000 (10 minutes).
128
+ */
129
+ pollTimeoutMillis: z.number().int().positive().optional()
130
+ })));
131
+ //#endregion
132
+ //#region src/topaz-image-settings.ts
133
+ /**
134
+ * Maps AI SDK image model ids onto Topaz API model names.
135
+ *
136
+ * @see https://developer.topazlabs.com/image-models/wonder/wonder-3.5-new
137
+ */
138
+ const topazImageApiModelIds = { "wonder-3.5": "Wonder 3.5" };
194
139
  function resolveTopazImageApiModelId(modelId) {
195
- var _a;
196
- return (_a = topazImageApiModelIds[modelId]) != null ? _a : modelId;
140
+ return topazImageApiModelIds[modelId] ?? modelId;
197
141
  }
198
-
199
- // src/topaz-image-model.ts
200
- var DEFAULT_POLL_INTERVAL_MILLIS = 2e3;
201
- var DEFAULT_POLL_TIMEOUT_MILLIS = 6e5;
202
- var TopazImageModel = class {
203
- constructor(modelId, config) {
204
- this.modelId = modelId;
205
- this.config = config;
206
- this.specificationVersion = "v3";
207
- this.maxImagesPerCall = 1;
208
- this.supportsFileInputs = true;
209
- this.supportsMaskInputs = false;
210
- }
211
- get provider() {
212
- return this.config.provider;
213
- }
214
- async doGenerate(options) {
215
- var _a, _b, _c, _d, _e, _f;
216
- const currentDate = (_c = (_b = (_a = this.config._internal) == null ? void 0 : _a.currentDate) == null ? void 0 : _b.call(_a)) != null ? _c : /* @__PURE__ */ new Date();
217
- const warnings = [];
218
- const topazOptions = await parseProviderOptions({
219
- provider: "topaz",
220
- providerOptions: options.providerOptions,
221
- schema: topazImageModelOptionsSchema
222
- });
223
- const headers = combineHeaders(
224
- await resolve(this.config.headers),
225
- options.headers
226
- );
227
- this.addUnsupportedWarnings(options, warnings);
228
- const formData = await this.buildFormData(options, topazOptions, warnings);
229
- const { value: submitResponse } = await postFormDataToApi({
230
- url: `${this.config.baseURL}/image/v1/enhance-gen/async`,
231
- headers,
232
- formData,
233
- successfulResponseHandler: createJsonResponseHandler(
234
- topazImageSubmitResponseSchema
235
- ),
236
- failedResponseHandler: topazFailedResponseHandler,
237
- abortSignal: options.abortSignal,
238
- fetch: this.config.fetch
239
- });
240
- const processId = submitResponse.process_id;
241
- if (processId == null) {
242
- throw new InvalidResponseDataError({
243
- data: submitResponse,
244
- message: "Topaz did not return a process_id for the enhance request."
245
- });
246
- }
247
- let status;
248
- try {
249
- status = await this.waitForCompletion({
250
- processId,
251
- headers,
252
- abortSignal: options.abortSignal,
253
- pollIntervalMillis: (_d = topazOptions == null ? void 0 : topazOptions.pollIntervalMillis) != null ? _d : DEFAULT_POLL_INTERVAL_MILLIS,
254
- pollTimeoutMillis: (_e = topazOptions == null ? void 0 : topazOptions.pollTimeoutMillis) != null ? _e : DEFAULT_POLL_TIMEOUT_MILLIS
255
- });
256
- } catch (error) {
257
- if ((_f = options.abortSignal) == null ? void 0 : _f.aborted) {
258
- await this.cancelQuietly(processId, headers);
259
- }
260
- throw error;
261
- }
262
- const { image, responseHeaders } = await this.download({
263
- processId,
264
- headers,
265
- abortSignal: options.abortSignal
266
- });
267
- return {
268
- images: [image],
269
- warnings,
270
- response: {
271
- timestamp: currentDate,
272
- modelId: this.modelId,
273
- headers: responseHeaders
274
- },
275
- providerMetadata: {
276
- topaz: {
277
- images: [
278
- {
279
- processId,
280
- // Topaz bills exactly the credits reported on the completed job.
281
- ...status.credits != null ? { credits: status.credits } : {},
282
- ...status.output_width != null ? { width: status.output_width } : {},
283
- ...status.output_height != null ? { height: status.output_height } : {},
284
- ...status.output_format != null ? { format: status.output_format } : {}
285
- }
286
- ]
287
- }
288
- }
289
- };
290
- }
291
- addUnsupportedWarnings(options, warnings) {
292
- if (options.prompt != null) {
293
- warnings.push({
294
- type: "unsupported",
295
- feature: "prompt",
296
- details: "Topaz image models enhance an existing image and do not take a text prompt. The prompt was ignored."
297
- });
298
- }
299
- if (options.aspectRatio != null) {
300
- warnings.push({
301
- type: "unsupported",
302
- feature: "aspectRatio",
303
- details: "Topaz image models do not support aspectRatio. Use `size`, or the `outputWidth` / `outputHeight` provider options, to set output dimensions."
304
- });
305
- }
306
- if (options.seed != null) {
307
- warnings.push({
308
- type: "unsupported",
309
- feature: "seed",
310
- details: "Topaz image models do not support seed."
311
- });
312
- }
313
- if (options.mask != null) {
314
- warnings.push({
315
- type: "unsupported",
316
- feature: "mask",
317
- details: "Topaz image models do not support masks. The mask was ignored."
318
- });
319
- }
320
- if (options.n > 1) {
321
- warnings.push({
322
- type: "unsupported",
323
- feature: "n",
324
- details: "Topaz image models enhance one image per call. Only 1 image will be returned."
325
- });
326
- }
327
- }
328
- async buildFormData(options, topazOptions, warnings) {
329
- var _a, _b, _c, _d;
330
- const file = (_a = options.files) == null ? void 0 : _a[0];
331
- if (file == null) {
332
- throw new InvalidArgumentError({
333
- argument: "files",
334
- message: "Topaz image models enhance an existing image. Pass the input image via `prompt.images`."
335
- });
336
- }
337
- if (options.files != null && options.files.length > 1) {
338
- warnings.push({
339
- type: "unsupported",
340
- feature: "files",
341
- details: "Topaz image models enhance one image per call. Only the first file was used."
342
- });
343
- }
344
- const formData = new FormData();
345
- formData.append("model", resolveTopazImageApiModelId(this.modelId));
346
- if (file.type === "url") {
347
- formData.append("source_url", file.url);
348
- } else {
349
- const bytes = typeof file.data === "string" ? convertBase64ToUint8Array(file.data) : file.data;
350
- const extension = (_b = mediaTypeToExtension(file.mediaType)) != null ? _b : "png";
351
- formData.append(
352
- "image",
353
- new File([bytes], `image.${extension}`, {
354
- type: file.mediaType
355
- })
356
- );
357
- }
358
- const { width, height } = parseSize(options.size);
359
- appendIfDefined(
360
- formData,
361
- "output_width",
362
- (_c = topazOptions == null ? void 0 : topazOptions.outputWidth) != null ? _c : width
363
- );
364
- appendIfDefined(
365
- formData,
366
- "output_height",
367
- (_d = topazOptions == null ? void 0 : topazOptions.outputHeight) != null ? _d : height
368
- );
369
- appendIfDefined(formData, "output_format", topazOptions == null ? void 0 : topazOptions.outputFormat);
370
- appendIfDefined(formData, "crop_to_fill", topazOptions == null ? void 0 : topazOptions.cropToFill);
371
- appendIfDefined(formData, "webhook_url", topazOptions == null ? void 0 : topazOptions.webhookUrl);
372
- appendIfDefined(
373
- formData,
374
- "enhancementStrength",
375
- topazOptions == null ? void 0 : topazOptions.enhancementStrength
376
- );
377
- appendIfDefined(formData, "grain", topazOptions == null ? void 0 : topazOptions.grain);
378
- appendIfDefined(formData, "grainDensity", topazOptions == null ? void 0 : topazOptions.grainDensity);
379
- appendIfDefined(formData, "grainModel", topazOptions == null ? void 0 : topazOptions.grainModel);
380
- appendIfDefined(formData, "grainSize", topazOptions == null ? void 0 : topazOptions.grainSize);
381
- appendIfDefined(formData, "grainStrength", topazOptions == null ? void 0 : topazOptions.grainStrength);
382
- appendIfDefined(formData, "inputWidth", topazOptions == null ? void 0 : topazOptions.inputWidth);
383
- appendIfDefined(formData, "inputHeight", topazOptions == null ? void 0 : topazOptions.inputHeight);
384
- return formData;
385
- }
386
- async waitForCompletion({
387
- processId,
388
- headers,
389
- abortSignal,
390
- pollIntervalMillis,
391
- pollTimeoutMillis
392
- }) {
393
- var _a;
394
- const deadline = Date.now() + pollTimeoutMillis;
395
- while (true) {
396
- const { value: status } = await topazGetFromApi({
397
- url: `${this.config.baseURL}/image/v1/status/${processId}`,
398
- // Built from the configured baseURL, not from response data.
399
- validateUrl: false,
400
- headers,
401
- successfulResponseHandler: createJsonResponseHandler(
402
- topazImageStatusResponseSchema
403
- ),
404
- failedResponseHandler: topazFailedResponseHandler,
405
- abortSignal,
406
- fetch: this.config.fetch
407
- });
408
- if (status.status === "Completed") {
409
- return status;
410
- }
411
- if (status.status === "Failed" || status.status === "Cancelled") {
412
- throw new AISDKError({
413
- name: "TOPAZ_IMAGE_ENHANCEMENT_FAILED",
414
- message: `Topaz image enhancement ${status.status.toLowerCase()} for process ${processId}.`
415
- });
416
- }
417
- if (Date.now() + pollIntervalMillis > deadline) {
418
- await this.cancelQuietly(processId, headers != null ? headers : {});
419
- throw new AISDKError({
420
- name: "TOPAZ_IMAGE_ENHANCEMENT_TIMEOUT",
421
- message: `Topaz image enhancement did not finish within ${pollTimeoutMillis}ms (process ${processId}, last status ${(_a = status.status) != null ? _a : "unknown"}). Increase the \`pollTimeoutMillis\` provider option if the job needs longer.`
422
- });
423
- }
424
- await delay(pollIntervalMillis, { abortSignal });
425
- }
426
- }
427
- async cancelQuietly(processId, headers) {
428
- var _a;
429
- const fetchImpl = (_a = this.config.fetch) != null ? _a : globalThis.fetch;
430
- try {
431
- await fetchImpl(`${this.config.baseURL}/image/v1/cancel/${processId}`, {
432
- method: "DELETE",
433
- headers: removeUndefinedEntries(headers)
434
- });
435
- } catch (e) {
436
- }
437
- }
438
- async download({
439
- processId,
440
- headers,
441
- abortSignal
442
- }) {
443
- const { value: downloadResponse } = await topazGetFromApi({
444
- url: `${this.config.baseURL}/image/v1/download/${processId}`,
445
- // Built from the configured baseURL, not from response data.
446
- validateUrl: false,
447
- headers,
448
- successfulResponseHandler: createJsonResponseHandler(
449
- topazImageDownloadResponseSchema
450
- ),
451
- failedResponseHandler: topazFailedResponseHandler,
452
- abortSignal,
453
- fetch: this.config.fetch
454
- });
455
- const downloadUrl = downloadResponse.download_url;
456
- if (downloadUrl == null) {
457
- throw new InvalidResponseDataError({
458
- data: downloadResponse,
459
- message: `Topaz did not return a download URL for process ${processId}.`
460
- });
461
- }
462
- const { value: image, responseHeaders } = await topazGetFromApi({
463
- url: downloadUrl,
464
- // Comes from the Topaz response body and normally points at object
465
- // storage, so it is validated and the API key is withheld off-origin.
466
- validateUrl: true,
467
- credentialedOrigin: this.config.baseURL,
468
- trustedOrigin: this.config.baseURL,
469
- headers,
470
- successfulResponseHandler: createBinaryResponseHandler(),
471
- failedResponseHandler: topazFailedResponseHandler,
472
- abortSignal,
473
- fetch: this.config.fetch
474
- });
475
- return { image, responseHeaders };
476
- }
142
+ //#endregion
143
+ //#region src/topaz-image-model.ts
144
+ const DEFAULT_POLL_INTERVAL_MILLIS = 2e3;
145
+ const DEFAULT_POLL_TIMEOUT_MILLIS = 6e5;
146
+ /**
147
+ * Topaz enhances an image that the caller supplies, so `files` is required and
148
+ * exactly one image is processed per call.
149
+ */
150
+ var TopazImageModel = class TopazImageModel {
151
+ get provider() {
152
+ return this.config.provider;
153
+ }
154
+ static [WORKFLOW_SERIALIZE](model) {
155
+ return serializeModelOptions({
156
+ modelId: model.modelId,
157
+ config: model.config
158
+ });
159
+ }
160
+ static [WORKFLOW_DESERIALIZE](options) {
161
+ return new TopazImageModel(options.modelId, options.config);
162
+ }
163
+ constructor(modelId, config) {
164
+ this.modelId = modelId;
165
+ this.config = config;
166
+ this.specificationVersion = "v4";
167
+ this.maxImagesPerCall = 1;
168
+ this.supportsFileInputs = true;
169
+ this.supportsMaskInputs = false;
170
+ }
171
+ async doGenerate(options) {
172
+ const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
173
+ const warnings = [];
174
+ const topazOptions = await parseProviderOptions({
175
+ provider: "topaz",
176
+ providerOptions: options.providerOptions,
177
+ schema: topazImageModelOptionsSchema
178
+ });
179
+ const headers = combineHeaders(await resolve(this.config.headers), options.headers);
180
+ this.addUnsupportedWarnings(options, warnings);
181
+ const formData = await this.buildFormData(options, topazOptions, warnings);
182
+ const { value: submitResponse } = await postFormDataToApi({
183
+ url: `${this.config.baseURL}/image/v1/enhance-gen/async`,
184
+ headers,
185
+ formData,
186
+ successfulResponseHandler: createJsonResponseHandler(topazImageSubmitResponseSchema),
187
+ failedResponseHandler: topazFailedResponseHandler,
188
+ abortSignal: options.abortSignal,
189
+ fetch: this.config.fetch
190
+ });
191
+ const processId = submitResponse.process_id;
192
+ if (processId == null) throw new InvalidResponseDataError({
193
+ data: submitResponse,
194
+ message: "Topaz did not return a process_id for the enhance request."
195
+ });
196
+ let status;
197
+ try {
198
+ status = await this.waitForCompletion({
199
+ processId,
200
+ headers,
201
+ abortSignal: options.abortSignal,
202
+ pollIntervalMillis: topazOptions?.pollIntervalMillis ?? DEFAULT_POLL_INTERVAL_MILLIS,
203
+ pollTimeoutMillis: topazOptions?.pollTimeoutMillis ?? DEFAULT_POLL_TIMEOUT_MILLIS
204
+ });
205
+ } catch (error) {
206
+ if (options.abortSignal?.aborted) await this.cancelQuietly(processId, headers);
207
+ throw error;
208
+ }
209
+ const { image, responseHeaders } = await this.download({
210
+ processId,
211
+ headers,
212
+ abortSignal: options.abortSignal
213
+ });
214
+ return {
215
+ images: [image],
216
+ warnings,
217
+ response: {
218
+ timestamp: currentDate,
219
+ modelId: this.modelId,
220
+ headers: responseHeaders
221
+ },
222
+ providerMetadata: { topaz: { images: [{
223
+ processId,
224
+ ...status.credits != null ? { credits: status.credits } : {},
225
+ ...status.output_width != null ? { width: status.output_width } : {},
226
+ ...status.output_height != null ? { height: status.output_height } : {},
227
+ ...status.output_format != null ? { format: status.output_format } : {}
228
+ }] } }
229
+ };
230
+ }
231
+ addUnsupportedWarnings(options, warnings) {
232
+ if (options.prompt != null) warnings.push({
233
+ type: "unsupported",
234
+ feature: "prompt",
235
+ details: "Topaz image models enhance an existing image and do not take a text prompt. The prompt was ignored."
236
+ });
237
+ if (options.aspectRatio != null) warnings.push({
238
+ type: "unsupported",
239
+ feature: "aspectRatio",
240
+ details: "Topaz image models do not support aspectRatio. Use `size`, or the `outputWidth` / `outputHeight` provider options, to set output dimensions."
241
+ });
242
+ if (options.seed != null) warnings.push({
243
+ type: "unsupported",
244
+ feature: "seed",
245
+ details: "Topaz image models do not support seed."
246
+ });
247
+ if (options.mask != null) warnings.push({
248
+ type: "unsupported",
249
+ feature: "mask",
250
+ details: "Topaz image models do not support masks. The mask was ignored."
251
+ });
252
+ if (options.n > 1) warnings.push({
253
+ type: "unsupported",
254
+ feature: "n",
255
+ details: "Topaz image models enhance one image per call. Only 1 image will be returned."
256
+ });
257
+ }
258
+ async buildFormData(options, topazOptions, warnings) {
259
+ const file = options.files?.[0];
260
+ if (file == null) throw new InvalidArgumentError({
261
+ argument: "files",
262
+ message: "Topaz image models enhance an existing image. Pass the input image via `prompt.images`."
263
+ });
264
+ if (options.files != null && options.files.length > 1) warnings.push({
265
+ type: "unsupported",
266
+ feature: "files",
267
+ details: "Topaz image models enhance one image per call. Only the first file was used."
268
+ });
269
+ const formData = new FormData();
270
+ formData.append("model", resolveTopazImageApiModelId(this.modelId));
271
+ if (file.type === "url") formData.append("source_url", file.url);
272
+ else {
273
+ const bytes = typeof file.data === "string" ? convertBase64ToUint8Array(file.data) : file.data;
274
+ const extension = mediaTypeToExtension(file.mediaType) ?? "png";
275
+ formData.append("image", new File([bytes], `image.${extension}`, { type: file.mediaType }));
276
+ }
277
+ const { width, height } = parseSize(options.size);
278
+ appendIfDefined(formData, "output_width", topazOptions?.outputWidth ?? width);
279
+ appendIfDefined(formData, "output_height", topazOptions?.outputHeight ?? height);
280
+ appendIfDefined(formData, "output_format", topazOptions?.outputFormat);
281
+ appendIfDefined(formData, "crop_to_fill", topazOptions?.cropToFill);
282
+ appendIfDefined(formData, "webhook_url", topazOptions?.webhookUrl);
283
+ appendIfDefined(formData, "enhancementStrength", topazOptions?.enhancementStrength);
284
+ appendIfDefined(formData, "grain", topazOptions?.grain);
285
+ appendIfDefined(formData, "grainDensity", topazOptions?.grainDensity);
286
+ appendIfDefined(formData, "grainModel", topazOptions?.grainModel);
287
+ appendIfDefined(formData, "grainSize", topazOptions?.grainSize);
288
+ appendIfDefined(formData, "grainStrength", topazOptions?.grainStrength);
289
+ appendIfDefined(formData, "inputWidth", topazOptions?.inputWidth);
290
+ appendIfDefined(formData, "inputHeight", topazOptions?.inputHeight);
291
+ return formData;
292
+ }
293
+ async waitForCompletion({ processId, headers, abortSignal, pollIntervalMillis, pollTimeoutMillis }) {
294
+ const deadline = Date.now() + pollTimeoutMillis;
295
+ while (true) {
296
+ const { value: status } = await getFromApi({
297
+ url: `${this.config.baseURL}/image/v1/status/${processId}`,
298
+ validateUrl: false,
299
+ headers,
300
+ successfulResponseHandler: createJsonResponseHandler(topazImageStatusResponseSchema),
301
+ failedResponseHandler: topazFailedResponseHandler,
302
+ abortSignal,
303
+ fetch: this.config.fetch
304
+ });
305
+ if (status.status === "Completed") return status;
306
+ if (status.status === "Failed" || status.status === "Cancelled") throw new AISDKError({
307
+ name: "TOPAZ_IMAGE_ENHANCEMENT_FAILED",
308
+ message: `Topaz image enhancement ${status.status.toLowerCase()} for process ${processId}.`
309
+ });
310
+ if (Date.now() + pollIntervalMillis > deadline) {
311
+ await this.cancelQuietly(processId, headers ?? {});
312
+ throw new AISDKError({
313
+ name: "TOPAZ_IMAGE_ENHANCEMENT_TIMEOUT",
314
+ message: `Topaz image enhancement did not finish within ${pollTimeoutMillis}ms (process ${processId}, last status ${status.status ?? "unknown"}). Increase the \`pollTimeoutMillis\` provider option if the job needs longer.`
315
+ });
316
+ }
317
+ await delay(pollIntervalMillis, { abortSignal });
318
+ }
319
+ }
320
+ async cancelQuietly(processId, headers) {
321
+ const fetchImpl = this.config.fetch ?? globalThis.fetch;
322
+ try {
323
+ await fetchImpl(`${this.config.baseURL}/image/v1/cancel/${processId}`, {
324
+ method: "DELETE",
325
+ headers: removeUndefinedEntries(headers)
326
+ });
327
+ } catch {}
328
+ }
329
+ async download({ processId, headers, abortSignal }) {
330
+ const { value: downloadResponse } = await getFromApi({
331
+ url: `${this.config.baseURL}/image/v1/download/${processId}`,
332
+ validateUrl: false,
333
+ headers,
334
+ successfulResponseHandler: createJsonResponseHandler(topazImageDownloadResponseSchema),
335
+ failedResponseHandler: topazFailedResponseHandler,
336
+ abortSignal,
337
+ fetch: this.config.fetch
338
+ });
339
+ const downloadUrl = downloadResponse.download_url;
340
+ if (downloadUrl == null) throw new InvalidResponseDataError({
341
+ data: downloadResponse,
342
+ message: `Topaz did not return a download URL for process ${processId}.`
343
+ });
344
+ const { value: image, responseHeaders } = await getFromApi({
345
+ url: downloadUrl,
346
+ validateUrl: true,
347
+ credentialedOrigin: this.config.baseURL,
348
+ trustedOrigin: this.config.baseURL,
349
+ headers,
350
+ successfulResponseHandler: createBinaryResponseHandler(),
351
+ failedResponseHandler: topazFailedResponseHandler,
352
+ abortSignal,
353
+ fetch: this.config.fetch
354
+ });
355
+ return {
356
+ image,
357
+ responseHeaders
358
+ };
359
+ }
477
360
  };
478
361
  function parseSize(size) {
479
- if (size == null) {
480
- return { width: void 0, height: void 0 };
481
- }
482
- const [width, height] = size.split("x").map(Number);
483
- return { width, height };
362
+ if (size == null) return {
363
+ width: void 0,
364
+ height: void 0
365
+ };
366
+ const [width, height] = size.split("x").map(Number);
367
+ return {
368
+ width,
369
+ height
370
+ };
484
371
  }
485
372
  function appendIfDefined(formData, key, value) {
486
- if (value !== void 0) {
487
- formData.append(key, String(value));
488
- }
373
+ if (value !== void 0) formData.append(key, String(value));
489
374
  }
490
- var topazImageSubmitResponseSchema = z3.object({
491
- process_id: z3.string().nullish()
492
- });
493
- var topazImageStatusResponseSchema = z3.object({
494
- // Documented values are Pending, Processing, Completed, Cancelled and Failed.
495
- // Unknown values keep polling rather than failing the parse.
496
- status: z3.string().nullish(),
497
- credits: z3.number().nullish(),
498
- output_width: z3.number().nullish(),
499
- output_height: z3.number().nullish(),
500
- output_format: z3.string().nullish()
375
+ const topazImageSubmitResponseSchema = z.object({ process_id: z.string().nullish() });
376
+ const topazImageStatusResponseSchema = z.object({
377
+ status: z.string().nullish(),
378
+ credits: z.number().nullish(),
379
+ output_width: z.number().nullish(),
380
+ output_height: z.number().nullish(),
381
+ output_format: z.string().nullish()
501
382
  });
502
- var topazImageDownloadResponseSchema = z3.object({
503
- download_url: z3.string().nullish()
504
- });
505
-
506
- // src/topaz-video-model.ts
507
- import {
508
- AISDKError as AISDKError2,
509
- APICallError,
510
- InvalidArgumentError as InvalidArgumentError2,
511
- InvalidResponseDataError as InvalidResponseDataError2
512
- } from "@ai-sdk/provider";
513
- import {
514
- combineHeaders as combineHeaders2,
515
- convertBase64ToUint8Array as convertBase64ToUint8Array2,
516
- createJsonResponseHandler as createJsonResponseHandler2,
517
- delay as delay2,
518
- isSameOrigin as isSameOrigin2,
519
- parseProviderOptions as parseProviderOptions2,
520
- postJsonToApi,
521
- removeUndefinedEntries as removeUndefinedEntries2,
522
- resolve as resolve2,
523
- validateDownloadUrl,
524
- withUserAgentSuffix
525
- } from "@ai-sdk/provider-utils";
526
- import { z as z5 } from "zod/v4";
527
-
528
- // src/topaz-video-model-options.ts
529
- import {
530
- lazySchema as lazySchema2,
531
- zodSchema as zodSchema2
532
- } from "@ai-sdk/provider-utils";
533
- import { z as z4 } from "zod/v4";
534
- var topazSourceContainers = [
535
- "3gp",
536
- "avi",
537
- "dv",
538
- "flv",
539
- "m1v",
540
- "m2t",
541
- "m2ts",
542
- "m2v",
543
- "m4v",
544
- "mkv",
545
- "mov",
546
- "mp4",
547
- "mpeg",
548
- "mpg",
549
- "mts",
550
- "mxf",
551
- "ser",
552
- "ts",
553
- "vob",
554
- "webm",
555
- "wmv"
383
+ const topazImageDownloadResponseSchema = z.object({ download_url: z.string().nullish() });
384
+ //#endregion
385
+ //#region src/topaz-video-model-options.ts
386
+ /**
387
+ * Containers Topaz accepts for the input video.
388
+ */
389
+ const topazSourceContainers = [
390
+ "3gp",
391
+ "avi",
392
+ "dv",
393
+ "flv",
394
+ "m1v",
395
+ "m2t",
396
+ "m2ts",
397
+ "m2v",
398
+ "m4v",
399
+ "mkv",
400
+ "mov",
401
+ "mp4",
402
+ "mpeg",
403
+ "mpg",
404
+ "mts",
405
+ "mxf",
406
+ "ser",
407
+ "ts",
408
+ "vob",
409
+ "webm",
410
+ "wmv"
556
411
  ];
557
- var topazOutputContainers = [
558
- "mp4",
559
- "mov",
560
- "mkv",
561
- "avi",
562
- "webm"
412
+ /**
413
+ * Containers Topaz can produce for the enhanced video.
414
+ */
415
+ const topazOutputContainers = [
416
+ "mp4",
417
+ "mov",
418
+ "mkv",
419
+ "avi",
420
+ "webm"
563
421
  ];
564
- var topazVideoSourceSchema = z4.object({
565
- /**
566
- * Width of the input video in pixels.
567
- */
568
- width: z4.number().int().positive().optional(),
569
- /**
570
- * Height of the input video in pixels.
571
- */
572
- height: z4.number().int().positive().optional(),
573
- /**
574
- * Duration of the input video in seconds.
575
- */
576
- duration: z4.number().positive().optional(),
577
- /**
578
- * Frame rate of the input video.
579
- */
580
- frameRate: z4.number().positive().optional(),
581
- /**
582
- * Total number of frames in the input video. Derived from
583
- * `duration * frameRate` when omitted, which is only correct for
584
- * constant-frame-rate input, so set it explicitly for variable-frame-rate
585
- * sources.
586
- */
587
- frameCount: z4.number().int().positive().optional(),
588
- /**
589
- * Container of the input video. Detected from the input file's media type
590
- * or URL extension when omitted.
591
- */
592
- container: z4.enum(topazSourceContainers).optional()
422
+ /**
423
+ * Metadata about the input video.
424
+ *
425
+ * Starlight models require `width`, `height`, `duration` and `frameRate`
426
+ * (Topaz prices them from these values), and for other models they let Topaz
427
+ * estimate the cost before it has the video. The AI SDK does not inspect
428
+ * media files, so the values come from the caller. Setting any of them
429
+ * requires all four.
430
+ */
431
+ const topazVideoSourceSchema = z.object({
432
+ /**
433
+ * Width of the input video in pixels.
434
+ */
435
+ width: z.number().int().positive().optional(),
436
+ /**
437
+ * Height of the input video in pixels.
438
+ */
439
+ height: z.number().int().positive().optional(),
440
+ /**
441
+ * Duration of the input video in seconds.
442
+ */
443
+ duration: z.number().positive().optional(),
444
+ /**
445
+ * Frame rate of the input video.
446
+ */
447
+ frameRate: z.number().positive().optional(),
448
+ /**
449
+ * Total number of frames in the input video. Derived from
450
+ * `duration * frameRate` when omitted, which is only correct for
451
+ * constant-frame-rate input, so set it explicitly for variable-frame-rate
452
+ * sources.
453
+ */
454
+ frameCount: z.number().int().positive().optional(),
455
+ /**
456
+ * Container of the input video. Detected from the input file's media type
457
+ * or URL extension when omitted.
458
+ */
459
+ container: z.enum(topazSourceContainers).optional()
593
460
  });
594
- var topazVideoOutputSchema = z4.object({
595
- /**
596
- * Width of the output video in pixels. Takes precedence over the
597
- * `resolution` call option.
598
- */
599
- width: z4.number().int().positive().optional(),
600
- /**
601
- * Height of the output video in pixels. Takes precedence over the
602
- * `resolution` call option.
603
- */
604
- height: z4.number().int().positive().optional(),
605
- /**
606
- * Frame rate of the output video. Takes precedence over the `fps` call
607
- * option. Topaz only changes the frame rate when a frame-interpolation
608
- * filter is present.
609
- */
610
- frameRate: z4.number().positive().optional(),
611
- /**
612
- * Audio codec of the output video. Defaults to `AAC`.
613
- */
614
- audioCodec: z4.enum(["AAC", "AC3", "PCM"]).optional(),
615
- /**
616
- * Audio bitrate, e.g. `192k`. Topaz uses the codec default when omitted.
617
- */
618
- audioBitrate: z4.string().optional(),
619
- /**
620
- * How to handle the input audio track. Defaults to `Copy`.
621
- */
622
- audioTransfer: z4.enum(["Copy", "Convert", "None"]).optional(),
623
- /**
624
- * Video encoder. Topaz defaults to `H265`. `ProRes` forces a `mov`
625
- * container, `AV1` and `VP9` force `mp4`.
626
- */
627
- videoEncoder: z4.enum(["AV1", "H264", "H265", "ProRes", "VP9"]).optional(),
628
- /**
629
- * Encoder profile, e.g. `Main10` for H265 or `422 HQ` for ProRes. Topaz uses
630
- * the encoder's default profile when omitted.
631
- */
632
- videoProfile: z4.string().optional(),
633
- /**
634
- * Constant bitrate, e.g. `20m`. Required for `VP9`. Mutually exclusive with
635
- * `dynamicCompressionLevel`.
636
- */
637
- videoBitrate: z4.string().optional(),
638
- /**
639
- * Automatic constant-quality compression level. Topaz defaults to `High`
640
- * unless `videoBitrate` is set.
641
- */
642
- dynamicCompressionLevel: z4.enum(["Low", "Mid", "High"]).optional(),
643
- /**
644
- * Center-crop to fit the output dimensions.
645
- */
646
- cropToFit: z4.boolean().optional(),
647
- /**
648
- * Container of the output video. Defaults to the input container when Topaz
649
- * can produce it, otherwise `mp4`.
650
- */
651
- container: z4.enum(topazOutputContainers).optional()
461
+ /**
462
+ * Output settings for the enhanced video.
463
+ */
464
+ const topazVideoOutputSchema = z.object({
465
+ /**
466
+ * Width of the output video in pixels. Takes precedence over the
467
+ * `resolution` call option.
468
+ */
469
+ width: z.number().int().positive().optional(),
470
+ /**
471
+ * Height of the output video in pixels. Takes precedence over the
472
+ * `resolution` call option.
473
+ */
474
+ height: z.number().int().positive().optional(),
475
+ /**
476
+ * Frame rate of the output video. Takes precedence over the `fps` call
477
+ * option. Topaz only changes the frame rate when a frame-interpolation
478
+ * filter is present.
479
+ */
480
+ frameRate: z.number().positive().optional(),
481
+ /**
482
+ * Audio codec of the output video. Defaults to `AAC`.
483
+ */
484
+ audioCodec: z.enum([
485
+ "AAC",
486
+ "AC3",
487
+ "PCM"
488
+ ]).optional(),
489
+ /**
490
+ * Audio bitrate, e.g. `192k`. Topaz uses the codec default when omitted.
491
+ */
492
+ audioBitrate: z.string().optional(),
493
+ /**
494
+ * How to handle the input audio track. Defaults to `Copy`.
495
+ */
496
+ audioTransfer: z.enum([
497
+ "Copy",
498
+ "Convert",
499
+ "None"
500
+ ]).optional(),
501
+ /**
502
+ * Video encoder. Topaz defaults to `H265`. `ProRes` forces a `mov`
503
+ * container, `AV1` and `VP9` force `mp4`.
504
+ */
505
+ videoEncoder: z.enum([
506
+ "AV1",
507
+ "H264",
508
+ "H265",
509
+ "ProRes",
510
+ "VP9"
511
+ ]).optional(),
512
+ /**
513
+ * Encoder profile, e.g. `Main10` for H265 or `422 HQ` for ProRes. Topaz uses
514
+ * the encoder's default profile when omitted.
515
+ */
516
+ videoProfile: z.string().optional(),
517
+ /**
518
+ * Constant bitrate, e.g. `20m`. Required for `VP9`. Mutually exclusive with
519
+ * `dynamicCompressionLevel`.
520
+ */
521
+ videoBitrate: z.string().optional(),
522
+ /**
523
+ * Automatic constant-quality compression level. Topaz defaults to `High`
524
+ * unless `videoBitrate` is set.
525
+ */
526
+ dynamicCompressionLevel: z.enum([
527
+ "Low",
528
+ "Mid",
529
+ "High"
530
+ ]).optional(),
531
+ /**
532
+ * Center-crop to fit the output dimensions.
533
+ */
534
+ cropToFit: z.boolean().optional(),
535
+ /**
536
+ * Container of the output video. Defaults to the input container when Topaz
537
+ * can produce it, otherwise `mp4`.
538
+ */
539
+ container: z.enum(topazOutputContainers).optional()
652
540
  });
653
- var topazVideoModelOptionsSchema = lazySchema2(
654
- () => zodSchema2(
655
- z4.object({
656
- /** Interval between status polls, in milliseconds. Defaults to 2000. */
657
- pollIntervalMillis: z4.number().positive().optional(),
658
- /** Maximum time to poll, in milliseconds. Defaults to 3600000 (1 hour). */
659
- pollTimeoutMillis: z4.number().positive().optional(),
660
- source: topazVideoSourceSchema.optional(),
661
- output: topazVideoOutputSchema.optional(),
662
- /**
663
- * Extra `filters[]` entries to send alongside the model's own filter, e.g. a
664
- * frame-interpolation filter. Each entry must include a `model` key.
665
- */
666
- additionalFilters: z4.array(z4.record(z4.string(), z4.unknown())).optional(),
667
- /**
668
- * Escape hatch for filter settings this package does not model yet. Merged
669
- * into the model's filter entry, taking precedence over the typed options
670
- * below.
671
- */
672
- filter: z4.record(z4.string(), z4.unknown()).optional(),
673
- // ---------------------------------------------------------------------------
674
- // Proteus (`proteus`)
675
- // ---------------------------------------------------------------------------
676
- /** Proteus: how the input frames are encoded. */
677
- videoType: z4.enum(["Progressive", "Interlaced", "ProgressiveInterlaced"]).optional(),
678
- /** Proteus: parameter estimation mode. */
679
- auto: z4.enum(["Auto", "Manual", "Relative"]).optional(),
680
- /** Proteus: field order for interlaced input. */
681
- fieldOrder: z4.enum(["TopFirst", "BottomFirst", "Auto"]).optional(),
682
- /** Proteus: focus-fix strength. */
683
- focusFixLevel: z4.enum(["None", "Normal", "Strong"]).optional(),
684
- /** Proteus: compression artifact removal, -1 to 1. */
685
- compression: z4.number().min(-1).max(1).optional(),
686
- /** Proteus: detail recovery, -1 to 1. */
687
- details: z4.number().min(-1).max(1).optional(),
688
- /** Proteus: pre-processing noise reduction, 0 to 0.1. */
689
- prenoise: z4.number().min(0).max(0.1).optional(),
690
- /** Proteus: noise reduction, -1 to 1. */
691
- noise: z4.number().min(-1).max(1).optional(),
692
- /** Proteus: halo suppression, -1 to 1. */
693
- halo: z4.number().min(-1).max(1).optional(),
694
- /** Proteus: pre-processing blur, -1 to 1. */
695
- preblur: z4.number().min(-1).max(1).optional(),
696
- /** Proteus: sharpening, -1 to 1. */
697
- blur: z4.number().min(-1).max(1).optional(),
698
- /** Proteus: grain amount, 0 to 0.1. */
699
- grain: z4.number().min(0).max(0.1).optional(),
700
- /** Proteus: grain sigma, 0 to 1. */
701
- grainSigma: z4.number().min(0).max(1).optional(),
702
- /** Proteus: grain size, 0 to 5. */
703
- grainSize: z4.number().min(0).max(5).optional(),
704
- /** Proteus: grain model. */
705
- grainType: z4.enum(["silver_rich", "gaussian", "grey"]).optional(),
706
- /** Proteus: original detail recovery, 0 to 1. */
707
- recoverOriginalDetailValue: z4.number().min(0).max(1).optional(),
708
- // ---------------------------------------------------------------------------
709
- // Starlight Precise (`starlight-precise-2.6`)
710
- // ---------------------------------------------------------------------------
711
- /** Starlight: sharpening applied to the output, 1.0 to 5.0. Defaults to 5.0. */
712
- sharpness: z4.number().min(1).max(5).optional(),
713
- /** Starlight: output bit depth. */
714
- videoBitDepth: z4.number().int().positive().optional(),
715
- /** Starlight: output video codec. */
716
- videoCodec: z4.enum(["ffv1", "prores", "vp9"]).optional(),
717
- /** Starlight: output chroma subsampling profile. */
718
- videoProfile: z4.enum(["420", "422", "444"]).optional(),
719
- /** Starlight: whether to watermark the output. Defaults to `false`. */
720
- watermark: z4.boolean().optional()
721
- })
722
- )
723
- );
724
- var TOPAZ_NON_FILTER_OPTION_KEYS = [
725
- "pollIntervalMillis",
726
- "pollTimeoutMillis",
727
- "source",
728
- "output",
729
- "additionalFilters",
730
- "filter"
541
+ /**
542
+ * Provider options for Topaz video models.
543
+ *
544
+ * @see https://developer.topazlabs.com/video-models/proteus/proteus-1
545
+ * @see https://developer.topazlabs.com/video-models/starlight/starlight-precise-2.6
546
+ */
547
+ const topazVideoModelOptionsSchema = lazySchema(() => zodSchema(z.object({
548
+ source: topazVideoSourceSchema.optional(),
549
+ output: topazVideoOutputSchema.optional(),
550
+ /**
551
+ * Extra `filters[]` entries to send alongside the model's own filter, e.g. a
552
+ * frame-interpolation filter. Each entry must include a `model` key.
553
+ */
554
+ additionalFilters: z.array(z.record(z.string(), z.unknown())).optional(),
555
+ /**
556
+ * Escape hatch for filter settings this package does not model yet. Merged
557
+ * into the model's filter entry, taking precedence over the typed options
558
+ * below.
559
+ */
560
+ filter: z.record(z.string(), z.unknown()).optional(),
561
+ /** Proteus: how the input frames are encoded. */
562
+ videoType: z.enum([
563
+ "Progressive",
564
+ "Interlaced",
565
+ "ProgressiveInterlaced"
566
+ ]).optional(),
567
+ /** Proteus: parameter estimation mode. */
568
+ auto: z.enum([
569
+ "Auto",
570
+ "Manual",
571
+ "Relative"
572
+ ]).optional(),
573
+ /** Proteus: field order for interlaced input. */
574
+ fieldOrder: z.enum([
575
+ "TopFirst",
576
+ "BottomFirst",
577
+ "Auto"
578
+ ]).optional(),
579
+ /** Proteus: focus-fix strength. */
580
+ focusFixLevel: z.enum([
581
+ "None",
582
+ "Normal",
583
+ "Strong"
584
+ ]).optional(),
585
+ /** Proteus: compression artifact removal, -1 to 1. */
586
+ compression: z.number().min(-1).max(1).optional(),
587
+ /** Proteus: detail recovery, -1 to 1. */
588
+ details: z.number().min(-1).max(1).optional(),
589
+ /** Proteus: pre-processing noise reduction, 0 to 0.1. */
590
+ prenoise: z.number().min(0).max(.1).optional(),
591
+ /** Proteus: noise reduction, -1 to 1. */
592
+ noise: z.number().min(-1).max(1).optional(),
593
+ /** Proteus: halo suppression, -1 to 1. */
594
+ halo: z.number().min(-1).max(1).optional(),
595
+ /** Proteus: pre-processing blur, -1 to 1. */
596
+ preblur: z.number().min(-1).max(1).optional(),
597
+ /** Proteus: sharpening, -1 to 1. */
598
+ blur: z.number().min(-1).max(1).optional(),
599
+ /** Proteus: grain amount, 0 to 0.1. */
600
+ grain: z.number().min(0).max(.1).optional(),
601
+ /** Proteus: grain sigma, 0 to 1. */
602
+ grainSigma: z.number().min(0).max(1).optional(),
603
+ /** Proteus: grain size, 0 to 5. */
604
+ grainSize: z.number().min(0).max(5).optional(),
605
+ /** Proteus: grain model. */
606
+ grainType: z.enum([
607
+ "silver_rich",
608
+ "gaussian",
609
+ "grey"
610
+ ]).optional(),
611
+ /** Proteus: original detail recovery, 0 to 1. */
612
+ recoverOriginalDetailValue: z.number().min(0).max(1).optional(),
613
+ /** Starlight: sharpening applied to the output, 1.0 to 5.0. Defaults to 5.0. */
614
+ sharpness: z.number().min(1).max(5).optional(),
615
+ /** Starlight: output bit depth. */
616
+ videoBitDepth: z.number().int().positive().optional(),
617
+ /** Starlight: output video codec. */
618
+ videoCodec: z.enum([
619
+ "ffv1",
620
+ "prores",
621
+ "vp9"
622
+ ]).optional(),
623
+ /** Starlight: output chroma subsampling profile. */
624
+ videoProfile: z.enum([
625
+ "420",
626
+ "422",
627
+ "444"
628
+ ]).optional(),
629
+ /** Starlight: whether to watermark the output. Defaults to `false`. */
630
+ watermark: z.boolean().optional()
631
+ })));
632
+ /**
633
+ * Option keys that are structural rather than filter settings, so they are not
634
+ * forwarded into the `filters[]` entry.
635
+ */
636
+ const TOPAZ_NON_FILTER_OPTION_KEYS = [
637
+ "source",
638
+ "output",
639
+ "additionalFilters",
640
+ "filter"
731
641
  ];
732
-
733
- // src/topaz-video-settings.ts
734
- var topazVideoApiModelIds = {
735
- proteus: "prob-4",
736
- "starlight-precise-2.6": "slp-2.6"
642
+ //#endregion
643
+ //#region src/topaz-video-settings.ts
644
+ /**
645
+ * Maps AI SDK video model ids onto Topaz API model names.
646
+ *
647
+ * @see https://developer.topazlabs.com/video-models/proteus/proteus-1
648
+ * @see https://developer.topazlabs.com/video-models/starlight/starlight-precise-2.6
649
+ */
650
+ const topazVideoApiModelIds = {
651
+ proteus: "prob-4",
652
+ "starlight-precise-2.6": "slp-2.6"
737
653
  };
738
654
  function resolveTopazVideoApiModelId(modelId) {
739
- var _a;
740
- return (_a = topazVideoApiModelIds[modelId]) != null ? _a : modelId;
655
+ return topazVideoApiModelIds[modelId] ?? modelId;
741
656
  }
742
-
743
- // src/version.ts
744
- var VERSION = true ? "2.0.0" : "0.0.0-test";
745
-
746
- // src/topaz-video-model.ts
747
- var DEFAULT_POLL_INTERVAL_MILLIS2 = 2e3;
748
- var DEFAULT_POLL_TIMEOUT_MILLIS2 = 36e5;
749
- var mediaTypeContainers = {
750
- "video/mp4": "mp4",
751
- "video/quicktime": "mov",
752
- "video/mov": "mov",
753
- "video/x-matroska": "mkv",
754
- "video/matroska": "mkv",
755
- "video/webm": "webm",
756
- "video/x-msvideo": "avi",
757
- "video/avi": "avi",
758
- "video/mpeg": "mpeg",
759
- "video/mp2t": "ts",
760
- "video/x-ms-wmv": "wmv",
761
- "video/x-flv": "flv",
762
- "video/3gpp": "3gp",
763
- "video/x-m4v": "m4v",
764
- "application/mxf": "mxf"
657
+ //#endregion
658
+ //#region src/version.ts
659
+ const VERSION = "3.0.1";
660
+ //#endregion
661
+ //#region src/topaz-video-model.ts
662
+ const mediaTypeContainers = {
663
+ "video/mp4": "mp4",
664
+ "video/quicktime": "mov",
665
+ "video/mov": "mov",
666
+ "video/x-matroska": "mkv",
667
+ "video/matroska": "mkv",
668
+ "video/webm": "webm",
669
+ "video/x-msvideo": "avi",
670
+ "video/avi": "avi",
671
+ "video/mpeg": "mpeg",
672
+ "video/mp2t": "ts",
673
+ "video/x-ms-wmv": "wmv",
674
+ "video/x-flv": "flv",
675
+ "video/3gpp": "3gp",
676
+ "video/x-m4v": "m4v",
677
+ "application/mxf": "mxf"
765
678
  };
766
- var extensionContainers = {
767
- ...Object.fromEntries(topazSourceContainers.map((c) => [c, c])),
768
- qt: "mov"
679
+ const extensionContainers = {
680
+ ...Object.fromEntries(topazSourceContainers.map((c) => [c, c])),
681
+ qt: "mov"
769
682
  };
770
- var containerMediaTypes = {
771
- mp4: "video/mp4",
772
- m4v: "video/mp4",
773
- mov: "video/quicktime",
774
- mkv: "video/x-matroska",
775
- webm: "video/webm",
776
- avi: "video/x-msvideo",
777
- mpeg: "video/mpeg",
778
- mpg: "video/mpeg",
779
- ts: "video/mp2t",
780
- wmv: "video/x-ms-wmv",
781
- flv: "video/x-flv",
782
- "3gp": "video/3gpp",
783
- mxf: "application/mxf"
683
+ const containerMediaTypes = {
684
+ mp4: "video/mp4",
685
+ m4v: "video/mp4",
686
+ mov: "video/quicktime",
687
+ mkv: "video/x-matroska",
688
+ webm: "video/webm",
689
+ avi: "video/x-msvideo",
690
+ mpeg: "video/mpeg",
691
+ mpg: "video/mpeg",
692
+ ts: "video/mp2t",
693
+ wmv: "video/x-ms-wmv",
694
+ flv: "video/x-flv",
695
+ "3gp": "video/3gpp",
696
+ mxf: "application/mxf"
784
697
  };
785
- var TopazVideoModel = class {
786
- constructor(modelId, config) {
787
- this.modelId = modelId;
788
- this.config = config;
789
- this.specificationVersion = "v3";
790
- this.maxVideosPerCall = 1;
791
- }
792
- get provider() {
793
- return this.config.provider;
794
- }
795
- async doGenerate(options) {
796
- var _a, _b, _c;
797
- const topazOptions = await parseProviderOptions2({
798
- provider: "topaz",
799
- providerOptions: options.providerOptions,
800
- schema: topazVideoModelOptionsSchema
801
- });
802
- const started = await this.start(options);
803
- const pollIntervalMillis = (_a = topazOptions == null ? void 0 : topazOptions.pollIntervalMillis) != null ? _a : DEFAULT_POLL_INTERVAL_MILLIS2;
804
- const pollTimeoutMillis = (_b = topazOptions == null ? void 0 : topazOptions.pollTimeoutMillis) != null ? _b : DEFAULT_POLL_TIMEOUT_MILLIS2;
805
- const deadline = Date.now() + pollTimeoutMillis;
806
- try {
807
- while (true) {
808
- const result = await this.getStatus({
809
- operation: started.operation,
810
- headers: options.headers,
811
- abortSignal: options.abortSignal
812
- });
813
- if (result != null) {
814
- return {
815
- ...result,
816
- warnings: [...started.warnings, ...result.warnings],
817
- response: {
818
- ...result.response,
819
- timestamp: started.response.timestamp
820
- }
821
- };
822
- }
823
- if (Date.now() + pollIntervalMillis > deadline) {
824
- const headers = combineHeaders2(
825
- await resolve2(this.config.headers),
826
- options.headers
827
- );
828
- await this.cancelQuietly(started.operation.requestId, headers);
829
- throw new AISDKError2({
830
- name: "TOPAZ_VIDEO_ENHANCEMENT_TIMEOUT",
831
- message: `Topaz video enhancement did not finish within ${pollTimeoutMillis}ms (request ${started.operation.requestId}). Increase the \`pollTimeoutMillis\` provider option if the job needs longer.`
832
- });
833
- }
834
- await delay2(pollIntervalMillis, { abortSignal: options.abortSignal });
835
- }
836
- } catch (error) {
837
- if ((_c = options.abortSignal) == null ? void 0 : _c.aborted) {
838
- const headers = combineHeaders2(
839
- await resolve2(this.config.headers),
840
- options.headers
841
- );
842
- await this.cancelQuietly(started.operation.requestId, headers);
843
- }
844
- throw error;
845
- }
846
- }
847
- async start(options) {
848
- var _a, _b, _c, _d, _e, _f, _g;
849
- const currentDate = (_c = (_b = (_a = this.config._internal) == null ? void 0 : _a.currentDate) == null ? void 0 : _b.call(_a)) != null ? _c : /* @__PURE__ */ new Date();
850
- const warnings = [];
851
- const topazOptions = await parseProviderOptions2({
852
- provider: "topaz",
853
- providerOptions: options.providerOptions,
854
- schema: topazVideoModelOptionsSchema
855
- });
856
- const headers = combineHeaders2(
857
- await resolve2(this.config.headers),
858
- options.headers
859
- );
860
- this.addUnsupportedWarnings(options, warnings);
861
- const input = this.selectInputVideo(options, warnings);
862
- const container = resolveContainer(input, (_d = topazOptions == null ? void 0 : topazOptions.source) == null ? void 0 : _d.container);
863
- const source = resolveSource(topazOptions, container);
864
- const output = buildOutput({ options, topazOptions, source });
865
- const filters = [
866
- buildFilter(this.modelId, topazOptions),
867
- ...(_e = topazOptions == null ? void 0 : topazOptions.additionalFilters) != null ? _e : []
868
- ];
869
- const contentType = (_f = containerMediaTypes[source.container]) != null ? _f : "application/octet-stream";
870
- const metadata = source.metadata;
871
- const bytes = input.type === "file" ? fileBytes(input) : void 0;
872
- let requestId;
873
- try {
874
- const { value: created, responseHeaders } = await postJsonToApi({
875
- url: `${this.config.baseURL}/video/express`,
876
- headers,
877
- body: {
878
- source: {
879
- container: source.container,
880
- ...metadata != null ? {
881
- duration: metadata.duration,
882
- frameCount: metadata.frameCount,
883
- frameRate: metadata.frameRate,
884
- resolution: {
885
- width: metadata.width,
886
- height: metadata.height
887
- }
888
- } : {},
889
- ...input.type === "url" ? (
890
- // Topaz fetches URL inputs itself, so, like other providers,
891
- // the SDK never downloads them.
892
- { external: { provider: "s3", presignedUrl: input.url } }
893
- ) : { size: bytes == null ? void 0 : bytes.byteLength }
894
- },
895
- output,
896
- filters
897
- },
898
- successfulResponseHandler: createJsonResponseHandler2(
899
- topazVideoExpressResponseSchema
900
- ),
901
- failedResponseHandler: topazFailedResponseHandler,
902
- abortSignal: options.abortSignal,
903
- fetch: this.config.fetch
904
- });
905
- requestId = requireRequestId(created.requestId);
906
- if (bytes != null) {
907
- await this.uploadVideo({
908
- requestId,
909
- bytes,
910
- urls: created.uploadUrls,
911
- contentType,
912
- abortSignal: options.abortSignal
913
- });
914
- }
915
- return {
916
- // The output container travels with the operation so the polling code can
917
- // report the right media type without re-deriving it.
918
- operation: { requestId, outputContainer: output.container },
919
- warnings,
920
- providerMetadata: {
921
- topaz: {
922
- requestId,
923
- // Preliminary: Topaz estimates up front only when it gets source
924
- // metadata, and the completed status carries the billed value.
925
- ...((_g = created.estimates) == null ? void 0 : _g.cost) != null ? { estimatedCredits: created.estimates.cost } : {}
926
- }
927
- },
928
- response: {
929
- timestamp: currentDate,
930
- modelId: this.modelId,
931
- headers: responseHeaders
932
- }
933
- };
934
- } catch (error) {
935
- if (requestId != null) {
936
- await this.cancelQuietly(requestId, headers);
937
- }
938
- throw error;
939
- }
940
- }
941
- async getStatus(options) {
942
- var _a, _b, _c, _d, _e, _f, _g, _h;
943
- const currentDate = (_c = (_b = (_a = this.config._internal) == null ? void 0 : _a.currentDate) == null ? void 0 : _b.call(_a)) != null ? _c : /* @__PURE__ */ new Date();
944
- const { requestId, outputContainer } = options.operation;
945
- const { value: status, responseHeaders } = await topazGetFromApi({
946
- url: `${this.config.baseURL}/video/${requestId}/status`,
947
- // Built from the configured baseURL, not from response data.
948
- validateUrl: false,
949
- headers: combineHeaders2(
950
- await resolve2(this.config.headers),
951
- options.headers
952
- ),
953
- successfulResponseHandler: createJsonResponseHandler2(
954
- topazVideoStatusResponseSchema
955
- ),
956
- failedResponseHandler: topazFailedResponseHandler,
957
- abortSignal: options.abortSignal,
958
- fetch: this.config.fetch
959
- });
960
- const response = {
961
- timestamp: currentDate,
962
- modelId: this.modelId,
963
- headers: responseHeaders
964
- };
965
- if (status.status === "complete") {
966
- const credits = lowerBoundCredits((_d = status.estimates) == null ? void 0 : _d.cost);
967
- const url = (_e = status.download) == null ? void 0 : _e.url;
968
- if (url == null) {
969
- throw new InvalidResponseDataError2({
970
- data: status,
971
- message: `Topaz reported request ${requestId} complete but returned no download URL.`
972
- });
973
- }
974
- return {
975
- videos: [
976
- {
977
- type: "url",
978
- url,
979
- mediaType: (_f = outputContainer != null ? containerMediaTypes[outputContainer] : void 0) != null ? _f : "video/mp4"
980
- }
981
- ],
982
- warnings: [],
983
- response,
984
- providerMetadata: {
985
- topaz: {
986
- requestId,
987
- ...credits != null ? { credits } : {},
988
- ...((_g = status.estimates) == null ? void 0 : _g.cost) != null ? { estimatedCredits: status.estimates.cost } : {},
989
- ...status.outputSize != null ? { outputSize: status.outputSize } : {},
990
- ...((_h = status.download) == null ? void 0 : _h.expiresAt) != null ? { expiresAt: status.download.expiresAt } : {}
991
- }
992
- }
993
- };
994
- }
995
- if (status.status === "failed" || status.status === "canceled") {
996
- throw new AISDKError2({
997
- name: "TOPAZ_VIDEO_ENHANCEMENT_FAILED",
998
- message: `Topaz video request ${requestId} ${status.status}` + (status.errorCode != null ? ` (${status.errorCode})` : "") + (status.message != null ? `: ${status.message}` : ".")
999
- });
1000
- }
1001
- return void 0;
1002
- }
1003
- addUnsupportedWarnings(options, warnings) {
1004
- if (options.prompt != null && options.prompt.trim() !== "") {
1005
- warnings.push({
1006
- type: "unsupported",
1007
- feature: "prompt",
1008
- details: "Topaz video models enhance an existing video and do not take a text prompt. The prompt was ignored."
1009
- });
1010
- }
1011
- if (options.aspectRatio != null) {
1012
- warnings.push({
1013
- type: "unsupported",
1014
- feature: "aspectRatio",
1015
- details: "Topaz video models do not support aspectRatio. Use `resolution`, or the `output` provider option, to set the output dimensions."
1016
- });
1017
- }
1018
- if (options.seed != null) {
1019
- warnings.push({
1020
- type: "unsupported",
1021
- feature: "seed",
1022
- details: "Topaz video models do not support seed."
1023
- });
1024
- }
1025
- if (options.duration != null) {
1026
- warnings.push({
1027
- type: "unsupported",
1028
- feature: "duration",
1029
- details: "Topaz video models enhance the whole input video, so duration was ignored. Pass the input duration via the `source.duration` provider option instead."
1030
- });
1031
- }
1032
- if (options.generateAudio != null) {
1033
- warnings.push({
1034
- type: "unsupported",
1035
- feature: "generateAudio",
1036
- details: "Topaz video models do not generate audio. Use the `output.audioTransfer` provider option to control how the input audio track is carried over."
1037
- });
1038
- }
1039
- if (options.frameImages != null && options.frameImages.length > 0) {
1040
- warnings.push({
1041
- type: "unsupported",
1042
- feature: "frameImages",
1043
- details: "Topaz video models do not support first/last frame generation. The frame images were ignored."
1044
- });
1045
- }
1046
- if (options.n > 1) {
1047
- warnings.push({
1048
- type: "unsupported",
1049
- feature: "n",
1050
- details: "Topaz video models enhance one video per call. Only 1 video will be produced."
1051
- });
1052
- }
1053
- }
1054
- selectInputVideo(options, warnings) {
1055
- var _a;
1056
- const references = (_a = options.inputReferences) != null ? _a : [];
1057
- const videos = references.filter(isVideoReference);
1058
- if (videos.length === 0) {
1059
- if (options.image != null) {
1060
- throw new InvalidArgumentError2({
1061
- argument: "image",
1062
- message: "Topaz video models enhance an existing video, not a still image. Pass the input video via `inputReferences`."
1063
- });
1064
- }
1065
- throw new InvalidArgumentError2({
1066
- argument: "inputReferences",
1067
- message: 'Topaz video models require an input video. Pass it via `inputReferences`, e.g. `inputReferences: [{ type: "file", mediaType: "video/mp4", data: bytes }]`. For URL references, include `mediaType` so the reference is recognized as a video.'
1068
- });
1069
- }
1070
- if (references.length > 1) {
1071
- warnings.push({
1072
- type: "unsupported",
1073
- feature: "inputReferences",
1074
- details: "Topaz video models enhance a single video. Only the first video reference was used."
1075
- });
1076
- }
1077
- return videos[0];
1078
- }
1079
- async uploadVideo({
1080
- requestId,
1081
- bytes,
1082
- urls,
1083
- contentType,
1084
- abortSignal
1085
- }) {
1086
- var _a;
1087
- const url = urls == null ? void 0 : urls[0];
1088
- if (url == null) {
1089
- throw new InvalidResponseDataError2({
1090
- data: urls,
1091
- message: `Topaz returned no upload URL for request ${requestId}.`
1092
- });
1093
- }
1094
- if (!isSameOrigin2(url, this.config.baseURL)) {
1095
- validateDownloadUrl(url);
1096
- }
1097
- const fetchImpl = (_a = this.config.fetch) != null ? _a : globalThis.fetch;
1098
- const response = await fetchImpl(url, {
1099
- method: "PUT",
1100
- headers: withUserAgentSuffix(
1101
- { "Content-Type": contentType },
1102
- `ai-sdk-topaz/${VERSION}`
1103
- ),
1104
- body: bytes,
1105
- signal: abortSignal
1106
- });
1107
- if (!response.ok) {
1108
- throw new APICallError({
1109
- message: `Uploading the input video failed with status ${response.status}.`,
1110
- url,
1111
- requestBodyValues: {},
1112
- statusCode: response.status,
1113
- responseHeaders: Object.fromEntries(response.headers.entries()),
1114
- responseBody: await response.text()
1115
- });
1116
- }
1117
- }
1118
- async cancelQuietly(requestId, headers) {
1119
- var _a;
1120
- const fetchImpl = (_a = this.config.fetch) != null ? _a : globalThis.fetch;
1121
- try {
1122
- await fetchImpl(`${this.config.baseURL}/video/${requestId}`, {
1123
- method: "DELETE",
1124
- headers: removeUndefinedEntries2(headers)
1125
- });
1126
- } catch (e) {
1127
- }
1128
- }
698
+ /**
699
+ * Topaz video models enhance a video the caller supplies, passed through
700
+ * `inputReferences`. `doStart` creates an express request: Topaz fetches URL
701
+ * inputs itself, and file inputs are uploaded to the returned URL. Processing
702
+ * starts once Topaz has the video, and `doStatus` polls.
703
+ */
704
+ var TopazVideoModel = class TopazVideoModel {
705
+ get provider() {
706
+ return this.config.provider;
707
+ }
708
+ static [WORKFLOW_SERIALIZE](model) {
709
+ return serializeModelOptions({
710
+ modelId: model.modelId,
711
+ config: model.config
712
+ });
713
+ }
714
+ static [WORKFLOW_DESERIALIZE](options) {
715
+ return new TopazVideoModel(options.modelId, options.config);
716
+ }
717
+ constructor(modelId, config) {
718
+ this.modelId = modelId;
719
+ this.config = config;
720
+ this.specificationVersion = "v4";
721
+ this.maxVideosPerCall = 1;
722
+ }
723
+ async doStart(options) {
724
+ const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
725
+ const warnings = [];
726
+ const topazOptions = await parseProviderOptions({
727
+ provider: "topaz",
728
+ providerOptions: options.providerOptions,
729
+ schema: topazVideoModelOptionsSchema
730
+ });
731
+ const headers = combineHeaders(await resolve(this.config.headers), options.headers);
732
+ this.addUnsupportedWarnings(options, warnings);
733
+ const input = this.selectInputVideo(options, warnings);
734
+ const source = resolveSource(topazOptions, resolveContainer(input, topazOptions?.source?.container));
735
+ const output = buildOutput({
736
+ options,
737
+ topazOptions,
738
+ source
739
+ });
740
+ const filters = [buildFilter(this.modelId, topazOptions), ...topazOptions?.additionalFilters ?? []];
741
+ const contentType = containerMediaTypes[source.container] ?? "application/octet-stream";
742
+ const metadata = source.metadata;
743
+ const bytes = input.type === "file" ? fileBytes(input) : void 0;
744
+ let requestId;
745
+ try {
746
+ const { value: created, responseHeaders } = await postJsonToApi({
747
+ url: `${this.config.baseURL}/video/express`,
748
+ headers,
749
+ body: {
750
+ source: {
751
+ container: source.container,
752
+ ...metadata != null ? {
753
+ duration: metadata.duration,
754
+ frameCount: metadata.frameCount,
755
+ frameRate: metadata.frameRate,
756
+ resolution: {
757
+ width: metadata.width,
758
+ height: metadata.height
759
+ }
760
+ } : {},
761
+ ...input.type === "url" ? { external: {
762
+ provider: "s3",
763
+ presignedUrl: input.url
764
+ } } : { size: bytes?.byteLength }
765
+ },
766
+ output,
767
+ filters
768
+ },
769
+ successfulResponseHandler: createJsonResponseHandler(topazVideoExpressResponseSchema),
770
+ failedResponseHandler: topazFailedResponseHandler,
771
+ abortSignal: options.abortSignal,
772
+ fetch: this.config.fetch
773
+ });
774
+ requestId = requireRequestId(created.requestId);
775
+ if (bytes != null) await this.uploadVideo({
776
+ requestId,
777
+ bytes,
778
+ urls: created.uploadUrls,
779
+ contentType,
780
+ abortSignal: options.abortSignal
781
+ });
782
+ return {
783
+ operation: {
784
+ requestId,
785
+ outputContainer: output.container
786
+ },
787
+ warnings,
788
+ providerMetadata: { topaz: {
789
+ requestId,
790
+ ...created.estimates?.cost != null ? { estimatedCredits: created.estimates.cost } : {}
791
+ } },
792
+ response: {
793
+ timestamp: currentDate,
794
+ modelId: this.modelId,
795
+ headers: responseHeaders
796
+ }
797
+ };
798
+ } catch (error) {
799
+ if (requestId != null) await this.cancelQuietly(requestId, headers);
800
+ throw error;
801
+ }
802
+ }
803
+ async doStatus(options) {
804
+ const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
805
+ const { requestId, outputContainer } = options.operation;
806
+ const { value: status, responseHeaders } = await getFromApi({
807
+ url: `${this.config.baseURL}/video/${requestId}/status`,
808
+ validateUrl: false,
809
+ headers: combineHeaders(await resolve(this.config.headers), options.headers),
810
+ successfulResponseHandler: createJsonResponseHandler(topazVideoStatusResponseSchema),
811
+ failedResponseHandler: topazFailedResponseHandler,
812
+ abortSignal: options.abortSignal,
813
+ fetch: this.config.fetch
814
+ });
815
+ const response = {
816
+ timestamp: currentDate,
817
+ modelId: this.modelId,
818
+ headers: responseHeaders
819
+ };
820
+ if (status.status === "complete") {
821
+ const credits = lowerBoundCredits(status.estimates?.cost);
822
+ const url = status.download?.url;
823
+ if (url == null) throw new InvalidResponseDataError({
824
+ data: status,
825
+ message: `Topaz reported request ${requestId} complete but returned no download URL.`
826
+ });
827
+ return {
828
+ status: "completed",
829
+ videos: [{
830
+ type: "url",
831
+ url,
832
+ mediaType: (outputContainer != null ? containerMediaTypes[outputContainer] : void 0) ?? "video/mp4"
833
+ }],
834
+ warnings: [],
835
+ response,
836
+ providerMetadata: { topaz: {
837
+ requestId,
838
+ ...credits != null ? { credits } : {},
839
+ ...status.estimates?.cost != null ? { estimatedCredits: status.estimates.cost } : {},
840
+ ...status.outputSize != null ? { outputSize: status.outputSize } : {},
841
+ ...status.download?.expiresAt != null ? { expiresAt: status.download.expiresAt } : {}
842
+ } }
843
+ };
844
+ }
845
+ if (status.status === "failed" || status.status === "canceled") return {
846
+ status: "error",
847
+ error: `Topaz video request ${requestId} ${status.status}` + (status.errorCode != null ? ` (${status.errorCode})` : "") + (status.message != null ? `: ${status.message}` : "."),
848
+ providerMetadata: { topaz: {
849
+ requestId,
850
+ ...status.errorCode != null ? { errorCode: status.errorCode } : {}
851
+ } },
852
+ response
853
+ };
854
+ return {
855
+ status: "pending",
856
+ response
857
+ };
858
+ }
859
+ addUnsupportedWarnings(options, warnings) {
860
+ if (options.prompt != null && options.prompt.trim() !== "") warnings.push({
861
+ type: "unsupported",
862
+ feature: "prompt",
863
+ details: "Topaz video models enhance an existing video and do not take a text prompt. The prompt was ignored."
864
+ });
865
+ if (options.aspectRatio != null) warnings.push({
866
+ type: "unsupported",
867
+ feature: "aspectRatio",
868
+ details: "Topaz video models do not support aspectRatio. Use `resolution`, or the `output` provider option, to set the output dimensions."
869
+ });
870
+ if (options.seed != null) warnings.push({
871
+ type: "unsupported",
872
+ feature: "seed",
873
+ details: "Topaz video models do not support seed."
874
+ });
875
+ if (options.duration != null) warnings.push({
876
+ type: "unsupported",
877
+ feature: "duration",
878
+ details: "Topaz video models enhance the whole input video, so duration was ignored. Pass the input duration via the `source.duration` provider option instead."
879
+ });
880
+ if (options.generateAudio != null) warnings.push({
881
+ type: "unsupported",
882
+ feature: "generateAudio",
883
+ details: "Topaz video models do not generate audio. Use the `output.audioTransfer` provider option to control how the input audio track is carried over."
884
+ });
885
+ if (options.frameImages != null && options.frameImages.length > 0) warnings.push({
886
+ type: "unsupported",
887
+ feature: "frameImages",
888
+ details: "Topaz video models do not support first/last frame generation. The frame images were ignored."
889
+ });
890
+ if (options.n > 1) warnings.push({
891
+ type: "unsupported",
892
+ feature: "n",
893
+ details: "Topaz video models enhance one video per call. Only 1 video will be produced."
894
+ });
895
+ }
896
+ selectInputVideo(options, warnings) {
897
+ const references = options.inputReferences ?? [];
898
+ const videos = references.filter(isVideoReference);
899
+ if (videos.length === 0) {
900
+ if (options.image != null) throw new InvalidArgumentError({
901
+ argument: "image",
902
+ message: "Topaz video models enhance an existing video, not a still image. Pass the input video via `inputReferences`."
903
+ });
904
+ throw new InvalidArgumentError({
905
+ argument: "inputReferences",
906
+ message: "Topaz video models require an input video. Pass it via `inputReferences`, e.g. `inputReferences: [{ type: \"file\", mediaType: \"video/mp4\", data: bytes }]`. For URL references, include `mediaType` so the reference is recognized as a video."
907
+ });
908
+ }
909
+ if (references.length > 1) warnings.push({
910
+ type: "unsupported",
911
+ feature: "inputReferences",
912
+ details: "Topaz video models enhance a single video. Only the first video reference was used."
913
+ });
914
+ return videos[0];
915
+ }
916
+ async uploadVideo({ requestId, bytes, urls, contentType, abortSignal }) {
917
+ const url = urls?.[0];
918
+ if (url == null) throw new InvalidResponseDataError({
919
+ data: urls,
920
+ message: `Topaz returned no upload URL for request ${requestId}.`
921
+ });
922
+ if (!isSameOrigin(url, this.config.baseURL)) validateDownloadUrl(url);
923
+ const response = await (this.config.fetch ?? globalThis.fetch)(url, {
924
+ method: "PUT",
925
+ headers: withUserAgentSuffix({ "Content-Type": contentType }, `ai-sdk-topaz/${VERSION}`),
926
+ body: bytes,
927
+ signal: abortSignal
928
+ });
929
+ if (!response.ok) throw new APICallError({
930
+ message: `Uploading the input video failed with status ${response.status}.`,
931
+ url,
932
+ requestBodyValues: {},
933
+ statusCode: response.status,
934
+ responseHeaders: Object.fromEntries(response.headers.entries()),
935
+ responseBody: await response.text()
936
+ });
937
+ }
938
+ async cancelQuietly(requestId, headers) {
939
+ const fetchImpl = this.config.fetch ?? globalThis.fetch;
940
+ try {
941
+ await fetchImpl(`${this.config.baseURL}/video/${requestId}`, {
942
+ method: "DELETE",
943
+ headers: removeUndefinedEntries(headers)
944
+ });
945
+ } catch {}
946
+ }
1129
947
  };
1130
948
  function lowerBoundCredits(cost) {
1131
- return cost != null && cost.length > 0 ? Math.min(...cost) : void 0;
949
+ return cost != null && cost.length > 0 ? Math.min(...cost) : void 0;
1132
950
  }
1133
951
  function fileBytes(input) {
1134
- return typeof input.data === "string" ? convertBase64ToUint8Array2(input.data) : input.data;
952
+ return typeof input.data === "string" ? convertBase64ToUint8Array(input.data) : input.data;
1135
953
  }
1136
954
  function resolveContainer(input, declaredContainer) {
1137
- var _a;
1138
- if (declaredContainer != null) {
1139
- return declaredContainer;
1140
- }
1141
- if (input.type === "file") {
1142
- const container2 = mediaTypeContainers[input.mediaType.toLowerCase()];
1143
- if (container2 == null) {
1144
- throw new InvalidArgumentError2({
1145
- argument: "inputReferences",
1146
- message: `Could not map the media type "${input.mediaType}" onto a Topaz container. Set the \`source.container\` provider option explicitly.`
1147
- });
1148
- }
1149
- return container2;
1150
- }
1151
- const container = (_a = input.mediaType != null ? mediaTypeContainers[input.mediaType.toLowerCase()] : void 0) != null ? _a : containerFromUrl(input.url);
1152
- if (container == null) {
1153
- throw new InvalidArgumentError2({
1154
- argument: "inputReferences",
1155
- message: `Could not determine the container of the input video at "${input.url}". Set the \`source.container\` provider option, or pass \`mediaType\` on the reference.`
1156
- });
1157
- }
1158
- return container;
955
+ if (declaredContainer != null) return declaredContainer;
956
+ if (input.type === "file") {
957
+ const container = mediaTypeContainers[input.mediaType.toLowerCase()];
958
+ if (container == null) throw new InvalidArgumentError({
959
+ argument: "inputReferences",
960
+ message: `Could not map the media type "${input.mediaType}" onto a Topaz container. Set the \`source.container\` provider option explicitly.`
961
+ });
962
+ return container;
963
+ }
964
+ const container = (input.mediaType != null ? mediaTypeContainers[input.mediaType.toLowerCase()] : void 0) ?? containerFromUrl(input.url);
965
+ if (container == null) throw new InvalidArgumentError({
966
+ argument: "inputReferences",
967
+ message: `Could not determine the container of the input video at "${input.url}". Set the \`source.container\` provider option, or pass \`mediaType\` on the reference.`
968
+ });
969
+ return container;
1159
970
  }
1160
971
  function isVideoReference(reference) {
1161
- if (reference.type === "file") {
1162
- return reference.mediaType.toLowerCase().startsWith("video/");
1163
- }
1164
- if (reference.mediaType != null) {
1165
- return reference.mediaType.toLowerCase().startsWith("video/");
1166
- }
1167
- return containerFromUrl(reference.url) != null;
972
+ if (reference.type === "file") return reference.mediaType.toLowerCase().startsWith("video/");
973
+ if (reference.mediaType != null) return reference.mediaType.toLowerCase().startsWith("video/");
974
+ return containerFromUrl(reference.url) != null;
1168
975
  }
1169
976
  function containerFromUrl(url) {
1170
- var _a;
1171
- const withoutQuery = url.split(/[?#]/)[0];
1172
- const extension = (_a = withoutQuery.split(".").pop()) == null ? void 0 : _a.toLowerCase();
1173
- return extension != null ? extensionContainers[extension] : void 0;
977
+ const extension = url.split(/[?#]/)[0].split(".").pop()?.toLowerCase();
978
+ return extension != null ? extensionContainers[extension] : void 0;
1174
979
  }
980
+ /**
981
+ * Reads the optional source metadata, which is all or nothing. Starlight
982
+ * models require it, and it lets Topaz estimate the cost up front. Nothing is
983
+ * read out of the video bytes: no provider package inspects media files.
984
+ */
1175
985
  function resolveSource(topazOptions, container) {
1176
- var _a;
1177
- const source = topazOptions == null ? void 0 : topazOptions.source;
1178
- const { width, height, duration, frameRate } = source != null ? source : {};
1179
- if (width == null && height == null && duration == null && frameRate == null && (source == null ? void 0 : source.frameCount) == null) {
1180
- return { container, metadata: void 0 };
1181
- }
1182
- const frameCount = (_a = source == null ? void 0 : source.frameCount) != null ? _a : duration != null && frameRate != null ? Math.round(duration * frameRate) : void 0;
1183
- const missing = [
1184
- width == null ? "source.width" : void 0,
1185
- height == null ? "source.height" : void 0,
1186
- duration == null ? "source.duration" : void 0,
1187
- frameRate == null ? "source.frameRate" : void 0
1188
- ].filter((field) => field != null);
1189
- if (width == null || height == null || duration == null || frameRate == null || frameCount == null) {
1190
- throw new InvalidArgumentError2({
1191
- argument: "providerOptions.topaz.source",
1192
- message: `Source metadata must be complete. Missing: ${missing.join(", ")}.`
1193
- });
1194
- }
1195
- return {
1196
- container,
1197
- metadata: { duration, frameRate, frameCount, width, height }
1198
- };
986
+ const source = topazOptions?.source;
987
+ const { width, height, duration, frameRate } = source ?? {};
988
+ if (width == null && height == null && duration == null && frameRate == null && source?.frameCount == null) return {
989
+ container,
990
+ metadata: void 0
991
+ };
992
+ const frameCount = source?.frameCount ?? (duration != null && frameRate != null ? Math.round(duration * frameRate) : void 0);
993
+ const missing = [
994
+ width == null ? "source.width" : void 0,
995
+ height == null ? "source.height" : void 0,
996
+ duration == null ? "source.duration" : void 0,
997
+ frameRate == null ? "source.frameRate" : void 0
998
+ ].filter((field) => field != null);
999
+ if (width == null || height == null || duration == null || frameRate == null || frameCount == null) throw new InvalidArgumentError({
1000
+ argument: "providerOptions.topaz.source",
1001
+ message: `Source metadata must be complete. Missing: ${missing.join(", ")}.`
1002
+ });
1003
+ return {
1004
+ container,
1005
+ metadata: {
1006
+ duration,
1007
+ frameRate,
1008
+ frameCount,
1009
+ width,
1010
+ height
1011
+ }
1012
+ };
1199
1013
  }
1200
1014
  function parseResolution(resolution) {
1201
- if (resolution == null) {
1202
- return { width: void 0, height: void 0 };
1203
- }
1204
- const [width, height] = resolution.split("x").map(Number);
1205
- return { width, height };
1015
+ if (resolution == null) return {
1016
+ width: void 0,
1017
+ height: void 0
1018
+ };
1019
+ const [width, height] = resolution.split("x").map(Number);
1020
+ return {
1021
+ width,
1022
+ height
1023
+ };
1206
1024
  }
1207
- function buildOutput({
1208
- options,
1209
- topazOptions,
1210
- source
1211
- }) {
1212
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
1213
- const output = topazOptions == null ? void 0 : topazOptions.output;
1214
- const resolution = parseResolution(options.resolution);
1215
- const width = (_c = (_a = output == null ? void 0 : output.width) != null ? _a : resolution.width) != null ? _c : (_b = source.metadata) == null ? void 0 : _b.width;
1216
- const height = (_f = (_d = output == null ? void 0 : output.height) != null ? _d : resolution.height) != null ? _f : (_e = source.metadata) == null ? void 0 : _e.height;
1217
- if (width == null || height == null) {
1218
- throw new InvalidArgumentError2({
1219
- argument: "resolution",
1220
- message: "Topaz needs the output resolution. Set the `resolution` call option or the `output.width` / `output.height` provider options."
1221
- });
1222
- }
1223
- const frameRate = (_i = (_g = output == null ? void 0 : output.frameRate) != null ? _g : options.fps) != null ? _i : (_h = source.metadata) == null ? void 0 : _h.frameRate;
1224
- const audioTransfer = (_j = output == null ? void 0 : output.audioTransfer) != null ? _j : "Copy";
1225
- return {
1226
- resolution: { width, height },
1227
- ...frameRate != null ? { frameRate } : {},
1228
- audioTransfer,
1229
- ...audioTransfer !== "None" ? { audioCodec: (_k = output == null ? void 0 : output.audioCodec) != null ? _k : "AAC" } : (output == null ? void 0 : output.audioCodec) != null ? { audioCodec: output.audioCodec } : {},
1230
- ...(output == null ? void 0 : output.audioBitrate) != null ? { audioBitrate: output.audioBitrate } : {},
1231
- ...(output == null ? void 0 : output.videoEncoder) != null ? { videoEncoder: output.videoEncoder } : {},
1232
- ...(output == null ? void 0 : output.videoProfile) != null ? { videoProfile: output.videoProfile } : {},
1233
- ...(output == null ? void 0 : output.videoBitrate) != null ? { videoBitrate: output.videoBitrate } : {},
1234
- ...(output == null ? void 0 : output.dynamicCompressionLevel) != null ? { dynamicCompressionLevel: output.dynamicCompressionLevel } : {},
1235
- ...(output == null ? void 0 : output.cropToFit) != null ? { cropToFit: output.cropToFit } : {},
1236
- container: resolveOutputContainer(output, source.container)
1237
- };
1025
+ function buildOutput({ options, topazOptions, source }) {
1026
+ const output = topazOptions?.output;
1027
+ const resolution = parseResolution(options.resolution);
1028
+ const width = output?.width ?? resolution.width ?? source.metadata?.width;
1029
+ const height = output?.height ?? resolution.height ?? source.metadata?.height;
1030
+ if (width == null || height == null) throw new InvalidArgumentError({
1031
+ argument: "resolution",
1032
+ message: "Topaz needs the output resolution. Set the `resolution` call option or the `output.width` / `output.height` provider options."
1033
+ });
1034
+ const frameRate = output?.frameRate ?? options.fps ?? source.metadata?.frameRate;
1035
+ const audioTransfer = output?.audioTransfer ?? "Copy";
1036
+ return {
1037
+ resolution: {
1038
+ width,
1039
+ height
1040
+ },
1041
+ ...frameRate != null ? { frameRate } : {},
1042
+ audioTransfer,
1043
+ ...audioTransfer !== "None" ? { audioCodec: output?.audioCodec ?? "AAC" } : output?.audioCodec != null ? { audioCodec: output.audioCodec } : {},
1044
+ ...output?.audioBitrate != null ? { audioBitrate: output.audioBitrate } : {},
1045
+ ...output?.videoEncoder != null ? { videoEncoder: output.videoEncoder } : {},
1046
+ ...output?.videoProfile != null ? { videoProfile: output.videoProfile } : {},
1047
+ ...output?.videoBitrate != null ? { videoBitrate: output.videoBitrate } : {},
1048
+ ...output?.dynamicCompressionLevel != null ? { dynamicCompressionLevel: output.dynamicCompressionLevel } : {},
1049
+ ...output?.cropToFit != null ? { cropToFit: output.cropToFit } : {},
1050
+ container: resolveOutputContainer(output, source.container)
1051
+ };
1238
1052
  }
1053
+ /**
1054
+ * Mirrors Topaz's container rules so `doStatus` can report the media type of
1055
+ * the file Topaz will actually produce.
1056
+ */
1239
1057
  function resolveOutputContainer(output, sourceContainer) {
1240
- switch (output == null ? void 0 : output.videoEncoder) {
1241
- case "ProRes":
1242
- return "mov";
1243
- case "AV1":
1244
- case "VP9":
1245
- return "mp4";
1246
- }
1247
- if ((output == null ? void 0 : output.container) != null) {
1248
- return output.container;
1249
- }
1250
- return sourceContainer === "mov" || sourceContainer === "mkv" ? sourceContainer : "mp4";
1058
+ switch (output?.videoEncoder) {
1059
+ case "ProRes": return "mov";
1060
+ case "AV1":
1061
+ case "VP9": return "mp4";
1062
+ }
1063
+ if (output?.container != null) return output.container;
1064
+ return sourceContainer === "mov" || sourceContainer === "mkv" ? sourceContainer : "mp4";
1251
1065
  }
1252
1066
  function requireRequestId(requestId) {
1253
- if (requestId == null) {
1254
- throw new InvalidResponseDataError2({
1255
- data: requestId,
1256
- message: "Topaz did not return a requestId for the video request."
1257
- });
1258
- }
1259
- return requestId;
1067
+ if (requestId == null) throw new InvalidResponseDataError({
1068
+ data: requestId,
1069
+ message: "Topaz did not return a requestId for the video request."
1070
+ });
1071
+ return requestId;
1260
1072
  }
1261
1073
  function buildFilter(modelId, topazOptions) {
1262
- var _a;
1263
- const filter = {
1264
- model: resolveTopazVideoApiModelId(modelId)
1265
- };
1266
- if (topazOptions != null) {
1267
- const nonFilterKeys = new Set(TOPAZ_NON_FILTER_OPTION_KEYS);
1268
- for (const [key, value] of Object.entries(topazOptions)) {
1269
- if (!nonFilterKeys.has(key) && value !== void 0) {
1270
- filter[key] = value;
1271
- }
1272
- }
1273
- Object.assign(filter, (_a = topazOptions.filter) != null ? _a : {});
1274
- }
1275
- return filter;
1074
+ const filter = { model: resolveTopazVideoApiModelId(modelId) };
1075
+ if (topazOptions != null) {
1076
+ const nonFilterKeys = new Set(TOPAZ_NON_FILTER_OPTION_KEYS);
1077
+ for (const [key, value] of Object.entries(topazOptions)) if (!nonFilterKeys.has(key) && value !== void 0) filter[key] = value;
1078
+ Object.assign(filter, topazOptions.filter ?? {});
1079
+ }
1080
+ return filter;
1276
1081
  }
1277
- var topazVideoEstimatesSchema = z5.object({
1278
- cost: z5.array(z5.number()).nullish(),
1279
- time: z5.array(z5.number()).nullish()
1082
+ const topazVideoEstimatesSchema = z.object({
1083
+ cost: z.array(z.number()).nullish(),
1084
+ time: z.array(z.number()).nullish()
1280
1085
  }).nullish();
1281
- var topazVideoExpressResponseSchema = z5.object({
1282
- requestId: z5.string().nullish(),
1283
- uploadUrls: z5.array(z5.string()).nullish(),
1284
- estimates: topazVideoEstimatesSchema
1086
+ const topazVideoExpressResponseSchema = z.object({
1087
+ requestId: z.string().nullish(),
1088
+ uploadUrls: z.array(z.string()).nullish(),
1089
+ estimates: topazVideoEstimatesSchema
1285
1090
  });
1286
- var topazVideoStatusResponseSchema = z5.object({
1287
- status: z5.string().nullish(),
1288
- message: z5.string().nullish(),
1289
- errorCode: z5.string().nullish(),
1290
- // Documented as a string, accepted as a number too.
1291
- outputSize: z5.union([z5.string(), z5.number()]).nullish(),
1292
- estimates: topazVideoEstimatesSchema,
1293
- download: z5.object({
1294
- url: z5.string().nullish(),
1295
- expiresAt: z5.union([z5.string(), z5.number()]).nullish()
1296
- }).nullish()
1091
+ const topazVideoStatusResponseSchema = z.object({
1092
+ status: z.string().nullish(),
1093
+ message: z.string().nullish(),
1094
+ errorCode: z.string().nullish(),
1095
+ outputSize: z.union([z.string(), z.number()]).nullish(),
1096
+ estimates: topazVideoEstimatesSchema,
1097
+ download: z.object({
1098
+ url: z.string().nullish(),
1099
+ expiresAt: z.union([z.string(), z.number()]).nullish()
1100
+ }).nullish()
1297
1101
  });
1298
-
1299
- // src/topaz-provider.ts
1300
- var defaultBaseURL = "https://api.topazlabs.com";
1102
+ //#endregion
1103
+ //#region src/topaz-provider.ts
1104
+ const defaultBaseURL = "https://api.topazlabs.com";
1105
+ /**
1106
+ * Create a Topaz Labs provider instance.
1107
+ */
1301
1108
  function createTopaz(options = {}) {
1302
- var _a, _b;
1303
- const baseURL = (_b = withoutTrailingSlash((_a = options.baseURL) != null ? _a : defaultBaseURL)) != null ? _b : defaultBaseURL;
1304
- const getHeaders = () => withUserAgentSuffix2(
1305
- {
1306
- "X-API-Key": loadApiKey({
1307
- apiKey: options.apiKey,
1308
- environmentVariableName: "TOPAZ_API_KEY",
1309
- description: "Topaz Labs"
1310
- }),
1311
- accept: "application/json",
1312
- ...options.headers
1313
- },
1314
- `ai-sdk-topaz/${VERSION}`
1315
- );
1316
- const createImageModel = (modelId) => new TopazImageModel(modelId, {
1317
- provider: "topaz.image",
1318
- baseURL,
1319
- headers: getHeaders,
1320
- fetch: options.fetch
1321
- });
1322
- const createVideoModel = (modelId) => new TopazVideoModel(modelId, {
1323
- provider: "topaz.video",
1324
- baseURL,
1325
- headers: getHeaders,
1326
- fetch: options.fetch
1327
- });
1328
- const noSuchModel = (modelId, modelType) => {
1329
- throw new NoSuchModelError({ modelId, modelType });
1330
- };
1331
- const provider = {
1332
- specificationVersion: "v3",
1333
- image: createImageModel,
1334
- imageModel: createImageModel,
1335
- video: createVideoModel,
1336
- videoModel: createVideoModel,
1337
- languageModel: (modelId) => noSuchModel(modelId, "languageModel"),
1338
- embeddingModel: (modelId) => noSuchModel(modelId, "embeddingModel")
1339
- };
1340
- return provider;
1109
+ const baseURL = withoutTrailingSlash(options.baseURL ?? defaultBaseURL) ?? defaultBaseURL;
1110
+ const getHeaders = () => withUserAgentSuffix({
1111
+ "X-API-Key": loadApiKey({
1112
+ apiKey: options.apiKey,
1113
+ environmentVariableName: "TOPAZ_API_KEY",
1114
+ description: "Topaz Labs"
1115
+ }),
1116
+ accept: "application/json",
1117
+ ...options.headers
1118
+ }, `ai-sdk-topaz/${VERSION}`);
1119
+ const createImageModel = (modelId) => new TopazImageModel(modelId, {
1120
+ provider: "topaz.image",
1121
+ baseURL,
1122
+ headers: getHeaders,
1123
+ fetch: options.fetch
1124
+ });
1125
+ const createVideoModel = (modelId) => new TopazVideoModel(modelId, {
1126
+ provider: "topaz.video",
1127
+ baseURL,
1128
+ headers: getHeaders,
1129
+ fetch: options.fetch
1130
+ });
1131
+ const noSuchModel = (modelId, modelType) => {
1132
+ throw new NoSuchModelError({
1133
+ modelId,
1134
+ modelType
1135
+ });
1136
+ };
1137
+ return {
1138
+ specificationVersion: "v4",
1139
+ image: createImageModel,
1140
+ imageModel: createImageModel,
1141
+ video: createVideoModel,
1142
+ videoModel: createVideoModel,
1143
+ languageModel: (modelId) => noSuchModel(modelId, "languageModel"),
1144
+ embeddingModel: (modelId) => noSuchModel(modelId, "embeddingModel")
1145
+ };
1341
1146
  }
1342
- var topaz = createTopaz();
1343
- export {
1344
- VERSION,
1345
- createTopaz,
1346
- topaz
1347
- };
1147
+ /**
1148
+ * Default Topaz Labs provider instance.
1149
+ */
1150
+ const topaz = createTopaz();
1151
+ //#endregion
1152
+ export { VERSION, createTopaz, topaz };
1153
+
1348
1154
  //# sourceMappingURL=index.js.map