@runpod/ai-sdk-provider 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  } from "@ai-sdk/openai-compatible";
6
6
  import {
7
7
  loadApiKey,
8
- withoutTrailingSlash as withoutTrailingSlash2
8
+ withoutTrailingSlash as withoutTrailingSlash4
9
9
  } from "@ai-sdk/provider-utils";
10
10
 
11
11
  // src/runpod-image-model.ts
@@ -26,28 +26,37 @@ var runpodImageErrorSchema = z.object({
26
26
  error: z.string().optional(),
27
27
  message: z.string().optional()
28
28
  });
29
- var runpodImageFailedResponseHandler = createJsonErrorResponseHandler({
30
- errorSchema: runpodImageErrorSchema,
31
- errorToMessage: (data) => {
32
- if (data.message) {
33
- return data.message;
34
- }
35
- if (data.error) {
36
- const lastBraceIndex = data.error.lastIndexOf("{");
37
- if (lastBraceIndex !== -1) {
38
- try {
39
- const jsonStr = data.error.substring(lastBraceIndex);
40
- const nestedError = JSON.parse(jsonStr);
41
- if (nestedError.message && typeof nestedError.message === "string") {
42
- return nestedError.message;
43
- }
44
- } catch {
29
+ function extractErrorMessage(data) {
30
+ if (data.message) {
31
+ return data.message;
32
+ }
33
+ if (data.error) {
34
+ const lastBraceIndex = data.error.lastIndexOf("{");
35
+ if (lastBraceIndex !== -1) {
36
+ try {
37
+ const jsonStr = data.error.substring(lastBraceIndex);
38
+ const nestedError = JSON.parse(jsonStr);
39
+ if (nestedError.message && typeof nestedError.message === "string") {
40
+ return nestedError.message;
45
41
  }
42
+ } catch {
46
43
  }
47
- return data.error;
48
44
  }
49
- return "Unknown Runpod error";
45
+ return data.error;
50
46
  }
47
+ return "Unknown Runpod error";
48
+ }
49
+ var runpodImageFailedResponseHandler = createJsonErrorResponseHandler({
50
+ errorSchema: runpodImageErrorSchema,
51
+ errorToMessage: extractErrorMessage
52
+ });
53
+ var runpodTranscriptionFailedResponseHandler = createJsonErrorResponseHandler({
54
+ errorSchema: runpodImageErrorSchema,
55
+ errorToMessage: extractErrorMessage
56
+ });
57
+ var runpodVideoFailedResponseHandler = createJsonErrorResponseHandler({
58
+ errorSchema: runpodImageErrorSchema,
59
+ errorToMessage: extractErrorMessage
51
60
  });
52
61
 
53
62
  // src/runpod-image-model.ts
@@ -71,6 +80,8 @@ var SUPPORTED_SIZES = /* @__PURE__ */ new Set([
71
80
  "512*512",
72
81
  "768*768",
73
82
  "1024*1024",
83
+ "1280*1280",
84
+ // wan-2.6 max
74
85
  "1536*1536",
75
86
  "2048*2048",
76
87
  "4096*4096",
@@ -79,6 +90,100 @@ var SUPPORTED_SIZES = /* @__PURE__ */ new Set([
79
90
  "1024*768",
80
91
  "768*1024"
81
92
  ]);
93
+ var Z_IMAGE_TURBO_SUPPORTED_SIZES = /* @__PURE__ */ new Set([
94
+ "1328*1328",
95
+ // 1:1
96
+ "1472*1140",
97
+ // 4:3
98
+ "1140*1472",
99
+ // 3:4
100
+ "512*512",
101
+ "768*768",
102
+ "1024*1024",
103
+ "1280*1280",
104
+ "1536*1536",
105
+ "512*768",
106
+ "768*512",
107
+ "1024*768",
108
+ "768*1024",
109
+ "768*432",
110
+ "1024*576",
111
+ "1280*720",
112
+ "1536*864",
113
+ "432*768",
114
+ "576*1024",
115
+ "720*1280",
116
+ "864*1536"
117
+ ]);
118
+ var Z_IMAGE_TURBO_ASPECT_RATIOS = {
119
+ "1:1": "1328*1328",
120
+ "4:3": "1472*1140",
121
+ "3:4": "1140*1472",
122
+ "3:2": "768*512",
123
+ "2:3": "512*768",
124
+ "16:9": "1280*720",
125
+ "9:16": "720*1280"
126
+ };
127
+ var MODEL_SUPPORTED_SIZES = {
128
+ "tongyi-mai/z-image-turbo": Z_IMAGE_TURBO_SUPPORTED_SIZES,
129
+ "z-image-turbo": Z_IMAGE_TURBO_SUPPORTED_SIZES
130
+ // alias, not advertised
131
+ };
132
+ var MODEL_SUPPORTED_ASPECT_RATIOS = {
133
+ "tongyi-mai/z-image-turbo": Z_IMAGE_TURBO_ASPECT_RATIOS,
134
+ "z-image-turbo": Z_IMAGE_TURBO_ASPECT_RATIOS
135
+ // alias, not advertised
136
+ };
137
+ var WAN_ASPECT_RATIOS = {
138
+ "1:1": "1280*1280",
139
+ // 1,638,400 pixels
140
+ "2:3": "800*1200",
141
+ // 960,000 pixels
142
+ "3:2": "1200*800",
143
+ // 960,000 pixels
144
+ "3:4": "960*1280",
145
+ // 1,228,800 pixels
146
+ "4:3": "1280*960",
147
+ // 1,228,800 pixels
148
+ "9:16": "720*1280",
149
+ // 921,600 pixels
150
+ "16:9": "1280*720",
151
+ // 921,600 pixels
152
+ "21:9": "1344*576",
153
+ // 774,144 pixels
154
+ "9:21": "576*1344"
155
+ // 774,144 pixels
156
+ };
157
+ var WAN_MIN_PIXELS = 768 * 768;
158
+ var WAN_MAX_PIXELS = 1280 * 1280;
159
+ var WAN_MIN_ASPECT_RATIO = 1 / 4;
160
+ var WAN_MAX_ASPECT_RATIO = 4 / 1;
161
+ function validateWanSize(size) {
162
+ const [widthStr, heightStr] = size.split("*");
163
+ const width = parseInt(widthStr, 10);
164
+ const height = parseInt(heightStr, 10);
165
+ if (isNaN(width) || isNaN(height)) {
166
+ throw new InvalidArgumentError({
167
+ argument: "size",
168
+ message: `Invalid size format: ${size}. Expected format: WIDTHxHEIGHT or WIDTH*HEIGHT`
169
+ });
170
+ }
171
+ const totalPixels = width * height;
172
+ const aspectRatio = width / height;
173
+ if (totalPixels < WAN_MIN_PIXELS || totalPixels > WAN_MAX_PIXELS) {
174
+ throw new InvalidArgumentError({
175
+ argument: "size",
176
+ message: `Size ${size} has ${totalPixels} pixels, which is outside the valid range for WAN 2.6 (${WAN_MIN_PIXELS} to ${WAN_MAX_PIXELS} pixels). Try 1280x1280 or 1024x1024.`
177
+ });
178
+ }
179
+ if (aspectRatio < WAN_MIN_ASPECT_RATIO || aspectRatio > WAN_MAX_ASPECT_RATIO) {
180
+ throw new InvalidArgumentError({
181
+ argument: "size",
182
+ message: `Size ${size} has aspect ratio ${aspectRatio.toFixed(2)}, which is outside the valid range for WAN 2.6 (1:4 to 4:1).`
183
+ });
184
+ }
185
+ return true;
186
+ }
82
187
  var RunpodImageModel = class {
83
188
  constructor(modelId, config) {
84
189
  this.modelId = modelId;
@@ -112,28 +217,49 @@ var RunpodImageModel = class {
112
217
  }
113
218
  const isPrunaModel = this.modelId.includes("pruna") || this.modelId.includes("p-image");
114
219
  const isNanoBananaProModel = this.modelId.includes("nano-banana-pro");
220
+ const isWanModel = this.modelId.includes("wan-2");
115
221
  let runpodSize;
116
222
  if (isPrunaModel || isNanoBananaProModel) {
117
223
  runpodSize = aspectRatio || "1:1";
224
+ } else if (isWanModel) {
225
+ if (size) {
226
+ const runpodSizeCandidate = size.replace("x", "*");
227
+ validateWanSize(runpodSizeCandidate);
228
+ runpodSize = runpodSizeCandidate;
229
+ } else if (aspectRatio) {
230
+ if (!WAN_ASPECT_RATIOS[aspectRatio]) {
231
+ throw new InvalidArgumentError({
232
+ argument: "aspectRatio",
233
+ message: `Aspect ratio ${aspectRatio} is not supported by WAN 2.6. Supported aspect ratios: ${Object.keys(WAN_ASPECT_RATIOS).join(", ")}`
234
+ });
235
+ }
236
+ runpodSize = WAN_ASPECT_RATIOS[aspectRatio];
237
+ } else {
238
+ runpodSize = "1280*1280";
239
+ }
118
240
  } else if (size) {
119
241
  const runpodSizeCandidate = size.replace("x", "*");
120
- if (!SUPPORTED_SIZES.has(runpodSizeCandidate)) {
242
+ const supportedSizes = MODEL_SUPPORTED_SIZES[this.modelId] ?? SUPPORTED_SIZES;
243
+ if (!supportedSizes.has(runpodSizeCandidate)) {
121
244
  throw new InvalidArgumentError({
122
245
  argument: "size",
123
246
  message: `Size ${size} is not supported by Runpod. Supported sizes: ${Array.from(
124
- SUPPORTED_SIZES
247
+ supportedSizes
125
248
  ).map((s) => s.replace("*", "x")).join(", ")}`
126
249
  });
127
250
  }
128
251
  runpodSize = runpodSizeCandidate;
129
252
  } else if (aspectRatio) {
130
- if (!SUPPORTED_ASPECT_RATIOS[aspectRatio]) {
253
+ const supportedAspectRatios = MODEL_SUPPORTED_ASPECT_RATIOS[this.modelId] ?? SUPPORTED_ASPECT_RATIOS;
254
+ if (!supportedAspectRatios[aspectRatio]) {
131
255
  throw new InvalidArgumentError({
132
256
  argument: "aspectRatio",
133
- message: `Aspect ratio ${aspectRatio} is not supported by Runpod. Supported aspect ratios: ${Object.keys(SUPPORTED_ASPECT_RATIOS).join(", ")}`
257
+ message: `Aspect ratio ${aspectRatio} is not supported by Runpod. Supported aspect ratios: ${Object.keys(
258
+ supportedAspectRatios
259
+ ).join(", ")}`
134
260
  });
135
261
  }
136
- runpodSize = SUPPORTED_ASPECT_RATIOS[aspectRatio];
262
+ runpodSize = supportedAspectRatios[aspectRatio];
137
263
  } else {
138
264
  runpodSize = "1328*1328";
139
265
  }
@@ -228,6 +354,10 @@ var RunpodImageModel = class {
228
354
  }
229
355
  }
230
356
  };
357
+ } else if (typedResponse.status === "FAILED") {
358
+ throw new Error(
359
+ `Image generation failed: ${typedResponse.error || "Unknown error"}`
360
+ );
231
361
  } else {
232
362
  throw new Error(`Unexpected response status: ${typedResponse.status}`);
233
363
  }
@@ -432,6 +562,25 @@ var RunpodImageModel = class {
432
562
  }
433
563
  return qwenEdit2511Payload;
434
564
  }
565
+ const isZImageTurbo = this.modelId === "tongyi-mai/z-image-turbo" || this.modelId === "z-image-turbo";
566
+ if (isZImageTurbo) {
567
+ const zImageTurboPayload = {
568
+ prompt,
569
+ size: runpodSize,
570
+ seed: seed ?? -1,
571
+ output_format: runpodOptions?.output_format ?? "png",
572
+ enable_safety_checker: runpodOptions?.enable_safety_checker ?? true,
573
+ ...runpodOptions
574
+ };
575
+ if (standardizedImages && standardizedImages.length > 0) {
576
+ if (standardizedImages.length === 1) {
577
+ zImageTurboPayload.image = standardizedImages[0];
578
+ } else {
579
+ zImageTurboPayload.images = standardizedImages;
580
+ }
581
+ }
582
+ return zImageTurboPayload;
583
+ }
435
584
  const isWanModel = this.modelId.includes("wan-2");
436
585
  if (isWanModel) {
437
586
  return {
@@ -471,8 +620,10 @@ var runpodImageResponseSchema = z2.object({
471
620
  // URL to the generated image (Qwen format)
472
621
  image_url: z2.string().optional()
473
622
  // URL to the generated image (Flux format)
474
- }).optional()
623
+ }).optional(),
475
624
  // Optional for IN_QUEUE/IN_PROGRESS responses
625
+ error: z2.string().optional()
626
+ // Error message if FAILED
476
627
  });
477
628
  var runpodImageStatusSchema = z2.object({
478
629
  id: z2.string(),
@@ -593,6 +744,11 @@ var RunpodSpeechModel = class {
593
744
  const message = parsed && typeof parsed.error === "string" && parsed.error || rawBodyText || `HTTP ${response.status}`;
594
745
  throw new Error(`Runpod speech request failed: ${message}`);
595
746
  }
747
+ if (parsed?.status === "FAILED") {
748
+ throw new Error(
749
+ `Speech generation failed: ${parsed.error || "Unknown error"}`
750
+ );
751
+ }
596
752
  const output = parsed?.output ?? parsed;
597
753
  const audioUrl = output?.audio_url;
598
754
  if (typeof audioUrl !== "string" || audioUrl.length === 0) {
@@ -628,6 +784,430 @@ var RunpodSpeechModel = class {
628
784
  }
629
785
  };
630
786
 
787
+ // src/runpod-transcription-model.ts
788
+ import {
789
+ withoutTrailingSlash as withoutTrailingSlash2,
790
+ createJsonResponseHandler as createJsonResponseHandler2,
791
+ getFromApi as getFromApi2,
792
+ postJsonToApi as postJsonToApi2
793
+ } from "@ai-sdk/provider-utils";
794
+ import { z as z3 } from "zod";
795
+ function isRecord2(value) {
796
+ return typeof value === "object" && value !== null;
797
+ }
798
+ var runpodJobResponseSchema = z3.object({
799
+ id: z3.string(),
800
+ status: z3.string().optional()
801
+ });
802
+ var runpodStatusResponseSchema = z3.object({
803
+ id: z3.string().optional(),
804
+ status: z3.string(),
805
+ output: z3.object({
806
+ text: z3.string().optional(),
807
+ result: z3.string().optional(),
808
+ // Some workers use 'result' instead of 'text'
809
+ segments: z3.array(
810
+ z3.object({
811
+ text: z3.string().optional(),
812
+ start: z3.number().optional(),
813
+ end: z3.number().optional()
814
+ })
815
+ ).optional(),
816
+ language: z3.string().optional(),
817
+ duration: z3.number().optional(),
818
+ cost: z3.number().optional()
819
+ }).optional(),
820
+ error: z3.string().optional()
821
+ });
822
+ var RunpodTranscriptionModel = class {
823
+ constructor(modelId, config) {
824
+ this.modelId = modelId;
825
+ this.config = config;
826
+ this.specificationVersion = "v3";
827
+ }
828
+ get provider() {
829
+ return this.config.provider;
830
+ }
831
+ getRunpodRunUrl() {
832
+ const baseURL = withoutTrailingSlash2(this.config.baseURL) ?? this.config.baseURL;
833
+ if (baseURL.endsWith("/run") || baseURL.endsWith("/runsync")) {
834
+ return baseURL;
835
+ }
836
+ return `${baseURL}/run`;
837
+ }
838
+ async doGenerate(options) {
839
+ const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
840
+ const warnings = [];
841
+ const { audio, providerOptions, abortSignal, headers } = options;
842
+ const runpodOptions = this.extractRunpodOptions(providerOptions);
843
+ const input = {};
844
+ if (runpodOptions.audio && typeof runpodOptions.audio === "string") {
845
+ input.audio = runpodOptions.audio;
846
+ } else {
847
+ const base64Audio = this.convertAudioToBase64(audio);
848
+ input.audio_base64 = base64Audio;
849
+ }
850
+ if (runpodOptions.prompt || runpodOptions.initial_prompt) {
851
+ input.initial_prompt = runpodOptions.prompt ?? runpodOptions.initial_prompt;
852
+ }
853
+ if (runpodOptions.language) {
854
+ input.language = runpodOptions.language;
855
+ }
856
+ if (runpodOptions.word_timestamps !== void 0) {
857
+ input.word_timestamps = runpodOptions.word_timestamps;
858
+ }
859
+ if (runpodOptions.model) {
860
+ input.model = runpodOptions.model;
861
+ }
862
+ if (runpodOptions.transcription) {
863
+ input.transcription = runpodOptions.transcription;
864
+ }
865
+ if (runpodOptions.translate !== void 0) {
866
+ input.translate = runpodOptions.translate;
867
+ }
868
+ if (runpodOptions.enable_vad !== void 0) {
869
+ input.enable_vad = runpodOptions.enable_vad;
870
+ }
871
+ const requestBody = { input };
872
+ const url = this.getRunpodRunUrl();
873
+ const effectiveBaseURL = withoutTrailingSlash2(this.config.baseURL) ?? this.config.baseURL;
874
+ const { value: response, responseHeaders } = await postJsonToApi2({
875
+ url,
876
+ headers: {
877
+ ...this.config.headers(),
878
+ ...headers
879
+ },
880
+ body: requestBody,
881
+ failedResponseHandler: runpodTranscriptionFailedResponseHandler,
882
+ successfulResponseHandler: createJsonResponseHandler2(
883
+ runpodJobResponseSchema
884
+ ),
885
+ abortSignal,
886
+ fetch: this.config.fetch
887
+ });
888
+ const typedResponse = response;
889
+ const jobId = typedResponse.id;
890
+ if (!jobId) {
891
+ throw new Error(
892
+ "Runpod transcription response did not include a job id."
893
+ );
894
+ }
895
+ const pollOptions = {
896
+ maxAttempts: runpodOptions.maxPollAttempts ?? 120,
897
+ pollIntervalMillis: runpodOptions.pollIntervalMillis ?? 2e3
898
+ };
899
+ const result = await this.pollForCompletion(
900
+ jobId,
901
+ abortSignal,
902
+ pollOptions,
903
+ effectiveBaseURL
904
+ );
905
+ const output = result.output;
906
+ const text = output?.text ?? output?.result ?? "";
907
+ const segments = this.parseSegments(output);
908
+ const language = output?.language;
909
+ const durationInSeconds = output?.duration;
910
+ const providerMetadata = {
911
+ runpod: {
912
+ jobId
913
+ }
914
+ };
915
+ return {
916
+ text,
917
+ segments,
918
+ language,
919
+ durationInSeconds,
920
+ warnings,
921
+ request: {
922
+ body: JSON.stringify(requestBody)
923
+ },
924
+ response: {
925
+ timestamp: currentDate,
926
+ modelId: this.modelId,
927
+ headers: responseHeaders,
928
+ body: JSON.stringify(result)
929
+ },
930
+ providerMetadata
931
+ };
932
+ }
933
+ convertAudioToBase64(audio) {
934
+ if (typeof audio === "string") {
935
+ return audio;
936
+ }
937
+ return this.uint8ArrayToBase64(audio);
938
+ }
939
+ uint8ArrayToBase64(data) {
940
+ if (typeof Buffer !== "undefined") {
941
+ return Buffer.from(data).toString("base64");
942
+ }
943
+ let binary = "";
944
+ for (let i = 0; i < data.length; i++) {
945
+ binary += String.fromCharCode(data[i]);
946
+ }
947
+ return btoa(binary);
948
+ }
949
+ extractRunpodOptions(providerOptions) {
950
+ if (!providerOptions) return {};
951
+ const runpod2 = providerOptions.runpod;
952
+ if (isRecord2(runpod2)) {
953
+ return runpod2;
954
+ }
955
+ return {};
956
+ }
957
+ async pollForCompletion(jobId, abortSignal, pollOptions, effectiveBaseURL) {
958
+ const maxAttempts = pollOptions?.maxAttempts ?? 120;
959
+ const pollInterval = pollOptions?.pollIntervalMillis ?? 2e3;
960
+ const baseURL = effectiveBaseURL ?? this.config.baseURL;
961
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
962
+ if (abortSignal?.aborted) {
963
+ throw new Error("Transcription was aborted");
964
+ }
965
+ const { value: statusResponse } = await getFromApi2({
966
+ url: `${baseURL}/status/${jobId}`,
967
+ headers: this.config.headers(),
968
+ successfulResponseHandler: createJsonResponseHandler2(
969
+ runpodStatusResponseSchema
970
+ ),
971
+ failedResponseHandler: runpodTranscriptionFailedResponseHandler,
972
+ abortSignal,
973
+ fetch: this.config.fetch
974
+ });
975
+ const typedStatusResponse = statusResponse;
976
+ if (typedStatusResponse.status === "COMPLETED") {
977
+ return typedStatusResponse;
978
+ }
979
+ if (typedStatusResponse.status === "FAILED") {
980
+ throw new Error(
981
+ `Transcription failed: ${typedStatusResponse.error || "Unknown error"}`
982
+ );
983
+ }
984
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
985
+ }
986
+ throw new Error(
987
+ `Transcription timed out after ${maxAttempts} attempts (${maxAttempts * pollInterval / 1e3}s)`
988
+ );
989
+ }
990
+ parseSegments(output) {
991
+ if (!output?.segments || !Array.isArray(output.segments)) {
992
+ return [];
993
+ }
994
+ return output.segments.map((segment) => ({
995
+ text: segment.text ?? "",
996
+ startSecond: segment.start ?? 0,
997
+ endSecond: segment.end ?? 0
998
+ }));
999
+ }
1000
+ };
1001
+
1002
+ // src/runpod-video-model.ts
1003
+ import {
1004
+ withoutTrailingSlash as withoutTrailingSlash3,
1005
+ createJsonResponseHandler as createJsonResponseHandler3,
1006
+ getFromApi as getFromApi3,
1007
+ postJsonToApi as postJsonToApi3
1008
+ } from "@ai-sdk/provider-utils";
1009
+ import { z as z4 } from "zod";
1010
+ function isRecord3(value) {
1011
+ return typeof value === "object" && value !== null;
1012
+ }
1013
+ var runpodVideoJobResponseSchema = z4.object({
1014
+ id: z4.string(),
1015
+ status: z4.string().optional()
1016
+ });
1017
+ var runpodVideoStatusSchema = z4.object({
1018
+ id: z4.string().optional(),
1019
+ status: z4.string(),
1020
+ output: z4.unknown().optional(),
1021
+ error: z4.string().optional()
1022
+ });
1023
+ var RunpodVideoModel = class {
1024
+ constructor(modelId, config) {
1025
+ this.modelId = modelId;
1026
+ this.config = config;
1027
+ this.specificationVersion = "v3";
1028
+ this.maxVideosPerCall = 1;
1029
+ }
1030
+ get provider() {
1031
+ return this.config.provider;
1032
+ }
1033
+ getRunpodRunUrl() {
1034
+ const baseURL = withoutTrailingSlash3(this.config.baseURL) ?? this.config.baseURL;
1035
+ if (baseURL.endsWith("/run") || baseURL.endsWith("/runsync")) {
1036
+ return baseURL;
1037
+ }
1038
+ return `${baseURL}/run`;
1039
+ }
1040
+ async doGenerate(options) {
1041
+ const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
1042
+ const warnings = [];
1043
+ if (options.n > 1) {
1044
+ warnings.push({
1045
+ type: "unsupported",
1046
+ feature: "n > 1",
1047
+ details: "Runpod video models only support generating 1 video per call. Only 1 video will be generated."
1048
+ });
1049
+ }
1050
+ const { providerOptions, abortSignal, headers } = options;
1051
+ const runpodOptions = this.extractRunpodOptions(providerOptions);
1052
+ const input = this.buildInputPayload(options, runpodOptions);
1053
+ const requestBody = { input };
1054
+ const url = this.getRunpodRunUrl();
1055
+ const effectiveBaseURL = withoutTrailingSlash3(this.config.baseURL) ?? this.config.baseURL;
1056
+ const { value: response, responseHeaders } = await postJsonToApi3({
1057
+ url,
1058
+ headers: {
1059
+ ...this.config.headers(),
1060
+ ...headers
1061
+ },
1062
+ body: requestBody,
1063
+ failedResponseHandler: runpodVideoFailedResponseHandler,
1064
+ successfulResponseHandler: createJsonResponseHandler3(
1065
+ runpodVideoJobResponseSchema
1066
+ ),
1067
+ abortSignal,
1068
+ fetch: this.config.fetch
1069
+ });
1070
+ const typedResponse = response;
1071
+ const jobId = typedResponse.id;
1072
+ if (!jobId) {
1073
+ throw new Error("Runpod video response did not include a job id.");
1074
+ }
1075
+ const pollOptions = {
1076
+ maxAttempts: runpodOptions.maxPollAttempts ?? 120,
1077
+ pollIntervalMillis: runpodOptions.pollIntervalMillis ?? 5e3
1078
+ };
1079
+ const result = await this.pollForCompletion(
1080
+ jobId,
1081
+ abortSignal,
1082
+ pollOptions,
1083
+ effectiveBaseURL
1084
+ );
1085
+ const videoUrl = this.extractVideoUrl(result.output);
1086
+ const providerMetadata = {
1087
+ runpod: {
1088
+ jobId
1089
+ }
1090
+ };
1091
+ return {
1092
+ videos: [{ type: "url", url: videoUrl, mediaType: "video/mp4" }],
1093
+ warnings,
1094
+ response: {
1095
+ timestamp: currentDate,
1096
+ modelId: this.modelId,
1097
+ headers: responseHeaders
1098
+ },
1099
+ providerMetadata
1100
+ };
1101
+ }
1102
+ buildInputPayload(options, runpodOptions) {
1103
+ const apiOptions = Object.fromEntries(
1104
+ Object.entries(runpodOptions).filter(
1105
+ ([key]) => key !== "maxPollAttempts" && key !== "pollIntervalMillis"
1106
+ )
1107
+ );
1108
+ const input = {
1109
+ ...apiOptions
1110
+ };
1111
+ if (options.prompt) {
1112
+ input.prompt = options.prompt;
1113
+ }
1114
+ if (options.duration !== void 0) {
1115
+ input.duration = options.duration;
1116
+ }
1117
+ if (options.fps !== void 0) {
1118
+ input.fps = options.fps;
1119
+ }
1120
+ if (options.seed !== void 0) {
1121
+ input.seed = options.seed;
1122
+ }
1123
+ if (options.resolution) {
1124
+ input.size = options.resolution.replace("x", "*");
1125
+ } else if (options.aspectRatio) {
1126
+ input.aspect_ratio = options.aspectRatio;
1127
+ }
1128
+ if (options.image) {
1129
+ input.image = this.convertFileToRunpodFormat(options.image);
1130
+ }
1131
+ return input;
1132
+ }
1133
+ convertFileToRunpodFormat(file) {
1134
+ if (file.type === "url") {
1135
+ return file.url;
1136
+ }
1137
+ const mediaType = file.mediaType;
1138
+ const data = file.data;
1139
+ if (typeof data === "string") {
1140
+ return `data:${mediaType};base64,${data}`;
1141
+ }
1142
+ const base64 = this.uint8ArrayToBase64(data);
1143
+ return `data:${mediaType};base64,${base64}`;
1144
+ }
1145
+ uint8ArrayToBase64(data) {
1146
+ if (typeof Buffer !== "undefined") {
1147
+ return Buffer.from(data).toString("base64");
1148
+ }
1149
+ let binary = "";
1150
+ for (let i = 0; i < data.length; i++) {
1151
+ binary += String.fromCharCode(data[i]);
1152
+ }
1153
+ return btoa(binary);
1154
+ }
1155
+ extractRunpodOptions(providerOptions) {
1156
+ if (!providerOptions) return {};
1157
+ const runpod2 = providerOptions.runpod;
1158
+ if (isRecord3(runpod2)) {
1159
+ return runpod2;
1160
+ }
1161
+ return {};
1162
+ }
1163
+ extractVideoUrl(output) {
1164
+ if (isRecord3(output)) {
1165
+ if (typeof output.video_url === "string") return output.video_url;
1166
+ if (typeof output.result === "string") return output.result;
1167
+ if (typeof output.url === "string") return output.url;
1168
+ }
1169
+ if (typeof output === "string") {
1170
+ return output;
1171
+ }
1172
+ throw new Error(
1173
+ `Runpod video generation completed but no video URL was found in the output: ${JSON.stringify(output)}`
1174
+ );
1175
+ }
1176
+ async pollForCompletion(jobId, abortSignal, pollOptions, effectiveBaseURL) {
1177
+ const maxAttempts = pollOptions?.maxAttempts ?? 120;
1178
+ const pollInterval = pollOptions?.pollIntervalMillis ?? 5e3;
1179
+ const baseURL = effectiveBaseURL ?? this.config.baseURL;
1180
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
1181
+ if (abortSignal?.aborted) {
1182
+ throw new Error("Video generation was aborted");
1183
+ }
1184
+ const { value: statusResponse } = await getFromApi3({
1185
+ url: `${baseURL}/status/${jobId}`,
1186
+ headers: this.config.headers(),
1187
+ successfulResponseHandler: createJsonResponseHandler3(
1188
+ runpodVideoStatusSchema
1189
+ ),
1190
+ failedResponseHandler: runpodVideoFailedResponseHandler,
1191
+ abortSignal,
1192
+ fetch: this.config.fetch
1193
+ });
1194
+ const typedStatusResponse = statusResponse;
1195
+ if (typedStatusResponse.status === "COMPLETED") {
1196
+ return typedStatusResponse;
1197
+ }
1198
+ if (typedStatusResponse.status === "FAILED") {
1199
+ throw new Error(
1200
+ `Video generation failed: ${typedStatusResponse.error || "Unknown error"}`
1201
+ );
1202
+ }
1203
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
1204
+ }
1205
+ throw new Error(
1206
+ `Video generation timed out after ${maxAttempts} attempts (${maxAttempts * pollInterval / 1e3}s)`
1207
+ );
1208
+ }
1209
+ };
1210
+
631
1211
  // src/runpod-provider.ts
632
1212
  var MODEL_ID_TO_ENDPOINT_URL = {
633
1213
  "qwen/qwen3-32b-awq": "https://api.runpod.ai/v2/qwen3-32b-awq/openai/v1",
@@ -651,6 +1231,10 @@ var IMAGE_MODEL_ID_TO_ENDPOINT_URL = {
651
1231
  "black-forest-labs/flux-1-dev": "https://api.runpod.ai/v2/black-forest-labs-flux-1-dev",
652
1232
  // Alibaba Wan 2.6 (t2i)
653
1233
  "alibaba/wan-2.6": "https://api.runpod.ai/v2/wan-2-6-t2i",
1234
+ // Tongyi Z-Image Turbo (t2i)
1235
+ "tongyi-mai/z-image-turbo": "https://api.runpod.ai/v2/z-image-turbo",
1236
+ "z-image-turbo": "https://api.runpod.ai/v2/z-image-turbo",
1237
+ // alias, not advertised
654
1238
  // Nano Banana (edit only)
655
1239
  "google/nano-banana-edit": "https://api.runpod.ai/v2/nano-banana-edit",
656
1240
  "nano-banana-edit": "https://api.runpod.ai/v2/nano-banana-edit",
@@ -664,6 +1248,26 @@ var IMAGE_MODEL_ID_TO_ENDPOINT_URL = {
664
1248
  var SPEECH_MODEL_ID_TO_ENDPOINT_URL = {
665
1249
  "resembleai/chatterbox-turbo": "https://api.runpod.ai/v2/chatterbox-turbo/"
666
1250
  };
1251
+ var TRANSCRIPTION_MODEL_ID_TO_ENDPOINT_URL = {
1252
+ "pruna/whisper-v3-large": "https://api.runpod.ai/v2/whisper-v3-large"
1253
+ };
1254
+ var VIDEO_MODEL_ID_TO_ENDPOINT_URL = {
1255
+ "pruna/p-video": "https://api.runpod.ai/v2/p-video",
1256
+ "vidu/q3-t2v": "https://api.runpod.ai/v2/vidu-q3-t2v",
1257
+ "vidu/q3-i2v": "https://api.runpod.ai/v2/vidu-q3-i2v",
1258
+ "kwaivgi/kling-v2.6-std-motion-control": "https://api.runpod.ai/v2/kling-v2-6-std-motion-control",
1259
+ "kwaivgi/kling-video-o1-r2v": "https://api.runpod.ai/v2/kling-video-o1-r2v",
1260
+ "kwaivgi/kling-v2.1-i2v-pro": "https://api.runpod.ai/v2/kling-v2-1-i2v-pro",
1261
+ "alibaba/wan-2.6-t2v": "https://api.runpod.ai/v2/wan-2-6-t2v",
1262
+ "alibaba/wan-2.6-i2v": "https://api.runpod.ai/v2/wan-2-6-i2v",
1263
+ "alibaba/wan-2.5": "https://api.runpod.ai/v2/wan-2-5",
1264
+ "alibaba/wan-2.2-t2v-720-lora": "https://api.runpod.ai/v2/wan-2-2-t2v-720-lora",
1265
+ "alibaba/wan-2.2-i2v-720": "https://api.runpod.ai/v2/wan-2-2-i2v-720",
1266
+ "alibaba/wan-2.1-i2v-720": "https://api.runpod.ai/v2/wan-2-1-i2v-720",
1267
+ "bytedance/seedance-v1.5-pro-i2v": "https://api.runpod.ai/v2/seedance-v1-5-pro-i2v",
1268
+ "openai/sora-2-pro-i2v": "https://api.runpod.ai/v2/sora-2-pro-i2v",
1269
+ "openai/sora-2-i2v": "https://api.runpod.ai/v2/sora-2-i2v"
1270
+ };
667
1271
  var MODEL_ID_TO_OPENAI_NAME = {
668
1272
  "qwen/qwen3-32b-awq": "Qwen/Qwen3-32B-AWQ",
669
1273
  "deepcogito/cogito-671b-v2.1-fp8": "deepcogito/cogito-671b-v2.1-FP8",
@@ -722,7 +1326,7 @@ function createRunpod(options = {}) {
722
1326
  }
723
1327
  return {
724
1328
  provider: `runpod.${modelType}`,
725
- url: ({ path }) => `${withoutTrailingSlash2(baseURL)}${path}`,
1329
+ url: ({ path }) => `${withoutTrailingSlash4(baseURL)}${path}`,
726
1330
  headers: getHeaders,
727
1331
  fetch: runpodFetch
728
1332
  };
@@ -767,6 +1371,30 @@ function createRunpod(options = {}) {
767
1371
  fetch: runpodFetch
768
1372
  });
769
1373
  };
1374
+ const createTranscriptionModel = (modelId) => {
1375
+ const endpointIdFromConsole = parseRunpodConsoleEndpointId(modelId);
1376
+ const normalizedModelId = endpointIdFromConsole ?? modelId;
1377
+ const mappedBaseURL = TRANSCRIPTION_MODEL_ID_TO_ENDPOINT_URL[normalizedModelId];
1378
+ const baseURL = mappedBaseURL ?? (normalizedModelId.startsWith("http") ? normalizedModelId : `https://api.runpod.ai/v2/${normalizedModelId}`);
1379
+ return new RunpodTranscriptionModel(normalizedModelId, {
1380
+ provider: "runpod.transcription",
1381
+ baseURL,
1382
+ headers: getHeaders,
1383
+ fetch: runpodFetch
1384
+ });
1385
+ };
1386
+ const createVideoModel = (modelId) => {
1387
+ const endpointIdFromConsole = parseRunpodConsoleEndpointId(modelId);
1388
+ const normalizedModelId = endpointIdFromConsole ?? modelId;
1389
+ const mappedBaseURL = VIDEO_MODEL_ID_TO_ENDPOINT_URL[normalizedModelId];
1390
+ const baseURL = mappedBaseURL ?? (normalizedModelId.startsWith("http") ? normalizedModelId : `https://api.runpod.ai/v2/${normalizedModelId}`);
1391
+ return new RunpodVideoModel(normalizedModelId, {
1392
+ provider: "runpod.video",
1393
+ baseURL,
1394
+ headers: getHeaders,
1395
+ fetch: options.fetch
1396
+ });
1397
+ };
770
1398
  const provider = (modelId) => createChatModel(modelId);
771
1399
  provider.completionModel = createCompletionModel;
772
1400
  provider.languageModel = createChatModel;
@@ -775,6 +1403,10 @@ function createRunpod(options = {}) {
775
1403
  provider.image = createImageModel;
776
1404
  provider.speechModel = createSpeechModel;
777
1405
  provider.speech = createSpeechModel;
1406
+ provider.transcriptionModel = createTranscriptionModel;
1407
+ provider.transcription = createTranscriptionModel;
1408
+ provider.videoModel = createVideoModel;
1409
+ provider.video = createVideoModel;
778
1410
  return provider;
779
1411
  }
780
1412
  var runpod = createRunpod();