transloadit 6.5.0 → 6.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/alphalib/renderSimpleValue.d.ts +14 -0
  2. package/dist/alphalib/renderSimpleValue.d.ts.map +1 -0
  3. package/dist/alphalib/renderSimpleValue.js +87 -0
  4. package/dist/alphalib/renderSimpleValue.js.map +1 -0
  5. package/dist/alphalib/truncateString.d.ts +5 -0
  6. package/dist/alphalib/truncateString.d.ts.map +1 -0
  7. package/dist/alphalib/truncateString.js +19 -0
  8. package/dist/alphalib/truncateString.js.map +1 -0
  9. package/dist/alphalib/types/robots/_instructions-primitives.d.ts +6 -0
  10. package/dist/alphalib/types/robots/_instructions-primitives.d.ts.map +1 -1
  11. package/dist/alphalib/types/robots/_instructions-primitives.js +29 -7
  12. package/dist/alphalib/types/robots/_instructions-primitives.js.map +1 -1
  13. package/dist/alphalib/types/robots/_video-generate.d.ts +34 -0
  14. package/dist/alphalib/types/robots/_video-generate.d.ts.map +1 -0
  15. package/dist/alphalib/types/robots/_video-generate.js +157 -0
  16. package/dist/alphalib/types/robots/_video-generate.js.map +1 -0
  17. package/dist/alphalib/types/robots/ai-chat.d.ts +2 -0
  18. package/dist/alphalib/types/robots/ai-chat.d.ts.map +1 -1
  19. package/dist/alphalib/types/robots/ai-chat.js +7 -5
  20. package/dist/alphalib/types/robots/ai-chat.js.map +1 -1
  21. package/dist/alphalib/types/robots/audio-concat.d.ts.map +1 -1
  22. package/dist/alphalib/types/robots/audio-concat.js +5 -5
  23. package/dist/alphalib/types/robots/audio-concat.js.map +1 -1
  24. package/dist/alphalib/types/robots/azure-import.d.ts.map +1 -1
  25. package/dist/alphalib/types/robots/azure-import.js +3 -3
  26. package/dist/alphalib/types/robots/azure-import.js.map +1 -1
  27. package/dist/alphalib/types/robots/document-convert.d.ts.map +1 -1
  28. package/dist/alphalib/types/robots/document-convert.js +7 -5
  29. package/dist/alphalib/types/robots/document-convert.js.map +1 -1
  30. package/dist/alphalib/types/robots/document-extract.d.ts +8 -8
  31. package/dist/alphalib/types/robots/document-extract.d.ts.map +1 -1
  32. package/dist/alphalib/types/robots/document-extract.js +18 -2
  33. package/dist/alphalib/types/robots/document-extract.js.map +1 -1
  34. package/dist/alphalib/types/robots/document-split.d.ts +12 -0
  35. package/dist/alphalib/types/robots/document-split.d.ts.map +1 -1
  36. package/dist/alphalib/types/robots/document-split.js +15 -2
  37. package/dist/alphalib/types/robots/document-split.js.map +1 -1
  38. package/dist/alphalib/types/robots/file-decompress.d.ts.map +1 -1
  39. package/dist/alphalib/types/robots/file-decompress.js +2 -0
  40. package/dist/alphalib/types/robots/file-decompress.js.map +1 -1
  41. package/dist/alphalib/types/robots/file-preview.d.ts +22 -0
  42. package/dist/alphalib/types/robots/file-preview.d.ts.map +1 -1
  43. package/dist/alphalib/types/robots/file-preview.js +43 -26
  44. package/dist/alphalib/types/robots/file-preview.js.map +1 -1
  45. package/dist/alphalib/types/robots/http-request.d.ts.map +1 -1
  46. package/dist/alphalib/types/robots/http-request.js +9 -14
  47. package/dist/alphalib/types/robots/http-request.js.map +1 -1
  48. package/dist/alphalib/types/robots/image-enhance.d.ts +8 -8
  49. package/dist/alphalib/types/robots/image-enhance.js +2 -2
  50. package/dist/alphalib/types/robots/image-resize.d.ts.map +1 -1
  51. package/dist/alphalib/types/robots/image-resize.js +3 -1
  52. package/dist/alphalib/types/robots/image-resize.js.map +1 -1
  53. package/dist/alphalib/types/robots/image-upscale.js +1 -1
  54. package/dist/alphalib/types/robots/image-upscale.js.map +1 -1
  55. package/dist/alphalib/types/robots/s3-import.d.ts.map +1 -1
  56. package/dist/alphalib/types/robots/s3-import.js +14 -15
  57. package/dist/alphalib/types/robots/s3-import.js.map +1 -1
  58. package/dist/alphalib/types/robots/supabase-store.d.ts +16 -6
  59. package/dist/alphalib/types/robots/supabase-store.d.ts.map +1 -1
  60. package/dist/alphalib/types/robots/supabase-store.js +23 -1
  61. package/dist/alphalib/types/robots/supabase-store.js.map +1 -1
  62. package/dist/alphalib/types/robots/tigris-store.d.ts.map +1 -1
  63. package/dist/alphalib/types/robots/tigris-store.js +3 -4
  64. package/dist/alphalib/types/robots/tigris-store.js.map +1 -1
  65. package/dist/alphalib/types/robots/transloadit-import.d.ts +2 -2
  66. package/dist/alphalib/types/robots/transloadit-import.d.ts.map +1 -1
  67. package/dist/alphalib/types/robots/transloadit-import.js +4 -1
  68. package/dist/alphalib/types/robots/transloadit-import.js.map +1 -1
  69. package/dist/alphalib/types/robots/transloadit-store.d.ts +2 -3
  70. package/dist/alphalib/types/robots/transloadit-store.d.ts.map +1 -1
  71. package/dist/alphalib/types/robots/transloadit-store.js +19 -0
  72. package/dist/alphalib/types/robots/transloadit-store.js.map +1 -1
  73. package/dist/alphalib/types/robots/video-adaptive.d.ts.map +1 -1
  74. package/dist/alphalib/types/robots/video-adaptive.js +12 -0
  75. package/dist/alphalib/types/robots/video-adaptive.js.map +1 -1
  76. package/dist/alphalib/types/robots/video-concat.d.ts.map +1 -1
  77. package/dist/alphalib/types/robots/video-concat.js +9 -9
  78. package/dist/alphalib/types/robots/video-concat.js.map +1 -1
  79. package/dist/alphalib/types/robots/video-thumbs.d.ts +8 -8
  80. package/dist/alphalib/types/robots/video-thumbs.js +8 -8
  81. package/dist/alphalib/types/robots/video-thumbs.js.map +1 -1
  82. package/dist/alphalib/types/robots/vimeo-store.js +1 -1
  83. package/dist/alphalib/types/template.d.ts +3 -3
  84. package/dist/alphalib/types/templateValues.d.ts +83 -0
  85. package/dist/alphalib/types/templateValues.d.ts.map +1 -0
  86. package/dist/alphalib/types/templateValues.js +117 -0
  87. package/dist/alphalib/types/templateValues.js.map +1 -0
  88. package/dist/cli/stepsInput.d.ts +1 -1
  89. package/dist/cli/stepsInput.d.ts.map +1 -1
  90. package/dist/cli/stepsInput.js +31 -2
  91. package/dist/cli/stepsInput.js.map +1 -1
  92. package/package.json +2 -2
  93. package/src/alphalib/renderSimpleValue.ts +120 -0
  94. package/src/alphalib/truncateString.ts +23 -0
  95. package/src/alphalib/types/robots/_instructions-primitives.ts +34 -7
  96. package/src/alphalib/types/robots/_video-generate.ts +211 -0
  97. package/src/alphalib/types/robots/ai-chat.ts +8 -5
  98. package/src/alphalib/types/robots/audio-concat.ts +5 -4
  99. package/src/alphalib/types/robots/azure-import.ts +3 -4
  100. package/src/alphalib/types/robots/document-convert.ts +8 -5
  101. package/src/alphalib/types/robots/document-extract.ts +18 -2
  102. package/src/alphalib/types/robots/document-split.ts +17 -2
  103. package/src/alphalib/types/robots/file-decompress.ts +2 -0
  104. package/src/alphalib/types/robots/file-preview.ts +44 -25
  105. package/src/alphalib/types/robots/http-request.ts +9 -13
  106. package/src/alphalib/types/robots/image-enhance.ts +2 -2
  107. package/src/alphalib/types/robots/image-resize.ts +3 -1
  108. package/src/alphalib/types/robots/image-upscale.ts +1 -1
  109. package/src/alphalib/types/robots/s3-import.ts +14 -15
  110. package/src/alphalib/types/robots/supabase-store.ts +23 -1
  111. package/src/alphalib/types/robots/tigris-store.ts +2 -3
  112. package/src/alphalib/types/robots/transloadit-import.ts +4 -1
  113. package/src/alphalib/types/robots/transloadit-store.ts +19 -0
  114. package/src/alphalib/types/robots/video-adaptive.ts +12 -0
  115. package/src/alphalib/types/robots/video-concat.ts +9 -8
  116. package/src/alphalib/types/robots/video-thumbs.ts +8 -8
  117. package/src/alphalib/types/robots/vimeo-store.ts +1 -1
  118. package/src/alphalib/types/templateValues.ts +157 -0
  119. package/src/cli/stepsInput.ts +35 -2
