@transloadit/node 6.4.0 → 6.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/README.md +30 -5
  2. package/dist/Transloadit.d.ts.map +1 -1
  3. package/dist/Transloadit.js +61 -5
  4. package/dist/Transloadit.js.map +1 -1
  5. package/dist/alphalib/renderSimpleValue.d.ts +14 -0
  6. package/dist/alphalib/renderSimpleValue.d.ts.map +1 -0
  7. package/dist/alphalib/renderSimpleValue.js +87 -0
  8. package/dist/alphalib/renderSimpleValue.js.map +1 -0
  9. package/dist/alphalib/truncateString.d.ts +5 -0
  10. package/dist/alphalib/truncateString.d.ts.map +1 -0
  11. package/dist/alphalib/truncateString.js +19 -0
  12. package/dist/alphalib/truncateString.js.map +1 -0
  13. package/dist/alphalib/types/builtinTemplates.d.ts +15 -3
  14. package/dist/alphalib/types/builtinTemplates.d.ts.map +1 -1
  15. package/dist/alphalib/types/builtinTemplates.js +23 -3
  16. package/dist/alphalib/types/builtinTemplates.js.map +1 -1
  17. package/dist/alphalib/types/robots/_instructions-primitives.d.ts +6 -0
  18. package/dist/alphalib/types/robots/_instructions-primitives.d.ts.map +1 -1
  19. package/dist/alphalib/types/robots/_instructions-primitives.js +29 -7
  20. package/dist/alphalib/types/robots/_instructions-primitives.js.map +1 -1
  21. package/dist/alphalib/types/robots/_video-generate.d.ts +34 -0
  22. package/dist/alphalib/types/robots/_video-generate.d.ts.map +1 -0
  23. package/dist/alphalib/types/robots/_video-generate.js +157 -0
  24. package/dist/alphalib/types/robots/_video-generate.js.map +1 -0
  25. package/dist/alphalib/types/robots/ai-chat.d.ts +2 -0
  26. package/dist/alphalib/types/robots/ai-chat.d.ts.map +1 -1
  27. package/dist/alphalib/types/robots/ai-chat.js +7 -5
  28. package/dist/alphalib/types/robots/ai-chat.js.map +1 -1
  29. package/dist/alphalib/types/robots/audio-concat.d.ts.map +1 -1
  30. package/dist/alphalib/types/robots/audio-concat.js +5 -5
  31. package/dist/alphalib/types/robots/audio-concat.js.map +1 -1
  32. package/dist/alphalib/types/robots/azure-import.d.ts.map +1 -1
  33. package/dist/alphalib/types/robots/azure-import.js +3 -3
  34. package/dist/alphalib/types/robots/azure-import.js.map +1 -1
  35. package/dist/alphalib/types/robots/document-convert.d.ts.map +1 -1
  36. package/dist/alphalib/types/robots/document-convert.js +7 -5
  37. package/dist/alphalib/types/robots/document-convert.js.map +1 -1
  38. package/dist/alphalib/types/robots/document-extract.d.ts +8 -8
  39. package/dist/alphalib/types/robots/document-extract.d.ts.map +1 -1
  40. package/dist/alphalib/types/robots/document-extract.js +18 -2
  41. package/dist/alphalib/types/robots/document-extract.js.map +1 -1
  42. package/dist/alphalib/types/robots/document-split.d.ts +12 -0
  43. package/dist/alphalib/types/robots/document-split.d.ts.map +1 -1
  44. package/dist/alphalib/types/robots/document-split.js +15 -2
  45. package/dist/alphalib/types/robots/document-split.js.map +1 -1
  46. package/dist/alphalib/types/robots/file-decompress.d.ts.map +1 -1
  47. package/dist/alphalib/types/robots/file-decompress.js +2 -0
  48. package/dist/alphalib/types/robots/file-decompress.js.map +1 -1
  49. package/dist/alphalib/types/robots/file-preview.d.ts +22 -0
  50. package/dist/alphalib/types/robots/file-preview.d.ts.map +1 -1
  51. package/dist/alphalib/types/robots/file-preview.js +43 -26
  52. package/dist/alphalib/types/robots/file-preview.js.map +1 -1
  53. package/dist/alphalib/types/robots/http-request.d.ts.map +1 -1
  54. package/dist/alphalib/types/robots/http-request.js +9 -14
  55. package/dist/alphalib/types/robots/http-request.js.map +1 -1
  56. package/dist/alphalib/types/robots/image-enhance.d.ts +8 -8
  57. package/dist/alphalib/types/robots/image-enhance.js +2 -2
  58. package/dist/alphalib/types/robots/image-resize.d.ts.map +1 -1
  59. package/dist/alphalib/types/robots/image-resize.js +3 -1
  60. package/dist/alphalib/types/robots/image-resize.js.map +1 -1
  61. package/dist/alphalib/types/robots/image-upscale.js +1 -1
  62. package/dist/alphalib/types/robots/image-upscale.js.map +1 -1
  63. package/dist/alphalib/types/robots/s3-import.d.ts.map +1 -1
  64. package/dist/alphalib/types/robots/s3-import.js +14 -15
  65. package/dist/alphalib/types/robots/s3-import.js.map +1 -1
  66. package/dist/alphalib/types/robots/supabase-store.d.ts +16 -6
  67. package/dist/alphalib/types/robots/supabase-store.d.ts.map +1 -1
  68. package/dist/alphalib/types/robots/supabase-store.js +23 -1
  69. package/dist/alphalib/types/robots/supabase-store.js.map +1 -1
  70. package/dist/alphalib/types/robots/tigris-store.d.ts.map +1 -1
  71. package/dist/alphalib/types/robots/tigris-store.js +3 -4
  72. package/dist/alphalib/types/robots/tigris-store.js.map +1 -1
  73. package/dist/alphalib/types/robots/transloadit-import.d.ts +2 -2
  74. package/dist/alphalib/types/robots/transloadit-import.d.ts.map +1 -1
  75. package/dist/alphalib/types/robots/transloadit-import.js +4 -1
  76. package/dist/alphalib/types/robots/transloadit-import.js.map +1 -1
  77. package/dist/alphalib/types/robots/transloadit-store.d.ts +2 -3
  78. package/dist/alphalib/types/robots/transloadit-store.d.ts.map +1 -1
  79. package/dist/alphalib/types/robots/transloadit-store.js +19 -0
  80. package/dist/alphalib/types/robots/transloadit-store.js.map +1 -1
  81. package/dist/alphalib/types/robots/video-adaptive.d.ts.map +1 -1
  82. package/dist/alphalib/types/robots/video-adaptive.js +12 -0
  83. package/dist/alphalib/types/robots/video-adaptive.js.map +1 -1
  84. package/dist/alphalib/types/robots/video-concat.d.ts.map +1 -1
  85. package/dist/alphalib/types/robots/video-concat.js +9 -9
  86. package/dist/alphalib/types/robots/video-concat.js.map +1 -1
  87. package/dist/alphalib/types/robots/video-thumbs.d.ts +8 -8
  88. package/dist/alphalib/types/robots/video-thumbs.js +8 -8
  89. package/dist/alphalib/types/robots/video-thumbs.js.map +1 -1
  90. package/dist/alphalib/types/robots/vimeo-store.js +1 -1
  91. package/dist/alphalib/types/template.d.ts +3 -3
  92. package/dist/alphalib/types/templateValues.d.ts +83 -0
  93. package/dist/alphalib/types/templateValues.d.ts.map +1 -0
  94. package/dist/alphalib/types/templateValues.js +117 -0
  95. package/dist/alphalib/types/templateValues.js.map +1 -0
  96. package/dist/cli/OutputCtl.d.ts +1 -0
  97. package/dist/cli/OutputCtl.d.ts.map +1 -1
  98. package/dist/cli/OutputCtl.js +1 -1
  99. package/dist/cli/OutputCtl.js.map +1 -1
  100. package/dist/cli/commands/BaseCommand.d.ts +3 -2
  101. package/dist/cli/commands/BaseCommand.d.ts.map +1 -1
  102. package/dist/cli/commands/BaseCommand.js +27 -3
  103. package/dist/cli/commands/BaseCommand.js.map +1 -1
  104. package/dist/cli/commands/assemblies.d.ts +21 -1
  105. package/dist/cli/commands/assemblies.d.ts.map +1 -1
  106. package/dist/cli/commands/assemblies.js +584 -91
  107. package/dist/cli/commands/assemblies.js.map +1 -1
  108. package/dist/cli/commands/templates.d.ts +1 -1
  109. package/dist/cli/commands/templates.d.ts.map +1 -1
  110. package/dist/cli/commands/templates.js +57 -67
  111. package/dist/cli/commands/templates.js.map +1 -1
  112. package/dist/cli/errors.d.ts +8 -0
  113. package/dist/cli/errors.d.ts.map +1 -0
  114. package/dist/cli/errors.js +28 -0
  115. package/dist/cli/errors.js.map +1 -0
  116. package/dist/cli/helpers.d.ts.map +1 -1
  117. package/dist/cli/helpers.js +22 -0
  118. package/dist/cli/helpers.js.map +1 -1
  119. package/dist/cli/intentRuntime.d.ts +1 -0
  120. package/dist/cli/intentRuntime.d.ts.map +1 -1
  121. package/dist/cli/intentRuntime.js +9 -3
  122. package/dist/cli/intentRuntime.js.map +1 -1
  123. package/dist/cli/resultFiles.d.ts +1 -0
  124. package/dist/cli/resultFiles.d.ts.map +1 -1
  125. package/dist/cli/resultFiles.js.map +1 -1
  126. package/dist/cli/resultUrls.d.ts +2 -1
  127. package/dist/cli/resultUrls.d.ts.map +1 -1
  128. package/dist/cli/resultUrls.js +6 -3
  129. package/dist/cli/resultUrls.js.map +1 -1
  130. package/dist/cli/stepsInput.d.ts +1 -1
  131. package/dist/cli/stepsInput.d.ts.map +1 -1
  132. package/dist/cli/stepsInput.js +48 -6
  133. package/dist/cli/stepsInput.js.map +1 -1
  134. package/dist/cli/types.d.ts +66 -0
  135. package/dist/cli/types.d.ts.map +1 -1
  136. package/dist/cli/types.js +29 -0
  137. package/dist/cli/types.js.map +1 -1
  138. package/dist/tus.d.ts +7 -2
  139. package/dist/tus.d.ts.map +1 -1
  140. package/dist/tus.js +58 -4
  141. package/dist/tus.js.map +1 -1
  142. package/package.json +1 -1
  143. package/src/Transloadit.ts +65 -4
  144. package/src/alphalib/renderSimpleValue.ts +120 -0
  145. package/src/alphalib/truncateString.ts +23 -0
  146. package/src/alphalib/types/builtinTemplates.ts +33 -3
  147. package/src/alphalib/types/robots/_instructions-primitives.ts +34 -7
  148. package/src/alphalib/types/robots/_video-generate.ts +211 -0
  149. package/src/alphalib/types/robots/ai-chat.ts +8 -5
  150. package/src/alphalib/types/robots/audio-concat.ts +5 -4
  151. package/src/alphalib/types/robots/azure-import.ts +3 -4
  152. package/src/alphalib/types/robots/document-convert.ts +8 -5
  153. package/src/alphalib/types/robots/document-extract.ts +18 -2
  154. package/src/alphalib/types/robots/document-split.ts +17 -2
  155. package/src/alphalib/types/robots/file-decompress.ts +2 -0
  156. package/src/alphalib/types/robots/file-preview.ts +44 -25
  157. package/src/alphalib/types/robots/http-request.ts +9 -13
  158. package/src/alphalib/types/robots/image-enhance.ts +2 -2
  159. package/src/alphalib/types/robots/image-resize.ts +3 -1
  160. package/src/alphalib/types/robots/image-upscale.ts +1 -1
  161. package/src/alphalib/types/robots/s3-import.ts +14 -15
  162. package/src/alphalib/types/robots/supabase-store.ts +23 -1
  163. package/src/alphalib/types/robots/tigris-store.ts +2 -3
  164. package/src/alphalib/types/robots/transloadit-import.ts +4 -1
  165. package/src/alphalib/types/robots/transloadit-store.ts +19 -0
  166. package/src/alphalib/types/robots/video-adaptive.ts +12 -0
  167. package/src/alphalib/types/robots/video-concat.ts +9 -8
  168. package/src/alphalib/types/robots/video-thumbs.ts +8 -8
  169. package/src/alphalib/types/robots/vimeo-store.ts +1 -1
  170. package/src/alphalib/types/templateValues.ts +157 -0
  171. package/src/cli/OutputCtl.ts +2 -1
  172. package/src/cli/commands/BaseCommand.ts +29 -7
  173. package/src/cli/commands/assemblies.ts +775 -118
  174. package/src/cli/commands/templates.ts +93 -98
  175. package/src/cli/errors.ts +34 -0
  176. package/src/cli/helpers.ts +21 -0
  177. package/src/cli/intentRuntime.ts +10 -3
  178. package/src/cli/resultFiles.ts +1 -0
  179. package/src/cli/resultUrls.ts +11 -3
  180. package/src/cli/stepsInput.ts +54 -6
  181. package/src/cli/types.ts +35 -0
  182. package/src/tus.ts +72 -5