@@ -33,9 +33,7 @@ export const robotTigrisStoreInstructionsSchema = robotBase
33
33
  .merge(robotUse)
34
34
  .merge(tigrisBase)
35
35
  .extend({
36
- robot: z.literal('/tigris/store').describe(`
37
- The URL to the result file will be returned in the <dfn>Assembly Status JSON</dfn>.
38
- `),
36
+ robot: z.literal('/tigris/store'),
39
37
  path: storeFilePath,
40
38
  acl: storageAcl,
41
39
  headers: z
@@ -62,6 +60,7 @@ If this parameter is not used, no URL signing is done.
62
60
  .describe('The region of your Tigris bucket. This is optional as it can often be derived.'),
63
61
  })
64
62
  .strict()
63
+ .describe('The URL to the result file will be returned in the <dfn>Assembly Status JSON</dfn>.')
65
64
 
66
65
  export const robotDefinition: RobotDefinition<typeof robotTigrisStoreInstructionsSchema.shape> =
67
66
  defineRobot(meta, robotTigrisStoreInstructionsSchema)
@@ -40,12 +40,15 @@ Whether to import files from subfolders and sub-subfolders when \`path\` is a fo
40
40
  only the folder's own files are imported.
41
41
  `
42
42
 
43
- /** Storage imports require proof of possession independently of Workspace signature settings. */
43
+ /** Private Storage imports require proof of possession independently of Workspace settings. */
44
44
  export const transloaditImportAuthenticationGuidanceLines = [
45
45
  'Assemblies that use `/transloadit/import`, directly or through a Template, require',
46
46
  'either a bearer token or signed `params` with a future `params.auth.expires` timestamp.',
47
47
  'This applies even when Workspace Signature Authentication is disabled. An Auth Key alone',
48
48
  'is not sufficient; bearer-authenticated requests do not need a separate signature or expiry.',
49
+ 'Public Storage Builtin Templates are the exception: they can serve unsigned requests only',
50
+ 'when every Storage import remains within a declared public prefix. This does not authorize',
51
+ 'unsigned Assemblies with arbitrary Steps or access to private Storage paths.',
49
52
  ] as const