@@ -828,6 +828,22 @@ const deprecatedFfmpegVersions = formatEnglishList([
828
828
  ),
829
829
  ])
830
830
 
831
+ /** Common per-input concat fade behavior; Robot-specific transition controls stay owner-local. */
832
+ export function getConcatFadeDescription(media: 'audio' | 'video'): string {
833
+ const timing =
834
+ media === 'audio'
835
+ ? 'Audio fade-outs start on a whole-second boundary and may finish before the input ends.'
836
+ : 'Video fade durations are rounded down to whole frames and capped at the duration of each input.'
837
+
838
+ return `
839
+ When concatenating at least two files, a positive value fades out the ${media} of each input and fades in every input after the first. There is no fade-in at the beginning of the output, but the fade-out is also applied to the last input.
840
+
841
+ The value is a duration in seconds. For example, \`0.5\` means 500 milliseconds. Omit this parameter or set it to \`0\` to disable these per-input fades. They do not overlap clips or insert additional time between them.
842
+
843
+ ${timing}
844
+ `
845
+ }
846
+
831
847
  export const robotParameterDocs = {
832
848
  adaptive_filtering: {
833
849
  description: `
@@ -2075,6 +2091,12 @@ export const vimeoBase = z
2075
2091
  .optional()
2076
2092
  .describe(`
2077
2093
  Please create your associated <dfn>Template Credentials</dfn> in your Transloadit account and use the name of your <dfn>Template Credentials</dfn> as this parameter's value. They will contain the values for your Vimeo access token.
2094
+
2095
+ For /vimeo/import, create your own app at [developer.vimeo.com/apps](https://developer.vimeo.com/apps), open \`Generate Access Token\`, choose \`Authenticated (you)\`, and select \`Public\`, \`Private\`, and \`Video Files\` (\`public private video_files\`). Generate the personal access token, then choose **Vimeo** and **Paste a personal access token** in the Console. The token is stored as \`access_token\` and is never displayed back in the Console. Saving checks your Vimeo account and the token’s scopes. Video file links also require an eligible Vimeo account and video permissions.
2096
+
2097
+ Vimeo OAuth in the Console supports /vimeo/store. It cannot grant \`video_files\` for other members’ accounts because Transloadit is not a Vimeo partner. Imports require your own personal access token. To use that token for /vimeo/store too, also grant \`Upload\`, \`Edit\`, and \`Interact\` (\`upload edit interact\`) and ensure your Vimeo app has upload access.
2098
+
2099
+ Updating a credential does not run OAuth again or add permissions. To reconnect, **Remove** the credential (which revokes its token at Vimeo), then create a new credential with the same name. For a personal token, generate a new one before saving it again. Revocation also affects any other use of that token. See the [Vimeo credential instructions](/docs/topics/template-credentials/#vimeo).
2078
2100
  `),
2079
2101
  })