50
53
 
51
54
  export const meta: RobotMetaInput = {
@@ -64,6 +64,25 @@ existing asset. Always save the returned asset identity, version and final path.
64
64
  Sets the destination path inside your Transloadit Storage workspace, relative to its root: a
65
65
  filename, or folders and a filename such as \`website/hero.jpg\`. Folders that do not exist yet
66
66
  are created.
67
+
68
+ Repeated interior slashes are collapsed after variable interpolation. For example,
69
+ \`a/\${file.meta.relative_path}/\${file.name}\` stores a root-level HLS playlist as
70
+ \`a/playlist.m3u8\` when \`relative_path\` is empty. Leading or trailing slashes and
71
+ \`.\` or \`..\` segments remain invalid.
72
+ `),
73
+ result: z
74
+ .boolean()
75
+ .default(true)
76
+ .describe(`
77
+ Leave \`result\` unset or set it to \`true\` on this Storage Step. Setting \`result: false\`
78
+ here can omit stored entries, including asset and version IDs, from the Assembly Status JSON
79
+ even though the files are stored.
80
+
81
+ Stored results appear under their producing Step in the Assembly Status JSON, or under
82
+ \`:original\` for uploaded files, rather than under this Storage Step. Setting \`result: false\`
83
+ on a producing processing Step also omits its stored entries, including asset and version IDs,
84
+ from the Assembly Status JSON even though the files are stored. Leave \`result\` unset or set it
85
+ to \`true\` on that Step to receive those identities.
67
86
  `),
68
87
  })
69
88
  .strict()
@@ -71,6 +71,18 @@ This <dfn>Robot</dfn> accepts all types of video files and audio files. Do not f
71
71
 
72
72
  This <dfn>Robot</dfn> is normally used in combination with [🤖/video/encode](/docs/robots/video-encode/). We have implemented video and audio encoding presets specifically for MPEG-Dash and HTTP Live Streaming support. These presets are prefixed with \`"dash/"\` and \`"hls/"\`. [View a HTTP Live Streaming demo here](/demos/video-encoding/implement-http-live-streaming/).
73
73
 
74
+ ### HLS subtitles
75
+
76
+ With \`"technique": "hls"\`, the first embedded subtitle track of each input is converted to segmented WebVTT and linked from the master playlist. Additional subtitle tracks in the same input are dropped. The Assembly adds a warning when any input contains more than one subtitle track.
77
+
78
+ To add a track before packaging, use [🤖/video/subtitle](/docs/robots/video-subtitle/) with \`"subtitles_type": "external"\`, \`"preset": "empty"\`, and a \`language\` such as \`"eng"\`. Then encode with an \`"hls/"\` preset and pass the encoded video to this Robot. By default, the playlist’s subtitle \`NAME\` uses the language’s English name, for example “English”; its \`LANGUAGE\` preserves the input’s language tag. Unrecognized language codes are used as the name. If the language is missing or \`"und"\`, the name is “Unknown”. Custom \`ffmpeg.var_stream_map\` mappings control their own subtitle labels.
79
+
80
+ To set an explicit subtitle name in a custom \`ffmpeg.var_stream_map\`, select \`"ffmpeg_stack": "v8.0.0"\` and use its \`sname\` option.
81
+
82
+ WebVTT cues and MPEG-TS video use the same timestamp timeline. AAC priming or video frame reordering can move both after zero, so a small nonzero first cue timestamp does not by itself mean subtitles are delayed. Preserve the generated timestamps to keep subtitles synchronized with the video. When any input to an HLS Step has subtitles, \`ffmpeg.muxdelay\` defaults to \`0\` for the whole Step, including renditions without subtitles. Overriding it or other timestamp options can change that alignment.
83
+
84
+ Store the WebVTT segments and subtitle playlists along with all other results, preserving \`\${file.meta.relative_path}\` as described below.
85
+
74
86
  ### CMAF aspect ratios
75
87
 
76
88
  CMAF aligns near-equal rendition aspect ratios, within 1% of the largest rendition, without re-encoding or changing pixel dimensions. This lets presets such as \`"hls/480p"\` and \`"hls/720p"\` support bitrate switching together.
@@ -8,6 +8,7 @@ import { z } from 'zod'
8
8
 
9
9
  import {
10
10
  defineRobot,
11
+ getConcatFadeDescription,
11
12
  inputSortBySchema,
12
13
  robotBase,
13
14
  robotFFmpegVideo,
@@ -67,23 +68,23 @@ It’s possible to concatenate a virtually infinite number of video files using
67
68
  `),
68
69
  video_fade_seconds: z