2080
2102
  .strict()
@@ -2172,6 +2194,9 @@ While we recommend to use <dfn>Template Credentials</dfn> at all times, some use
2172
2194
  })
2173
2195
  .strict()
2174
2196
 
2197
+ /** Default SSH/SFTP port when no explicit connection port is supplied. */
2198
+ export const sftpDefaultPort = 22
2199
+
2175
2200
  export type SftpBase = z.infer<typeof sftpBase>
2176
2201
  export const sftpBase = z
2177
2202
  .object({
@@ -2184,7 +2209,7 @@ Please create your associated <dfn>Template Credentials</dfn> in your Transloadi
2184
2209
  While we recommend to use <dfn>Template Credentials</dfn> at all times, some use cases demand dynamic credentials for which using <dfn>Template Credentials</dfn> is too unwieldy because of their static nature. If you have this requirement, feel free to use the following parameters instead: \`"host"\`, \`"port"\`, \`"user"\`, \`"public_key"\` (optional).
2185
2210
  `),
2186
2211
  host: z.string().optional(),
2187
- port: port.default(21).describe('The port to use for the FTP connection.'),
2212
+ port: port.default(sftpDefaultPort).describe('The port to use for the SFTP connection.'),
2188
2213
  user: z.string().optional(),
2189
2214
  public_key: z.string().optional(),
2190
2215
  })