69
70
  .number()
70
- .default(1)
71
+ .default(0)
71
72
  .describe(`
72
- When used this adds a video fade in and out effect between each section of your concatenated video. The float value is used so if you want a video delay effect of 500 milliseconds between each video section you would select \`0.5\`, however, integer values can also be represented.
73
+ ${getConcatFadeDescription('video')}
73
74
 
74
- This parameter does not add a video fade effect at the beginning or end of your video. If you want to do so, create an additional [🤖/video/encode](/docs/robots/video-encode/) Step and use our \`ffmpeg\` parameter as shown in this [demo](/demos/video-encoding/concatenate-fade-effect/).
75
+ This parameter only applies when \`transition\` is \`"none"\`. It is ignored when \`transition\` is \`"crossfade"\` or \`"fade_to_black"\`; use \`transition_duration\` for those effects. Audio fades are controlled separately by \`audio_fade_seconds\`.
75
76
 
76
- Please note this parameter is independent of adding audio fades between sections.
77
+ To add a fade-in at the beginning of the output, use an additional [video encoding Step](/docs/robots/video-encode/) with the \`ffmpeg\` parameter, as shown in the [video fade demo](/demos/video-encoding/concatenate-fade-effect/).
77
78
  `),
78
79
  audio_fade_seconds: z
79
80
  .number()
80
- .default(1)
81
+ .default(0)
81
82
  .describe(`
82
- When used this adds an audio fade in and out effect between each section of your concatenated video. 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\`, however, integer values can also be represented.
83
+ ${getConcatFadeDescription('audio')}
83
84
 
84
- This parameter does not add an audio fade effect at the beginning or end of your video. If you want to do so, create an additional [🤖/video/encode](/docs/robots/video-encode/) Step and use our \`ffmpeg\` parameter as shown in this [demo](/demos/audio-encoding/ffmpeg-fade-in-and-out/).
85
+ This parameter only applies when \`transition\` is \`"none"\`. It is ignored when \`transition\` is \`"crossfade"\` or \`"fade_to_black"\`; use \`transition_duration\` for those effects. Video fades are controlled separately by \`video_fade_seconds\`.
85
86
 
86
- Please note this parameter is independent of adding video fades between sections.
87
+ To add an audio fade-in at the beginning of the output, use an additional [video encoding Step](/docs/robots/video-encode/) with the \`ffmpeg\` parameter. The [audio fade demo](/demos/audio-encoding/ffmpeg-fade-in-and-out/) shows the corresponding FFmpeg audio filter.
87
88
  `),
88
89
  chapter_markers: z
89
90
  .boolean()
@@ -91,18 +91,18 @@ For an upload-to-poster Template, SDK usage, and publication checks, see the [HT
91
91
  count: thumbnailCountSchema.describe(`
92
92
  The number of thumbnails to be extracted. As some videos have incorrect durations, the actual number of thumbnails generated may be less in rare cases. The maximum number of thumbnails we currently allow is 999.
93
93
 
94
- The thumbnails are taken at regular intervals, determined by dividing the video duration by the count. For example, a count of 3 will produce thumbnails at 25%, 50% and 75% through the video.
94
+ When \`smart\` is \`false\`, the thumbnails are taken at regular intervals. For example, a count of 3 produces thumbnails at 25%, 50% and 75% through the video. With \`smart: true\`, this is the number of thumbnails selected from the candidate pool instead.
95
95
 
96
96
  To extract thumbnails for specific timestamps, use the \`offsets\` parameter.
97
97
  `),
98
98
  offsets: z
99
- .union([z.array(z.number().finite()), z.array(percentageSchema)])
99
+ .union([z.array(z.number().finite()).min(1), z.array(percentageSchema).min(1)])
100
100
  .nullable()
101
- .default([])
101
+ .optional()
102
102
  .describe(`
103
103
  An array of offsets representing seconds of the file duration, such as \`[ 2, 45, 120 ]\`. Millisecond durations of a file can also be used by using decimal place values. For example, an offset from 1250 milliseconds would be represented with \`1.25\`. Offsets can also be percentage values such as \`[ "2%", "50%", "75%" ]\`.
104
104
 
105
- This option cannot be used with the \`count\` parameter, and takes precedence if both are specified. Out-of-range offsets are silently ignored.
105
+ When \`smart\` is \`false\`, \`offsets\` takes precedence over \`count\`. Request timestamps within the video duration. If FFmpeg produces no frame at a requested offset, the Robot may retry without timestamp seeking and return an early frame instead. It does not simply ignore the offset, and extraction can still fail if no frame is available.
106
106
 
107
107
  When \`smart\` is \`true\`, smart selection ignores \`offsets\` and uses \`count\` to select from its own candidate timestamps. If no smart candidates can be extracted, the Robot falls back to standard extraction, where \`offsets\` takes precedence. Use \`smart: false\` to consistently extract the specified timestamps.
108
108
 
@@ -128,17 +128,17 @@ The width of the thumbnail, in pixels. Omit or use \`null\` for the original wid
128
128
  .number()
129
129
  .int()
130
130
  .min(1)
131
- .max(1080)
131
+ .max(1920)
132
132
  .nullable()
133
133
  .optional()
134
134
  .describe(`
135
- The height of the thumbnail, in pixels. Omit or use \`null\` for the original height of the video.
135
+ The thumbnail height in pixels, capped like \`width\`. Omit or use \`null\` for the original height.
136
136
  `),
137
137
  resize_strategy: resize_strategy.describe(`
138
138
  One of the [available resize strategies](/docs/topics/resize-strategies/).
139
139
  `),
140
- background: color_with_alpha.default('#00000000').describe(`
141
- The background color of the resulting thumbnails in the \`"rrggbbaa"\` format (red, green, blue, alpha) when used with the \`"pad"\` resize strategy. The default color is black.
140
+ background: color_with_alpha.default('#000000ff').describe(`
141
+ The background color of the resulting thumbnails in the \`"rrggbbaa"\` format (red, green, blue, alpha) when used with the \`"pad"\` resize strategy. The default color is opaque black.
142
142
  `),
143
143
  rotate: z
144
144
  .union([z.literal(0), z.literal(90), z.literal(180), z.literal(270), z.literal(360)])
@@ -39,7 +39,7 @@ export const robotVimeoStoreInstructionsSchema = robotBase
39
39
  robot: z.literal('/vimeo/store').describe(`
40
40
  ## Getting started
41
41
 
42
- Since Vimeo works with OAuth, you will need to generate [Template Credentials](https://transloadit.com/c/template-credentials/) to use this <dfn>Robot</dfn>.
42
+ Create [Template Credentials](https://transloadit.com/c/template-credentials/) using Vimeo OAuth for uploads, or paste your own personal access token. A personal token needs \`upload\`, \`edit\`, and \`interact\` for this <dfn>Robot</dfn>, plus \`public\`, \`private\`, and \`video_files\` when saving it through the Console for imports. Your Vimeo app must have upload access. Follow the [Vimeo token instructions](/docs/topics/template-credentials/#vimeo) to create a token or reconnect.
43
43
 
44
44
  To change the \`title\` or \`description\` per video, we recommend to [inject variables into your Template](/docs/topics/templates/).
45
45
 
@@ -0,0 +1,157 @@
1
+ import type { SimpleValueReason } from '../renderSimpleValue.ts'
2
+
3
+ import { formatSimpleValueReason } from '../renderSimpleValue.ts'
4
+
5
+ /** Usage in MiB charged for each dynamically evaluated Assembly expression. */
6
+ export const expressionEvaluationUsageMebibytes = 0.2
7
+
8
+ /** Character domain shared by stored Template IDs and their pagination cursors. */
9
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Stored Template IDs allow the full ASCII range.
10
+ export const storedTemplateIdentifierPattern = /^[\x00-\x7F]*$/u
11
+
12
+ /** Template Assembly Status retention values shared by runtime validation and contracts. */
13
+ export const templateAssemblyStatusExpiries = Object.freeze({
14
+ noTmpFileRetention: 'NoSave',
15
+ oneDay: '1day',
16
+ sevenDays: '7days',
17
+ thirtyDays: '30days',
18
+ ninetyDays: '90days',
19
+ })
20
+
21
+ /** Template transcoding-result retention values shared by runtime validation and contracts. */
22
+ export const templateTranscodingResultExpiries = Object.freeze({
23
+ noTmpFileRetention: 'NoSave',
24
+ oneDay: '1day',
25
+ })
26
+
27
+ /** Default storage lifecycle selections; separate from upload and execution phase timeouts. */
28
+ export const templateRetentionDefaults = Object.freeze({
29
+ assemblyStatus: templateAssemblyStatusExpiries.ninetyDays,
30
+ transcodingResult: templateTranscodingResultExpiries.oneDay,
31
+ })
32
+
33
+ export const templateListFieldValues = [
34
+ 'account_id',
35
+ 'assembly_status_expiry',
36
+ 'cron_auth_key_id',
37
+ 'cron_enabled',
38
+ 'cron_max_concurrent_runs',
39
+ 'cron_region',
40
+ 'cron_schedule_with_seconds',
41
+ 'created',
42
+ 'deleted',
43
+ 'encryption_version',
44
+ 'id',
45
+ 'json',
46
+ 'last_used',
47
+ 'modified',
48
+ 'name',
49
+ 'require_signature_auth',
50
+ 'transcoding_result_expiry',
51
+ ] as const
52
+
53
+ export type TemplateListField = (typeof templateListFieldValues)[number]
54
+
55
+ export const templateListDefaultFields = [
56
+ 'id',
57
+ 'name',
58
+ 'json',
59
+ 'encryption_version',
60
+ 'require_signature_auth',
61
+ 'assembly_status_expiry',
62
+ 'transcoding_result_expiry',
63
+ 'last_used',
64
+ 'created',
65
+ 'modified',
66
+ ] as const satisfies readonly TemplateListField[]
67
+
68
+ export const templateListOrderValues = ['asc', 'desc'] as const
69
+ export type TemplateListOrder = (typeof templateListOrderValues)[number]
70
+
71
+ export const templateListSortValues = ['id', 'name', 'created', 'modified', 'last_used'] as const
72
+ export type TemplateListSort = (typeof templateListSortValues)[number]
73
+
74
+ export const templateIncludeBuiltinModes = ['none', 'latest', 'all'] as const
75
+ export type TemplateIncludeBuiltinMode = (typeof templateIncludeBuiltinModes)[number]
76
+
77
+ export const templateIncludeBuiltinValues = [
78
+ ...templateIncludeBuiltinModes,
79
+ 'exclusively-latest',
80
+ 'exclusively-all',
81
+ ] as const
82
+ export type TemplateIncludeBuiltinValue = (typeof templateIncludeBuiltinValues)[number]
83
+
84
+ /** Canonical Template-list defaults shared by API2 and generated clients. */
85
+ export const templateListDefaults = Object.freeze({
86
+ includeBuiltin: 'none',
87
+ keywords: '',
88
+ order: 'desc',
89
+ page: 1,
90
+ pagesize: 850,
91
+ sort: 'created',
92
+ } satisfies {
93
+ includeBuiltin: TemplateIncludeBuiltinValue
94
+ keywords: string
95
+ order: TemplateListOrder
96
+ page: number
97
+ pagesize: number
98
+ sort: TemplateListSort
99
+ })
100
+
101
+ /** Maximum page size accepted by Template listing. */
102
+ export const templateListMaxPageSize = 850
103
+
104
+ /** Code-unit length retained for the private admin Workspace override. */
105
+ export const templateListWorkspaceIdCodeUnits = 32
106
+
107
+ /** Maximum customer-supplied characters retained in a Template-list error reason. */
108
+ export const templateListInvalidRequestValueMaxCharacters = 200
109
+
110
+ /** Handler-priority Template-list validation reasons shared by runtime and contract enforcement. */
111
+ export const templateListInvalidRequestReasons = {
112
+ order: {
113
+ message: 'Invalid order (must be "asc" or "desc")',
114
+ value: 'string',
115
+ },
116
+ sort: {
117
+ message: 'Invalid sort (must be "created", "modified", "name", "id" or "last_used")',
118
+ suffix: '.',
119
+ value: 'string',
120
+ },
121
+ pagesize: {
122
+ message: `Invalid pagesize (must be between 1 and ${templateListMaxPageSize})`,
123
+ suffix: '.',
124
+ value: 'integer-prefix',
125
+ },
126
+ page: {
127
+ message: 'Invalid page',
128
+ suffix: '.',
129
+ value: 'integer-prefix',
130
+ },
131
+ account_id: {
132
+ message: 'Invalid accountId',
133
+ suffix: '.',
134
+ value: 'string',
135
+ },
136
+ include_builtin: {
137
+ message: 'Invalid include_builtin',
138
+ value: 'string',
139
+ },
140
+ } satisfies Record<string, SimpleValueReason>
141
+
142
+ export type TemplateListInvalidRequestField = keyof typeof templateListInvalidRequestReasons
143
+
144
+ /** Formats one established Template-list reason from its shared contract fact. */
145
+ export function formatTemplateListInvalidRequestReason(
146
+ field: TemplateListInvalidRequestField,
147
+ value: unknown,
148
+ ): string {
149
+ const reason = templateListInvalidRequestReasons[field]
150
+ return formatSimpleValueReason(
151
+ typeof value === 'number' && reason.value === 'integer-prefix'
152
+ ? { ...reason, value: 'string' }
153
+ : reason,
154
+ value,
155
+ templateListInvalidRequestValueMaxCharacters,
156
+ )
157
+ }
@@ -13,11 +13,44 @@ import { CliInputError, formatSchemaIssues } from './errors.ts'
13
13
 
14
14
  const stepsWrapperSchema = z.object({ steps: z.record(z.unknown()) })
15
15
 
16
- /** CLI inputs can be partial overrides; API2 validates OCR after merging their runtime context. */
16
+ /** CLI overrides warn on new parameters and defer contextual OCR checks to API2. */
17
17
  export function createCliStepsSchema(
18
18
  output: Pick<IOutputCtl, 'warn'> = console,
19
19
  ): z.ZodType<StepsInput> {
20
- const cliStepSchema = robotsSchema
20
+ const cliStepSchema = z
21
+ .object({ robot: z.string() })
22
+ .catchall(z.unknown())
23
+ .transform((step, context) => {
24
+ let validated = robotsSchema.safeParse(step, { path: context.path })
25
+ const unknownKeys = new Set<string>()
26
+ if (!validated.success) {
27
+ for (const issue of validated.error.issues) {
28
+ // Only new top-level Robot parameters are advisory; nested objects stay strict.
29
+ if (issue.code !== 'unrecognized_keys' || issue.path.length !== context.path.length)
30
+ continue
31
+ for (const key of issue.keys) unknownKeys.add(key)
32
+ }
33
+ if (unknownKeys.size > 0) {
34
+ validated = robotsSchema.safeParse(
35
+ Object.fromEntries(Object.entries(step).filter(([key]) => !unknownKeys.has(key))),
36
+ { path: context.path },
37
+ )
38
+ }
39
+ }
40
+ if (!validated.success) {
41
+ for (const issue of validated.error.issues) {
42
+ // Keep nested union diagnostics rooted at the Step while addIssue prefixes its own path.
43
+ context.addIssue({ ...issue, path: issue.path.slice(context.path.length) })
44
+ }
45
+ return z.NEVER
46
+ }
47
+ if (unknownKeys.size > 0) {
48
+ output.warn(
49
+ `${context.path.join('.')}: Unrecognized step parameters: ${[...unknownKeys].join(', ')}. Preserving them for API validation when submitted.`,
50
+ )
51
+ }
52
+ return { ...step, ...validated.data }
53
+ })
21
54
  .superRefine(refineTransloaditImportSelector)
22
55
  .superRefine((step, context) => {
23
56
  // API2 merges Template defaults before Robot validation. New OCR checks must not reject