@@ -2333,6 +2358,11 @@ export const filterConditionPartSchema = z.tuple([
2333
2358
  export type FilterCondition = z.infer<typeof filterCondition>
2334
2359
  export const filterCondition = z.union([z.null(), z.string(), z.array(filterConditionPartSchema)])
2335
2360
 
2361
+ /** English Turbo guidance shared by the public parameter and supporting video FAQ. */
2362
+ export const videoTurboParameterDocumentation = `Splits the video into multiple chunks so that each chunk can be encoded in parallel before all encoded chunks are stitched back together to form the result video. This comes at the expense of extra Priority Job Slots and may prove to be counter-productive for very small video files.
2363
+
2364
+ VP8 output and unresolved video-codec expressions use normal encoding even when \`turbo\` is \`true\`.`
2365
+
2336
2366
  /**
2337
2367
  * Parameters specific to the /video/encode robot. Useful for typing robots that pass files to /video/encode.
2338
2368
  */
@@ -2391,12 +2421,7 @@ Forces the video to be rotated by the specified degree integer. Currently, only
2391
2421
  .describe(`
2392
2422
  Enables hinting for mp4 files, for RTP/RTSP streaming.
2393
2423
  `),
2394
- turbo: z
2395
- .boolean()
2396
- .default(false)
2397
- .describe(`
2398
- Splits the video into multiple chunks so that each chunk can be encoded in parallel before all encoded chunks are stitched back together to form the result video. This comes at the expense of extra <dfn>Priority Job Slots</dfn> and may prove to be counter-productive for very small video files.
2399
- `),
2424
+ turbo: z.boolean().default(false).describe(videoTurboParameterDocumentation),
2400
2425
  chunk_duration: z
2401
2426
  .number()
2402
2427
  .int()
@@ -2404,6 +2429,8 @@ Splits the video into multiple chunks so that each chunk can be encoded in paral
2404
2429
  .optional()
2405
2430
  .describe(`
2406
2431
  Allows you to specify the duration of each chunk when \`turbo\` is set to \`true\`. This means you can take advantage of that feature while using fewer <dfn>Priority Job Slots</dfn>. For instance, the longer each chunk is, the fewer <dfn>Encoding Jobs</dfn> will need to be used.
2432
+
2433
+ Requested durations below 5 seconds are treated as 5 seconds.
2407
2434
  `),
2408
2435
  watermark_url: z
2409
2436
  .union([z.literal(''), httpUrlSchema])
@@ -0,0 +1,211 @@
1
+ import type { RobotVideoGenerateInstructions } from './video-generate.ts'
2
+
3
+ import { videoGenerateDefaultModelIdentifier } from './_ai-models.ts'
4
+
5
+ // Keep the schema wording portable without exposing the server's provider policy to SDKs.
6
+ export { videoGenerationParameterEnforcementDate } from './_video-generate-description.ts'
7
+
8
+ const optionalVideoParameters = [
9
+ 'seed',
10
+ 'aspect_ratio',
11
+ 'height',
12
+ 'width',
13
+ 'style',
14
+ 'duration',
15
+ 'fps',
16
+ 'motion_amount',
17
+ 'camera_motion',
18
+ 'negative_prompt',
19
+ 'reference_strength',
20
+ ] satisfies (keyof RobotVideoGenerateInstructions)[]
21
+
22
+ const ltxAspectRatioError = 'aspect_ratio must be a nonempty string for lightricks/ltx-video.'
23
+
24
+ /** Stable code included in Assembly warning text and structured server logs. */
25
+ export const videoGenerationParameterWarningCode = 'VIDEO_GENERATE_PARAMETER_IGNORED'
26
+
27
+ /** Flip this one flag on the enforcement date; both policy paths have regression coverage. */
28
+ export const videoGenerationParameterValidation = { enforceUnsupportedParameters: false }
29
+
30
+ type VideoGenerationParameters = Pick<
31
+ RobotVideoGenerateInstructions,
32
+ 'model' | 'prompt' | (typeof optionalVideoParameters)[number]
33
+ >
34
+
35
+ interface VideoGenerationModel {
36
+ replicateModel: `${string}/${string}`
37
+ parameters: readonly (typeof optionalVideoParameters)[number][]
38
+ imageInput?: string
39
+ imageMimeTypes?: readonly string[]
40
+ requiresImage?: boolean
41
+ }
42
+
43
+ // https://platform.minimax.io/docs/api-reference/video-generation-i2v#body-first-frame-image
44
+ const minimaxImageMimeTypes = ['image/jpeg', 'image/png', 'image/webp']
45
+
46
+ /** Verified Replicate video capabilities; model schemas do not accept a generic video input. */
47
+ // Unavailable luma/ray and image-only luma/photon stay excluded in both modes. The transition
48
+ // applies to ignored settings on supported video models, preserving the other validation fixes.
49
+ export const videoGenerationModels: Record<string, VideoGenerationModel> = {
50
+ // https://replicate.com/minimax/video-01/api/schema: fixed six-second output, no duration input.
51
+ 'minimax/video-01': {
52
+ replicateModel: 'minimax/video-01',
53
+ parameters: [],
54
+ imageInput: 'first_frame_image',
55
+ imageMimeTypes: minimaxImageMimeTypes,
56
+ },
57
+ 'minimax/video-01-live': {
58
+ replicateModel: 'minimax/video-01-live',
59
+ parameters: [],
60
+ imageInput: 'first_frame_image',
61
+ imageMimeTypes: minimaxImageMimeTypes,
62
+ requiresImage: true,
63
+ },
64
+ // https://replicate.com/tencent/hunyuan-video/api/schema: length is specified in frames.
65
+ 'tencent/hunyuan-video': {
66
+ replicateModel: 'tencent/hunyuan-video',
67
+ parameters: ['seed', 'height', 'width', 'fps'],
68
+ },
69
+ // https://replicate.com/lightricks/ltx-video/api/schema: length is specified in frames.
70
+ 'lightricks/ltx-video': {
71
+ replicateModel: 'lightricks/ltx-video',
72
+ parameters: ['seed', 'aspect_ratio', 'negative_prompt'],
73
+ imageInput: 'image',
74
+ },
75
+ }
76
+
77
+ /** Looks up only configured model names, excluding inherited object properties. */
78
+ export function getVideoGenerationModel(model: string): VideoGenerationModel | undefined {
79
+ return Object.hasOwn(videoGenerationModels, model) ? videoGenerationModels[model] : undefined
80
+ }
81
+
82
+ /** Identifies known unsupported frame formats without rejecting legacy CLI inputs lacking MIME. */
83
+ export function getVideoGenerationImageFormatError(
84
+ modelName: string,
85
+ mime: string | null | undefined,
86
+ ): string | undefined {
87
+ const supported = getVideoGenerationModel(modelName)?.imageMimeTypes
88
+ if (!mime || !supported || supported.includes(mime)) return undefined
89
+ return `Model ${modelName} supports only JPEG, PNG, or WebP input images. Convert the image before using it.`
90
+ }
91
+
92
+ /** Shares safe value constraints between migration warnings and later enforcement errors. */
93
+ export function getPreviouslyOmittedVideoGenerationValueError(
94
+ instructions: VideoGenerationParameters,
95
+ parameter: (typeof optionalVideoParameters)[number],
96
+ hasImage: boolean,
97
+ ): string | undefined {
98
+ if (instructions.model === 'tencent/hunyuan-video') {
99
+ if (
100
+ parameter === 'fps' &&
101
+ instructions.fps != null &&
102
+ (!Number.isInteger(instructions.fps) || instructions.fps < 1)
103
+ ) {
104
+ return 'fps must be a positive integer for tencent/hunyuan-video.'
105
+ }
106
+ if (parameter === 'height' || parameter === 'width') {
107
+ const value = instructions[parameter]
108
+ if (value != null && (value < 16 || value > 1280 || value % 16 !== 0)) {
109
+ return `${parameter} must be a multiple of 16 between 16 and 1280 for tencent/hunyuan-video.`
110
+ }
111
+ }
112
+ }
113
+ if (
114
+ instructions.model === 'lightricks/ltx-video' &&
115
+ parameter === 'aspect_ratio' &&
116
+ !hasImage &&
117
+ instructions.aspect_ratio === ''
118
+ ) {
119
+ return ltxAspectRatioError
120
+ }
121
+ return undefined
122
+ }
123
+
124
+ /** Enumerates ignored scalar settings without including customer-supplied values. */
125
+ export function getUnsupportedVideoGenerationParameters(
126
+ instructions: VideoGenerationParameters,
127
+ hasImage = false,
128
+ ): (typeof optionalVideoParameters)[number][] {
129
+ const model = getVideoGenerationModel(instructions.model ?? videoGenerateDefaultModelIdentifier)
130
+ if (!model) return []
131
+ return optionalVideoParameters.filter(
132
+ (parameter) =>
133
+ instructions[parameter] != null &&
134
+ (!model.parameters.includes(parameter) ||
135
+ (hasImage && parameter === 'aspect_ratio') ||
136
+ getPreviouslyOmittedVideoGenerationValueError(instructions, parameter, hasImage) != null),
137
+ )
138
+ }
139
+
140
+ /** Enforces model constraints and, after the transition, rejects ignored scalar settings. */
141
+ export function getVideoGenerationParameterError(
142
+ instructions: VideoGenerationParameters,
143
+ hasImage = false,
144
+ ): string | undefined {
145
+ const modelName = instructions.model ?? videoGenerateDefaultModelIdentifier
146
+ const model = getVideoGenerationModel(modelName)
147
+ if (!model) return `Model ${modelName} is not supported for video generation.`
148
+ const unsupported = getUnsupportedVideoGenerationParameters(instructions, hasImage)
149
+ if (videoGenerationParameterValidation.enforceUnsupportedParameters && unsupported.length > 0) {
150
+ const parameter = unsupported[0]
151
+ const valueError = getPreviouslyOmittedVideoGenerationValueError(
152
+ instructions,
153
+ parameter,
154
+ hasImage,
155
+ )
156
+ if (valueError) return valueError
157
+ const imageConstraint =
158
+ hasImage && parameter === 'aspect_ratio' && model.parameters.includes(parameter)
159
+ ? ' with an input image'
160
+ : ''
161
+ return `Model ${modelName} does not support ${parameter}${imageConstraint}. Omit this parameter or select a model that supports it.`
162
+ }
163
+ // Whitespace was forwarded before this change, unlike the previously omitted empty string.
164
+ if (
165
+ modelName === 'lightricks/ltx-video' &&
166
+ !hasImage &&
167
+ instructions.aspect_ratio != null &&
168
+ instructions.aspect_ratio !== '' &&
169
+ instructions.aspect_ratio.trim() === ''
170
+ ) {
171
+ return ltxAspectRatioError
172
+ }
173
+ return undefined
174
+ }
175
+
176
+ /** Requires necessary images immediately; unsupported inputs share the compatibility window. */
177
+ export function getVideoGenerationImageError(
178
+ instructions: Pick<VideoGenerationParameters, 'model' | 'aspect_ratio'>,
179
+ hasImage: boolean,
180
+ ): string | undefined {
181
+ const modelName = instructions.model ?? videoGenerateDefaultModelIdentifier
182
+ const model = getVideoGenerationModel(modelName)
183
+ if (!model) return `Model ${modelName} is not supported for video generation.`
184
+ if (model.requiresImage && !hasImage) return `Model ${modelName} requires an input image.`
185
+ if (
186
+ videoGenerationParameterValidation.enforceUnsupportedParameters &&
187
+ hasImage &&
188
+ !model.imageInput
189
+ ) {
190
+ return `Model ${modelName} does not support an input image.`
191
+ }
192
+ return undefined
193
+ }
194
+
195
+ /** Builds only model-supported provider fields, preserving explicit zero values. */
196
+ export function buildVideoGenerationInput(
197
+ instructions: VideoGenerationParameters,
198
+ hasImage = false,
199
+ ): Record<string, unknown> {
200
+ const error = getVideoGenerationParameterError(instructions, hasImage)
201
+ if (error) throw new Error(error)
202
+ const model = getVideoGenerationModel(instructions.model ?? videoGenerateDefaultModelIdentifier)
203
+ if (!model) throw new Error('Missing video generation model configuration.')
204
+ const unsupported = getUnsupportedVideoGenerationParameters(instructions, hasImage)
205
+ const input: Record<string, unknown> = { prompt: instructions.prompt }
206
+ for (const parameter of model.parameters) {
207
+ const value = instructions[parameter]
208
+ if (value != null && !unsupported.includes(parameter)) input[parameter] = value
209
+ }
210
+ return input
211
+ }
@@ -472,6 +472,9 @@ export const vendorModelSchema = z.enum([firstSupportedModel, ...remainingSuppor
472
472
 
473
473
  export type VendorModel = z.infer<typeof vendorModelSchema>
474
474
 
475
+ /** Reasoning effort used when the caller leaves the model on automatic selection. */
476
+ export const AI_CHAT_AUTO_REASONING_EFFORT = 'xhigh'
477
+
475
478
  export const robotAiChatInstructionsSchema = robotBase
476
479
  .merge(robotUse)
477
480
  .extend({
@@ -485,7 +488,7 @@ export const robotAiChatInstructionsSchema = robotBase
485
488
  .union([vendorModelSchema, z.literal('auto')])
486
489
  .default('auto')
487
490
  .describe(
488
- 'The model to use. Transloadit can pick the best model for the job if you set this to "auto".',
491
+ 'The model to use. The default, "auto", uses Transloadit\'s configured default model; it does not select a model based on the attached file. Choose a model that supports your input files.',
489
492
  ),
490
493
  format: z.enum(['json', 'text', 'meta']).default('json'),
491
494
  return_messages: z.enum(['all', 'last']).default('last'),
@@ -503,19 +506,19 @@ export const robotAiChatInstructionsSchema = robotBase
503
506
  .enum(['xhigh', 'high', 'medium', 'low'])
504
507
  .optional()
505
508
  .describe(
506
- 'Controls how much effort the model spends on reasoning. Higher values produce more thorough responses but cost more tokens. Applies to models that support extended thinking (OpenAI o-series, GPT-5.x, GPT-6, Anthropic Claude with thinking). If omitted, the model default is used.',
509
+ `Controls how much effort the model spends on reasoning. Higher values produce more thorough responses but cost more tokens. Applies to models that support extended thinking (OpenAI o-series, GPT-5.x, GPT-6, Anthropic Claude with thinking). If omitted with model: "auto" (the default), Transloadit uses "${AI_CHAT_AUTO_REASONING_EFFORT}". With an explicitly selected model, omitting this parameter leaves reasoning effort to that model's default.`,
507
510
  ),
508
511
  credentials: z
509
512
  .union([z.string(), z.array(z.string())])
510
513
  .optional()
511
514
  .describe(
512
- 'Names of template credentials to make available to the robot. When using your own AI provider keys, Transloadit charges a 30% markup (minimum $0.0005 per request).',
515
+ 'Names of Template Credentials to make available to the Robot. When using your own AI provider keys, you pay the provider directly and Transloadit charges a fee equal to 30% of the provider cost, with a minimum fee of $0.0005 per request.',
513
516
  ),
514
517
  test_credentials: z
515
518
  .boolean()
516
519
  .optional()
517
520
  .describe(
518
- 'Use Transloadit-provided credentials for testing. Usage is billed at provider cost plus a 30% markup (minimum $0.0005 per request).',
521
+ 'Use Transloadit-provided credentials for testing. Transloadit bills the provider cost plus a 30% markup, with a minimum markup of $0.0005 per request.',
519
522
  ),
520
523
  mcp_servers: z
521
524
  .array(
@@ -534,7 +537,7 @@ export const robotAiChatInstructionsSchema = robotBase
534
537
  )
535
538
  .optional()
536
539
  .describe(
537
- 'The MCP servers to use for tool calling. You can use any MCP server reachable from your environment. Use `headers` to pass server-specific auth (for example `Authorization: Bearer <token>`). For Transloadit\'s MCP server: Bearer tokens minted via `/token` satisfy Signature Authentication (signature checks apply only to key/secret requests). `auth: "transloadit"` is reserved for API2-managed auth to Transloadit-hosted MCP servers.',
540
+ 'The MCP servers to use for tool calling. The servers must be reachable from Transloadit\'s workers, not just from your own network. Use `headers` to pass server-specific auth (for example `Authorization: Bearer <token>`). For Transloadit\'s MCP server: Bearer tokens minted via `/token` satisfy Signature Authentication (signature checks apply only to key/secret requests). `auth: "transloadit"` is reserved for Transloadit-managed auth to Transloadit-hosted MCP servers.',
538
541
  ),
539
542
  })
540
543
  .strict()
@@ -5,6 +5,7 @@ import { z } from 'zod'
5
5
  import { stackVersions } from '../stackVersions.ts'
6
6
  import {
7
7
  bitrateSchema,
8
+ getConcatFadeDescription,
8
9
  inputSortBySchema,
9
10
  interpolateRobot,
10
11
  robotAudioEncodingMeta,
@@ -73,11 +74,11 @@ This Robot can concatenate an almost infinite number of audio files.
73
74
  .describe(robotParameterDocs.audio_sample_rate.description),
74
75
  audio_fade_seconds: z
75
76
  .number()
76
- .default(1)
77
+ .default(0)
77
78
  .describe(`
78
- When used this adds an audio fade in and out effect between each section of your concatenated audio file. The float value is used, so if you want an audio delay effect of 500 milliseconds between each video section, you would select 0.5. Integer values can also be represented.
79
+ ${getConcatFadeDescription('audio')}
79
80
 
80
- This parameter does not add an audio fade effect at the beginning or end of your result audio file. If you want to do so, create an additional [🤖/audio/encode](/docs/robots/audio-encode/) <dfn>Step</dfn> and use our \`ffmpeg\` parameter as shown in this [demo](/demos/audio-encoding/ffmpeg-fade-in-and-out/).
81
+ When \`crossfade\` is \`true\`, this parameter controls the overlap between inputs instead of these separate fades. To add a fade-in at the beginning of the output, use an additional [audio encoding Step](/docs/robots/audio-encode/) with the \`ffmpeg\` parameter, as shown in the [fade-in and fade-out demo](/demos/audio-encoding/ffmpeg-fade-in-and-out/).
81
82
  `),
82
83
  crossfade: z
83
84
  .boolean()
@@ -85,7 +86,7 @@ This parameter does not add an audio fade effect at the beginning or end of your
85
86
  .describe(`
86
87
  When set to \`true\`, this parameter enables crossfading between concatenated audio files using FFmpeg's \`acrossfade\` filter. This creates a smooth transition where the end of one audio file overlaps and blends with the beginning of the next file.
87
88
 
88
- The duration of the crossfade is controlled by the \`audio_fade_seconds\` parameter (defaults to 1 second if \`audio_fade_seconds\` is 0).
89
+ The \`audio_fade_seconds\` parameter controls the overlap duration. Omitting it or setting it to \`0\` uses a 1-second crossfade. Each overlap shortens the output by that duration; this mode does not add a fade-in at the beginning or a fade-out at the end.
89
90
 
90
91
  Note: This parameter requires at least 2 audio files to concatenate and only works with audio files, not video files.
91
92
  `),
@@ -22,8 +22,7 @@ import {
22
22
  export const meta: RobotMetaInput = {
23
23
  ...robotImportMeta,
24
24
  example_code: createStorageImportExample('/azure/import', 'YOUR_AZURE_CREDENTIALS', false),
25
- example_code_description:
26
- 'Import files from the `path/to/files` directory and its subdirectories:',
25
+ example_code_description: 'Import files from the `path/to/files` directory:',
27
26
  has_small_icon: true,
28
27
  purpose_sentence: 'imports whole directories of files from your Azure container',
29
28
  purpose_word: 'Azure',
@@ -41,14 +40,14 @@ export const robotAzureImportInstructionsSchema = robotBase
41
40
  path: path.describe(`
42
41
  The path in your container to the specific file or directory. If the path points to a file, only this file will be imported. For example: \`images/avatar.jpg\`.
43
42
 
44
- If it points to a directory, indicated by a trailing slash (\`/\`), then all files that are descendants of this directory are recursively imported. For example: \`images/\`.
43
+ If it points to a directory, indicated by a trailing slash (\`/\`), then files directly inside this directory are imported. For example: \`images/\`. Set \`recursive: true\` to include subdirectories.
45
44
 
46
45
  If you want to import all files from the root directory, please use \`/\` as the value here.
47
46
 
48
47
  You can also use an array of path strings here to import multiple paths in the same <dfn>Robot</dfn>'s <dfn>Step</dfn>.
49
48
  `),
50
49
  recursive: recursive.describe(`
51
- Setting this to \`true\` will enable importing files from subdirectories and sub-subdirectories (etc.) of the given path.
50
+ Setting this to \`true\` will enable importing files from subdirectories and sub-subdirectories (etc.) of the given path. By default, only files directly inside the given directory are imported.
52
51
  `),
53
52
  next_page_token: next_page_token.describe(`
54
53
  A string token used for pagination. The returned files of one paginated call have the next page token inside of their meta data, which needs to be used for the subsequent paging call.
@@ -38,6 +38,11 @@ A generic LibreOffice load refusal returns \`USER_COMMAND_ERROR\` with the reaso
38
38
  When diagnostics identify a problem in the conversion service, the load failure returns \`INTERNAL_COMMAND_ERROR\` with the reason “${documentConvertLoadInternalErrorReason}” Private diagnostics are not included in either response.
39
39
  `
40
40
 
41
+ const pdfAlternativesDocumentation = `
42
+ > [!Note]
43
+ > This <dfn>Robot</dfn> converts supported files to PDF, but does not convert PDF input to other formats. To render PDF pages as images, use [🤖/image/resize](/docs/robots/image-resize/). For native text extraction or OCR of PDF documents, use [🤖/document/extract](/docs/robots/document-extract/) and review its provider and input requirements. For OCR of images, use [🤖/image/ocr](/docs/robots/image-ocr/). These extraction Robots return recognized text; they do not add a searchable text layer to the original PDF.
44
+ `
45
+
41
46
  export const meta: RobotMetaInput = {
42
47
  ...robotDocumentProcessingMeta,
43
48
  example_code: createProcessingExample('converted', '/document/convert', {
@@ -45,14 +50,13 @@ export const meta: RobotMetaInput = {
45
50
  }),
46
51
  example_code_description: 'Convert uploaded files to PDF documents:',
47
52
  extended_description: `
48
- > [!Note]
49
- > This <dfn>Robot</dfn> can convert files to PDF, but cannot convert PDFs to different formats. If you want to convert PDFs to say, JPEG or TIFF, use [🤖/image/resize](/docs/robots/image-resize/). If you want to turn them into text files or recognize (OCR) them to make them searchable, reach out, as we have a new <dfn>Robot</dfn> in the works for this.
53
+ ${pdfAlternativesDocumentation}
50
54
 
51
55
  Sometimes, a certain file type might not support what you are trying to accomplish. Perhaps your company is trying to automate document formatting, but it only works with docx, so all your docs need to be converted. Or maybe your stored jpg files are taking up too much space and you want a lighter format. Whatever the case, we have you covered.
52
56
 
53
57
  Using this <dfn>Robot</dfn>, you can bypass the issues that certain file types may bring, by converting your file into the most suitable format. This also works in conjunction with our other <dfn>Robots</dfn>, allowing for even greater versatility when using our services.
54
58
 
55
- > ![Warning]
59
+ > [!Warning]
56
60
  > A general rule of this <dfn>Robot</dfn> is that converting files into an alien format category will result in an error. For example, SRT files can be converted into the VTT format, but not to an image.
57
61
 
58
62
  ${documentLoadErrorDocumentation}
@@ -122,8 +126,7 @@ export const robotDocumentConvertInstructionsSchema = robotBase
122
126
  robot: z.literal('/document/convert').describe(`
123
127
  ${htmlPdfReadinessDescription}
124
128
 
125
- > [!Note]
126
- > This Robot can convert files to PDF, but cannot convert PDFs to different formats. If you want to convert PDFs to say, JPEG or TIFF, use [🤖/image/resize](/docs/robots/image-resize/). If you want to turn them into text files or recognize (OCR) them to make them searchable, reach out, as we have a new Robot in the works for this.
129
+ ${pdfAlternativesDocumentation}
127
130
 
128
131
  Sometimes, a certain file type might not support what you are trying to accomplish. Perhaps your company is trying to automate document formatting, but it only works with docx, so all your docs need to be converted. Or maybe your stored jpg files are taking up too much space and you want a lighter format. Whatever the case, we have you covered.
129
132
 
@@ -19,7 +19,7 @@ import {
19
19
  const extractTargets = z.enum(['text', 'images'])
20
20
  const imageFormats = z.enum(['auto', 'original', 'png', 'jpg'])
21
21
  const ocrProviders = z.enum(['aws', 'gcp'])
22
- const textFormats = z.enum(['txt', 'json'])
22
+ const textFormats = z.enum(['txt', 'json', 'md', 'html'])
23
23
  const textGranularities = z.enum(['document', 'page'])
24
24
  const textMethods = z.enum(['native', 'ocr', 'auto'])
25
25
 
@@ -120,6 +120,10 @@ export const robotDocumentExtractInstructionsSchema = robotBase
120
120
  robot: z.literal('/document/extract').describe(`
121
121
  Extracts native/selectable text and embedded raster image assets from PDF documents.
122
122
 
123
+ For DOCX files, first use \`/document/convert\` with \`format: "pdf"\`, then pass its output to this Robot.
124
+
125
+ Markdown and HTML output are in beta and aim for readable structure, not a pixel-perfect copy. Native extraction detects paragraphs, larger-font headings, simple lists and aligned tables where possible. Multi-column text can interleave, prose can be mistaken for tables, and merged cells may need adjustment. HTML escapes extracted text and omits source scripts, links and event handlers.
126
+
123
127
  This robot does not render full pages. If you need page images, use \`/document/thumbs\`. Vector graphics, charts, and page backgrounds are not always embedded raster images and may therefore not be returned by this robot.
124
128
  `),
125
129
  extract: z
@@ -163,7 +167,19 @@ OCR modes currently cannot be combined with \`password\`.
163
167
  OCR provider to use when \`text_method\` is \`"ocr"\` or \`"auto"\`. Valid values are \`"aws"\` and \`"gcp"\`.
164
168
  `),
165
169
  text_format: textFormats.default('txt').describe(`
166
- Output format for extracted text. Use \`"txt"\` for plain text or \`"json"\` for structured output.
170
+ Output format for extracted text. Plain text and JSON retain the existing extraction behavior. Markdown and HTML output are in beta and infer basic structure from native text positions and font sizes.
171
+
172
+ Native Markdown and HTML extraction limits layout data to 8 MiB per input document, summed across its selected page ranges. For larger documents, use separate extraction Steps with fewer pages in each Step’s \`page_range\`.
173
+
174
+ If native extraction cannot confirm the reading order of bidirectional text, it returns a validation error. Try fewer pages or use \`text_method: "ocr"\` with an OCR provider.
175
+
176
+ When \`extract\` includes images, Markdown and HTML reference retained image results inline using relative, URL-encoded filenames matching each image result’s \`name\`. Save the text and image results together to resolve the references. Filtered images have no references; repeated images can reference the same deduplicated result. Images with unavailable or inconsistent positions, including retained image masks, are appended after their page’s text. Embedded base64 images are not produced.
177
+
178
+ When exporting, preserve result names in a shared directory per input document. For example, set \`/s3/store\`’s \`path\` to \`"\${assembly.id}/\${file.original_id}/\${file.name}"\`. Default storage paths using \`\${unique_prefix}/\${file.url_name}\` separate and rename files, so they do not preserve these relative image links.
179
+
180
+ Some native image encodings cannot be displayed by browsers or Markdown viewers. Set \`image_format\` to \`"png"\` or \`"jpg"\` for broadly viewable image results.
181
+
182
+ OCR Markdown and HTML preserve readable text with minimal structure detection and append extracted images in source order, since reliable text positions are unavailable.
167
183
  `),
168
184
  text_granularity: textGranularities.default('document').describe(`
169
185
  Controls text output grouping for native extraction.
@@ -6,6 +6,7 @@ import type {
6
6
 
7
7
  import { z } from 'zod'
8
8
 
9
+ import { expressionEvaluationUsageMebibytes } from '../templateValues.ts'
9
10
  import {
10
11
  createProcessingExample,
11
12
  defineRobot,
@@ -32,13 +33,27 @@ export const meta: RobotMetaInput = {
32
33
  export const robotDocumentSplitInstructionsSchema = robotBase
33
34
  .merge(robotUse)
34
35
  .extend({
35
- robot: z.literal('/document/split'),
36
+ robot: z.literal('/document/split').describe(`
37
+ By default, this Robot returns one PDF containing the selected pages and keeps the input file name. If \`pages\` is omitted, that PDF contains every page.
38
+
39
+ Set \`split: true\` to return separate PDFs. Without \`pages\`, each page becomes its own PDF. With \`pages\`, each entry becomes one PDF: \`pages: ["1", "2-3"]\` returns a one-page PDF and a two-page PDF. For an input named \`document.pdf\`, these files are named \`document_page_1.pdf\` and \`document_pages_2-3.pdf\`. Repeated selections receive an additional numeric suffix to keep file names unique, such as \`document_page_1_2.pdf\`.
40
+
41
+ Page numbers start at 1. When the page count is known, ranges are clamped to it and selections entirely beyond it are skipped. Split file names use the resulting page or range. An empty \`pages\` array is invalid in split mode; omit \`pages\` to split every page. If all selections exceed the page count, processing fails with a validation error.
42
+ `),
36
43
  pages: z
37
44
  .union([z.string(), z.array(z.string())])
38
45
  .describe(
39
- 'The pages to select from the input PDF and to be included in the output PDF. Each entry can be a single page number (e.g. 5), or a range (e.g. `5-10`). Page numbers start at 1. By default all pages are extracted.',
46
+ `The pages to select from the input PDF. Each entry can be a single page number (e.g. \`5\`) or a range (e.g. \`5-10\`). Page numbers start at 1. Accepts an array or a comma-separated string. By default, all selected pages are combined into one PDF; with \`split: true\`, each entry produces a separate PDF. If omitted, selects all pages.
47
+
48
+ For a PDF with at least three pages, \`pages: ["\${file.meta.page_count - 2}-\${file.meta.page_count}"]\` selects the last three pages. Each dynamically evaluated expression adds ${expressionEvaluationUsageMebibytes} MiB of usage per input file; simple variable references such as \`\${file.meta.page_count}\` are free.`,
40
49
  )
41
50
  .optional(),
51
+ split: z
52
+ .boolean()
53
+ .default(false)
54
+ .describe(
55
+ 'Return one PDF per `pages` entry, or one PDF per page when `pages` is omitted. File names include a page or range suffix, such as `document_page_1.pdf` or `document_pages_2-3.pdf`. Split selections and output PDFs are subject to the service’s per-Job file limit. By default, returns one PDF containing all selected pages and keeps the input file name.',
56
+ ),
42
57
  })
43
58
  .strict()
44
59
 
@@ -69,6 +69,8 @@ This <dfn>Robot</dfn> also detects and handles any of the following before evalu
69
69
 
70
70
  For security reasons, archives containing symlinks that point outside the archived directory will cause the <dfn>Assembly</dfn> to fail.
71
71
 
72
+ Extracted file and directory names are transliterated for supported accented Latin and Cyrillic characters. For example, \`café.pdf\` becomes \`cafe.pdf\`.
73
+
72
74
  Password-protected archives (ZIP with ZipCrypto or AES encryption, RAR encrypted, 7z with AES-256) are supported via the \`password\` parameter.
73
75
  `),
74
76
  ignore_errors: