@lemoncat7/dsh-partner 2.5.0 → 2.7.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 (653) hide show
  1. package/README.md +4 -0
  2. package/docs/THIRD_PARTY_LICENSES.md +13 -0
  3. package/docs/attachment-storage.md +18 -0
  4. package/docs/director-skills.md +24 -0
  5. package/docs/long-task-continuation.md +1 -1
  6. package/docs/releases/2.6.0.md +11 -0
  7. package/docs/releases/2.7.1.md +10 -0
  8. package/lib/agent-runtime.d.ts +3 -1
  9. package/lib/agent-runtime.d.ts.map +1 -1
  10. package/lib/agent-runtime.js +35 -11
  11. package/lib/agent-runtime.js.map +1 -1
  12. package/lib/api/features/attachments-api.d.ts.map +1 -1
  13. package/lib/api/features/attachments-api.js +31 -1
  14. package/lib/api/features/attachments-api.js.map +1 -1
  15. package/lib/api.d.ts +1 -1
  16. package/lib/api.d.ts.map +1 -1
  17. package/lib/api.js +1 -1
  18. package/lib/api.js.map +1 -1
  19. package/lib/attachments/grouped-storage.d.ts +5 -0
  20. package/lib/attachments/grouped-storage.d.ts.map +1 -0
  21. package/lib/attachments/grouped-storage.js +47 -0
  22. package/lib/attachments/grouped-storage.js.map +1 -0
  23. package/lib/attachments/service.d.ts +17 -0
  24. package/lib/attachments/service.d.ts.map +1 -1
  25. package/lib/attachments/service.js +100 -8
  26. package/lib/attachments/service.js.map +1 -1
  27. package/lib/attachments/shared-storage.d.ts +7 -0
  28. package/lib/attachments/shared-storage.d.ts.map +1 -0
  29. package/lib/attachments/shared-storage.js +44 -0
  30. package/lib/attachments/shared-storage.js.map +1 -0
  31. package/lib/attachments/storage-admin.d.ts +6 -0
  32. package/lib/attachments/storage-admin.d.ts.map +1 -0
  33. package/lib/attachments/storage-admin.js +43 -0
  34. package/lib/attachments/storage-admin.js.map +1 -0
  35. package/lib/attachments/storage-types.d.ts +33 -0
  36. package/lib/attachments/storage-types.d.ts.map +1 -0
  37. package/lib/attachments/storage-types.js +2 -0
  38. package/lib/attachments/storage-types.js.map +1 -0
  39. package/lib/attachments/tool.d.ts.map +1 -1
  40. package/lib/attachments/tool.js +16 -5
  41. package/lib/attachments/tool.js.map +1 -1
  42. package/lib/channels/delivery-policy.d.ts.map +1 -1
  43. package/lib/channels/delivery-policy.js +6 -4
  44. package/lib/channels/delivery-policy.js.map +1 -1
  45. package/lib/channels/direct/message-format.js +294 -157
  46. package/lib/channels/direct/message-format.js.map +4 -4
  47. package/lib/channels/manager.d.ts +6 -3
  48. package/lib/channels/manager.d.ts.map +1 -1
  49. package/lib/channels/manager.js +64 -44
  50. package/lib/channels/manager.js.map +1 -1
  51. package/lib/channels/weixin/api.d.ts.map +1 -1
  52. package/lib/channels/weixin/api.js +7 -2
  53. package/lib/channels/weixin/api.js.map +1 -1
  54. package/lib/channels/weixin/errors.d.ts +14 -0
  55. package/lib/channels/weixin/errors.d.ts.map +1 -0
  56. package/lib/channels/weixin/errors.js +29 -0
  57. package/lib/channels/weixin/errors.js.map +1 -0
  58. package/lib/channels/weixin/poll.d.ts +14 -0
  59. package/lib/channels/weixin/poll.d.ts.map +1 -0
  60. package/lib/channels/weixin/poll.js +50 -0
  61. package/lib/channels/weixin/poll.js.map +1 -0
  62. package/lib/client-controller.d.ts.map +1 -1
  63. package/lib/client-controller.js +3 -3
  64. package/lib/client-controller.js.map +1 -1
  65. package/lib/client.d.ts.map +1 -1
  66. package/lib/client.js +983 -575
  67. package/lib/client.js.map +4 -4
  68. package/lib/collaboration/composition.d.ts.map +1 -1
  69. package/lib/collaboration/composition.js +16 -6
  70. package/lib/collaboration/composition.js.map +1 -1
  71. package/lib/collaboration/domain.d.ts +6 -0
  72. package/lib/collaboration/domain.d.ts.map +1 -1
  73. package/lib/collaboration/domain.js.map +1 -1
  74. package/lib/collaboration/service.d.ts.map +1 -1
  75. package/lib/collaboration/service.js +33 -3
  76. package/lib/collaboration/service.js.map +1 -1
  77. package/lib/concern-notification.d.ts.map +1 -1
  78. package/lib/concern-notification.js +2 -1
  79. package/lib/concern-notification.js.map +1 -1
  80. package/lib/concern-tool.js +1 -1
  81. package/lib/concern-tool.js.map +1 -1
  82. package/lib/execution/reply-progress.d.ts.map +1 -1
  83. package/lib/execution/reply-progress.js +4 -2
  84. package/lib/execution/reply-progress.js.map +1 -1
  85. package/lib/execution/service.d.ts +1 -1
  86. package/lib/execution/service.d.ts.map +1 -1
  87. package/lib/execution/service.js +4 -7
  88. package/lib/execution/service.js.map +1 -1
  89. package/lib/index.d.ts.map +1 -1
  90. package/lib/index.js +3 -1
  91. package/lib/index.js.map +1 -1
  92. package/lib/memory-reflection.js +2 -2
  93. package/lib/memory-reflection.js.map +1 -1
  94. package/lib/message-source.d.ts +25 -0
  95. package/lib/message-source.d.ts.map +1 -0
  96. package/lib/message-source.js +8 -0
  97. package/lib/message-source.js.map +1 -0
  98. package/lib/observation-loop.js +4 -4
  99. package/lib/observation-loop.js.map +1 -1
  100. package/lib/pendant/reader.js +1 -1
  101. package/lib/pendant/reader.js.map +1 -1
  102. package/lib/pendant/renderer.d.ts.map +1 -1
  103. package/lib/pendant/renderer.js +18 -2
  104. package/lib/pendant/renderer.js.map +1 -1
  105. package/lib/pendant/settings-panel.js +1 -1
  106. package/lib/pendant/settings-panel.js.map +1 -1
  107. package/lib/pendant/startup.d.ts +4 -0
  108. package/lib/pendant/startup.d.ts.map +1 -0
  109. package/lib/pendant/startup.js +64 -0
  110. package/lib/pendant/startup.js.map +1 -0
  111. package/lib/pendant/widget.d.ts.map +1 -1
  112. package/lib/pendant/widget.js +2 -1
  113. package/lib/pendant/widget.js.map +1 -1
  114. package/lib/pendant-renderer.js +169 -169
  115. package/lib/persona/service.js +1 -1
  116. package/lib/persona/service.js.map +1 -1
  117. package/lib/requirements/attachments.d.ts +7 -0
  118. package/lib/requirements/attachments.d.ts.map +1 -0
  119. package/lib/requirements/attachments.js +26 -0
  120. package/lib/requirements/attachments.js.map +1 -0
  121. package/lib/requirements/domain.d.ts +1 -1
  122. package/lib/requirements/domain.d.ts.map +1 -1
  123. package/lib/requirements/service.d.ts.map +1 -1
  124. package/lib/requirements/service.js +4 -1
  125. package/lib/requirements/service.js.map +1 -1
  126. package/lib/scheduler/channel-origin.d.ts +7 -0
  127. package/lib/scheduler/channel-origin.d.ts.map +1 -0
  128. package/lib/scheduler/channel-origin.js +27 -0
  129. package/lib/scheduler/channel-origin.js.map +1 -0
  130. package/lib/scheduler/continuations.d.ts +2 -2
  131. package/lib/scheduler/continuations.d.ts.map +1 -1
  132. package/lib/scheduler/continuations.js +28 -8
  133. package/lib/scheduler/continuations.js.map +1 -1
  134. package/lib/scheduler/domain.d.ts +13 -0
  135. package/lib/scheduler/domain.d.ts.map +1 -1
  136. package/lib/scheduler/final-reply.d.ts +13 -0
  137. package/lib/scheduler/final-reply.d.ts.map +1 -0
  138. package/lib/scheduler/final-reply.js +21 -0
  139. package/lib/scheduler/final-reply.js.map +1 -0
  140. package/lib/skills/builtin-resources.d.ts +6 -0
  141. package/lib/skills/builtin-resources.d.ts.map +1 -0
  142. package/lib/skills/builtin-resources.js +47 -0
  143. package/lib/skills/builtin-resources.js.map +1 -0
  144. package/lib/skills/builtin.d.ts +2 -5
  145. package/lib/skills/builtin.d.ts.map +1 -1
  146. package/lib/skills/builtin.js +6 -0
  147. package/lib/skills/builtin.js.map +1 -1
  148. package/lib/skills/director-builtins.d.ts +8 -0
  149. package/lib/skills/director-builtins.d.ts.map +1 -0
  150. package/lib/skills/director-builtins.js +65 -0
  151. package/lib/skills/director-builtins.js.map +1 -0
  152. package/lib/skills/drama-builtin.d.ts +3 -0
  153. package/lib/skills/drama-builtin.d.ts.map +1 -0
  154. package/lib/skills/drama-builtin.js +49 -0
  155. package/lib/skills/drama-builtin.js.map +1 -0
  156. package/lib/skills/long-task-continuation.d.ts +2 -2
  157. package/lib/skills/long-task-continuation.d.ts.map +1 -1
  158. package/lib/skills/long-task-continuation.js +2 -2
  159. package/lib/skills/service.d.ts.map +1 -1
  160. package/lib/skills/service.js +8 -3
  161. package/lib/skills/service.js.map +1 -1
  162. package/lib/skills/viora-canvas-builtin.d.ts +3 -0
  163. package/lib/skills/viora-canvas-builtin.d.ts.map +1 -0
  164. package/lib/skills/viora-canvas-builtin.js +19 -0
  165. package/lib/skills/viora-canvas-builtin.js.map +1 -0
  166. package/lib/storage/migrate-layout.d.ts.map +1 -1
  167. package/lib/storage/migrate-layout.js +13 -4
  168. package/lib/storage/migrate-layout.js.map +1 -1
  169. package/lib/tasks/context.js +1 -0
  170. package/lib/tasks/context.js.map +1 -1
  171. package/lib/tasks/continuation.d.ts.map +1 -1
  172. package/lib/tasks/continuation.js +3 -0
  173. package/lib/tasks/continuation.js.map +1 -1
  174. package/lib/tasks/domain.d.ts +2 -0
  175. package/lib/tasks/domain.d.ts.map +1 -1
  176. package/lib/tasks/service.d.ts +2 -1
  177. package/lib/tasks/service.d.ts.map +1 -1
  178. package/lib/tasks/service.js +15 -1
  179. package/lib/tasks/service.js.map +1 -1
  180. package/lib/ui/attachment-storage.d.ts +2 -0
  181. package/lib/ui/attachment-storage.d.ts.map +1 -0
  182. package/lib/ui/attachment-storage.js +59 -0
  183. package/lib/ui/attachment-storage.js.map +1 -0
  184. package/lib/ui/capability-editor.js +1 -1
  185. package/lib/ui/capability-editor.js.map +1 -1
  186. package/lib/ui/companion-access-panel.js +1 -1
  187. package/lib/ui/companion-access-panel.js.map +1 -1
  188. package/lib/ui/companion-create.js +1 -1
  189. package/lib/ui/companion-create.js.map +1 -1
  190. package/lib/ui/companion-picker.js +1 -1
  191. package/lib/ui/companion-picker.js.map +1 -1
  192. package/lib/ui/identity-editor.js +1 -1
  193. package/lib/ui/identity-editor.js.map +1 -1
  194. package/lib/ui/mcp-capabilities.js +1 -1
  195. package/lib/ui/mcp-capabilities.js.map +1 -1
  196. package/lib/ui/mcp-panel.js +1 -1
  197. package/lib/ui/mcp-panel.js.map +1 -1
  198. package/lib/ui/memory/concern-board.js +2 -2
  199. package/lib/ui/memory/concern-board.js.map +1 -1
  200. package/lib/ui/memory/concern-deletion.d.ts +1 -1
  201. package/lib/ui/memory/concern-deletion.d.ts.map +1 -1
  202. package/lib/ui/memory/concern-deletion.js +9 -4
  203. package/lib/ui/memory/concern-deletion.js.map +1 -1
  204. package/lib/ui/memory/concern-panel.js +1 -1
  205. package/lib/ui/memory/concern-panel.js.map +1 -1
  206. package/lib/ui/memory/memory-library.js +1 -1
  207. package/lib/ui/memory/memory-library.js.map +1 -1
  208. package/lib/ui/memory/memory-panel.js +1 -1
  209. package/lib/ui/memory/memory-panel.js.map +1 -1
  210. package/lib/ui/partner-components.js +1 -1
  211. package/lib/ui/partner-components.js.map +1 -1
  212. package/lib/ui/requirement-board.js +1 -1
  213. package/lib/ui/requirement-board.js.map +1 -1
  214. package/lib/ui/requirement-dialog.js +1 -1
  215. package/lib/ui/requirement-dialog.js.map +1 -1
  216. package/lib/ui/requirement-tasks-panel.js +1 -1
  217. package/lib/ui/requirement-tasks-panel.js.map +1 -1
  218. package/lib/ui/schedule-panel.js +1 -1
  219. package/lib/ui/schedule-panel.js.map +1 -1
  220. package/lib/ui/skills-panel.js +1 -1
  221. package/lib/ui/skills-panel.js.map +1 -1
  222. package/lib/ui/storage-settings.d.ts.map +1 -1
  223. package/lib/ui/storage-settings.js +2 -1
  224. package/lib/ui/storage-settings.js.map +1 -1
  225. package/lib/ui/task-board-stage.js +1 -1
  226. package/lib/ui/task-board-stage.js.map +1 -1
  227. package/lib/ui/workspace-components.js +1 -1
  228. package/lib/ui/workspace-components.js.map +1 -1
  229. package/lib/ui/workspace-navigation.d.ts +16 -16
  230. package/lib/ui/workspace-navigation.js +1 -1
  231. package/lib/ui/workspace-navigation.js.map +1 -1
  232. package/package.json +53 -41
  233. package/resources/skills/cinematic-director/manifest.json +205 -0
  234. package/resources/skills/cinematic-director/upstream/CHANGELOG.md +213 -0
  235. package/resources/skills/cinematic-director/upstream/CONTRIBUTING.md +316 -0
  236. package/resources/skills/cinematic-director/upstream/LICENSE +21 -0
  237. package/resources/skills/cinematic-director/upstream/README.md +507 -0
  238. package/resources/skills/cinematic-director/upstream/SKILL.md +420 -0
  239. package/resources/skills/cinematic-director/upstream/assets/beat-sheet-template.md +97 -0
  240. package/resources/skills/cinematic-director/upstream/assets/director-book-template.md +254 -0
  241. package/resources/skills/cinematic-director/upstream/assets/edit-timeline-template.md +147 -0
  242. package/resources/skills/cinematic-director/upstream/assets/keyframe-prompt-template.md +334 -0
  243. package/resources/skills/cinematic-director/upstream/assets/qc-checklist.md +153 -0
  244. package/resources/skills/cinematic-director/upstream/assets/shot-plan-template.md +95 -0
  245. package/resources/skills/cinematic-director/upstream/assets/sound-plan-template.md +181 -0
  246. package/resources/skills/cinematic-director/upstream/assets/video-prompt-template.md +305 -0
  247. package/resources/skills/cinematic-director/upstream/evals/run.md +69 -0
  248. package/resources/skills/cinematic-director/upstream/references/ai-video-tool-adapters.md +527 -0
  249. package/resources/skills/cinematic-director/upstream/references/blocking-and-staging.md +424 -0
  250. package/resources/skills/cinematic-director/upstream/references/cinematic-language.md +595 -0
  251. package/resources/skills/cinematic-director/upstream/references/continuity-bible.md +410 -0
  252. package/resources/skills/cinematic-director/upstream/references/director_styles/01_spielberg.md +192 -0
  253. package/resources/skills/cinematic-director/upstream/references/director_styles/02_hitchcock.md +193 -0
  254. package/resources/skills/cinematic-director/upstream/references/director_styles/03_kubrick.md +196 -0
  255. package/resources/skills/cinematic-director/upstream/references/director_styles/04_kurosawa.md +196 -0
  256. package/resources/skills/cinematic-director/upstream/references/director_styles/05_scorsese.md +195 -0
  257. package/resources/skills/cinematic-director/upstream/references/director_styles/06_fellini.md +197 -0
  258. package/resources/skills/cinematic-director/upstream/references/director_styles/07_bergman.md +197 -0
  259. package/resources/skills/cinematic-director/upstream/references/director_styles/08_tarkovsky.md +184 -0
  260. package/resources/skills/cinematic-director/upstream/references/director_styles/09_wong_kar_wai.md +189 -0
  261. package/resources/skills/cinematic-director/upstream/references/director_styles/10_nolan.md +185 -0
  262. package/resources/skills/cinematic-director/upstream/references/director_styles/11_villeneuve.md +188 -0
  263. package/resources/skills/cinematic-director/upstream/references/director_styles/12_fincher.md +185 -0
  264. package/resources/skills/cinematic-director/upstream/references/director_styles/13_refn.md +186 -0
  265. package/resources/skills/cinematic-director/upstream/references/director_styles/14_bi_gan.md +188 -0
  266. package/resources/skills/cinematic-director/upstream/references/director_styles/15_zhang_yimou.md +192 -0
  267. package/resources/skills/cinematic-director/upstream/references/director_styles/16_hou_hsiao_hsien.md +192 -0
  268. package/resources/skills/cinematic-director/upstream/references/director_styles/17_park_chan_wook.md +192 -0
  269. package/resources/skills/cinematic-director/upstream/references/director_styles/18_malick.md +191 -0
  270. package/resources/skills/cinematic-director/upstream/references/director_styles/19_michael_mann.md +192 -0
  271. package/resources/skills/cinematic-director/upstream/references/director_styles/20_coen_brothers.md +194 -0
  272. package/resources/skills/cinematic-director/upstream/references/director_styles/README.md +89 -0
  273. package/resources/skills/cinematic-director/upstream/references/director_styles/example_comparisons.md +140 -0
  274. package/resources/skills/cinematic-director/upstream/references/editing-and-assembly.md +263 -0
  275. package/resources/skills/cinematic-director/upstream/references/failure-modes.md +594 -0
  276. package/resources/skills/cinematic-director/upstream/references/genre-playbooks.md +407 -0
  277. package/resources/skills/cinematic-director/upstream/references/image-model-adapters.md +290 -0
  278. package/resources/skills/cinematic-director/upstream/references/lighting-and-color.md +349 -0
  279. package/resources/skills/cinematic-director/upstream/references/product-and-macro.md +207 -0
  280. package/resources/skills/cinematic-director/upstream/references/production-workflow.md +238 -0
  281. package/resources/skills/cinematic-director/upstream/references/prompt-lexicon.md +602 -0
  282. package/resources/skills/cinematic-director/upstream/references/sound-and-dialogue.md +364 -0
  283. package/resources/skills/director-skills/manifest.json +541 -0
  284. package/resources/skills/director-skills/upstream/CHANGELOG.md +32 -0
  285. package/resources/skills/director-skills/upstream/LICENSE +21 -0
  286. package/resources/skills/director-skills/upstream/MANIFEST.md +366 -0
  287. package/resources/skills/director-skills/upstream/README.md +251 -0
  288. package/resources/skills/director-skills/upstream/RESEARCH_PROVENANCE.md +37 -0
  289. package/resources/skills/director-skills/upstream/SKILL.md +114 -0
  290. package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/SKILL.md +81 -0
  291. package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/checklists/continuity_checklist.md +8 -0
  292. package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/examples/character_continuity_update.md +9 -0
  293. package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/examples/location_continuity_update.md +9 -0
  294. package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/references/continuity_system.md +21 -0
  295. package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/references/golden_image_workflow.md +29 -0
  296. package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/references/identity_anchors.md +14 -0
  297. package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/references/visual_bible_system.md +15 -0
  298. package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/templates/character_profile.md +15 -0
  299. package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/templates/continuity_checklist.md +11 -0
  300. package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/templates/costume_profile.md +12 -0
  301. package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/templates/location_profile.md +14 -0
  302. package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/templates/prop_profile.md +13 -0
  303. package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/templates/visual_style_bible.md +15 -0
  304. package/resources/skills/director-skills/upstream/skills/character-location-prop-bible/tests/continuity_tests.md +6 -0
  305. package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/SKILL.md +80 -0
  306. package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/checklists/image_prompt_quality_checklist.md +10 -0
  307. package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/examples/shot_list_to_image_prompts.md +4 -0
  308. package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/references/image_prompt_components.md +22 -0
  309. package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/references/lighting_and_photorealism.md +19 -0
  310. package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/references/negative_prompt_patterns.md +18 -0
  311. package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/references/prompt_anatomy.md +18 -0
  312. package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/templates/character_reference_sheet_prompt.md +5 -0
  313. package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/templates/cinematic_still_prompt.md +3 -0
  314. package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/templates/keyframe_prompt.md +6 -0
  315. package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/templates/location_reference_prompt.md +3 -0
  316. package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/templates/prop_prompt.md +3 -0
  317. package/resources/skills/director-skills/upstream/skills/cinematic-image-prompting/tests/image_prompt_completeness_tests.md +5 -0
  318. package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/SKILL.md +84 -0
  319. package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/checklists/video_prompt_quality_checklist.md +9 -0
  320. package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/examples/image_prompt_to_video_prompt.md +3 -0
  321. package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/references/ai_film_production_pipeline.md +21 -0
  322. package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/references/audio_motion_timing.md +16 -0
  323. package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/references/camera_lens_movement.md +26 -0
  324. package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/references/negative_prompt_patterns.md +18 -0
  325. package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/references/prompt_anatomy.md +18 -0
  326. package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/references/video_prompt_components.md +25 -0
  327. package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/templates/image_to_video_prompt.md +5 -0
  328. package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/templates/text_to_video_prompt.md +5 -0
  329. package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/templates/video_beat_prompt.md +9 -0
  330. package/resources/skills/director-skills/upstream/skills/cinematic-video-prompting/tests/video_prompt_completeness_tests.md +5 -0
  331. package/resources/skills/director-skills/upstream/skills/model-adaptation/SKILL.md +82 -0
  332. package/resources/skills/director-skills/upstream/skills/model-adaptation/checklists/model_export_checklist.md +9 -0
  333. package/resources/skills/director-skills/upstream/skills/model-adaptation/examples/failed_model_transfer_diagnosis.md +5 -0
  334. package/resources/skills/director-skills/upstream/skills/model-adaptation/examples/image_prompt_translation.md +10 -0
  335. package/resources/skills/director-skills/upstream/skills/model-adaptation/examples/same_scene_across_models.md +9 -0
  336. package/resources/skills/director-skills/upstream/skills/model-adaptation/examples/universal_prompt_to_model_specific_exports.md +14 -0
  337. package/resources/skills/director-skills/upstream/skills/model-adaptation/examples/video_prompt_translation.md +9 -0
  338. package/resources/skills/director-skills/upstream/skills/model-adaptation/references/failure_modes_by_model.md +13 -0
  339. package/resources/skills/director-skills/upstream/skills/model-adaptation/references/image_model_profiles.md +226 -0
  340. package/resources/skills/director-skills/upstream/skills/model-adaptation/references/model_capability_matrix.md +24 -0
  341. package/resources/skills/director-skills/upstream/skills/model-adaptation/references/model_update_protocol.md +10 -0
  342. package/resources/skills/director-skills/upstream/skills/model-adaptation/references/parameter_compatibility.md +20 -0
  343. package/resources/skills/director-skills/upstream/skills/model-adaptation/references/prompt_translation_rules.md +18 -0
  344. package/resources/skills/director-skills/upstream/skills/model-adaptation/references/video_model_profiles.md +251 -0
  345. package/resources/skills/director-skills/upstream/skills/model-adaptation/templates/model_comparison_table.md +4 -0
  346. package/resources/skills/director-skills/upstream/skills/model-adaptation/templates/model_specific_prompt_export.md +21 -0
  347. package/resources/skills/director-skills/upstream/skills/model-adaptation/templates/multi_model_export_package.md +18 -0
  348. package/resources/skills/director-skills/upstream/skills/model-adaptation/templates/prompt_translation_report.md +13 -0
  349. package/resources/skills/director-skills/upstream/skills/model-adaptation/templates/universal_prompt_brief.md +25 -0
  350. package/resources/skills/director-skills/upstream/skills/model-adaptation/tests/model_routing_tests.md +8 -0
  351. package/resources/skills/director-skills/upstream/skills/model-adaptation/tests/prompt_translation_tests.md +7 -0
  352. package/resources/skills/director-skills/upstream/skills/model-adaptation/tests/research_provenance_tests.md +6 -0
  353. package/resources/skills/director-skills/upstream/skills/model-adaptation/tests/unknown_capability_tests.md +7 -0
  354. package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/SKILL.md +82 -0
  355. package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/checklists/diagnostic_checklist.md +8 -0
  356. package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/checklists/release_quality_checklist.md +9 -0
  357. package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/examples/failed_output_to_revised_prompt.md +10 -0
  358. package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/examples/final_prompt_package_export.md +10 -0
  359. package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/references/failure_modes.md +13 -0
  360. package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/references/iteration_diagnostics.md +17 -0
  361. package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/references/revision_loops.md +17 -0
  362. package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/references/story_voice_release_qc.md +30 -0
  363. package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/templates/failed_output_intake.md +13 -0
  364. package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/templates/final_production_package.md +11 -0
  365. package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/templates/prompt_revision_report.md +17 -0
  366. package/resources/skills/director-skills/upstream/skills/prompt-iteration-and-diagnostics/tests/prompt_quality_checklist_tests.md +6 -0
  367. package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/SKILL.md +81 -0
  368. package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/checklists/scene_quality_checklist.md +8 -0
  369. package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/examples/scene_to_screenplay_excerpt.md +18 -0
  370. package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/references/dialogue_and_subtext.md +15 -0
  371. package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/references/fountain_workflow.md +29 -0
  372. package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/references/scene_construction.md +20 -0
  373. package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/references/screenplay_format.md +18 -0
  374. package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/templates/beat_sheet.md +6 -0
  375. package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/templates/dialogue_pass.md +9 -0
  376. package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/templates/fountain_scene.md +25 -0
  377. package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/templates/screenplay_scene.md +19 -0
  378. package/resources/skills/director-skills/upstream/skills/screenplay-and-scene-writing/tests/screenplay_scene_tests.md +6 -0
  379. package/resources/skills/director-skills/upstream/skills/short-film-development/SKILL.md +83 -0
  380. package/resources/skills/director-skills/upstream/skills/short-film-development/checklists/concept_quality_checklist.md +8 -0
  381. package/resources/skills/director-skills/upstream/skills/short-film-development/examples/idea_to_logline.md +13 -0
  382. package/resources/skills/director-skills/upstream/skills/short-film-development/examples/logline_to_outline.md +10 -0
  383. package/resources/skills/director-skills/upstream/skills/short-film-development/references/ai_storytelling_workflow.md +46 -0
  384. package/resources/skills/director-skills/upstream/skills/short-film-development/references/continuity_handoff.md +8 -0
  385. package/resources/skills/director-skills/upstream/skills/short-film-development/references/screenplay_structure.md +19 -0
  386. package/resources/skills/director-skills/upstream/skills/short-film-development/references/story_development_workflow.md +23 -0
  387. package/resources/skills/director-skills/upstream/skills/short-film-development/templates/beat_sheet.md +7 -0
  388. package/resources/skills/director-skills/upstream/skills/short-film-development/templates/idea_intake.md +16 -0
  389. package/resources/skills/director-skills/upstream/skills/short-film-development/templates/logline.md +10 -0
  390. package/resources/skills/director-skills/upstream/skills/short-film-development/templates/short_film_treatment.md +23 -0
  391. package/resources/skills/director-skills/upstream/skills/short-film-development/templates/short_story_plan.md +22 -0
  392. package/resources/skills/director-skills/upstream/skills/short-film-development/templates/story_premise.md +19 -0
  393. package/resources/skills/director-skills/upstream/skills/short-film-development/templates/story_revision_audit.md +22 -0
  394. package/resources/skills/director-skills/upstream/skills/short-film-development/tests/output_format_tests.md +3 -0
  395. package/resources/skills/director-skills/upstream/skills/short-film-development/tests/routing_tests.md +7 -0
  396. package/resources/skills/director-skills/upstream/skills/shotlist-and-visual-breakdown/SKILL.md +77 -0
  397. package/resources/skills/director-skills/upstream/skills/shotlist-and-visual-breakdown/checklists/shotlist_quality_checklist.md +8 -0
  398. package/resources/skills/director-skills/upstream/skills/shotlist-and-visual-breakdown/examples/screenplay_scene_to_shot_list.md +8 -0
  399. package/resources/skills/director-skills/upstream/skills/shotlist-and-visual-breakdown/references/camera_plan.md +11 -0
  400. package/resources/skills/director-skills/upstream/skills/shotlist-and-visual-breakdown/references/production_breakdown.md +11 -0
  401. package/resources/skills/director-skills/upstream/skills/shotlist-and-visual-breakdown/references/shotlist_schema.md +21 -0
  402. package/resources/skills/director-skills/upstream/skills/shotlist-and-visual-breakdown/templates/asset_list.md +7 -0
  403. package/resources/skills/director-skills/upstream/skills/shotlist-and-visual-breakdown/templates/camera_plan.md +10 -0
  404. package/resources/skills/director-skills/upstream/skills/shotlist-and-visual-breakdown/templates/scene_breakdown.md +16 -0
  405. package/resources/skills/director-skills/upstream/skills/shotlist-and-visual-breakdown/templates/shot_list.md +5 -0
  406. package/resources/skills/director-skills/upstream/skills/shotlist-and-visual-breakdown/tests/screenplay_to_shotlist_tests.md +5 -0
  407. package/resources/skills/director-skills/upstream/skills/style-cinematography-director/SKILL.md +77 -0
  408. package/resources/skills/director-skills/upstream/skills/style-cinematography-director/checklists/style_quality_checklist.md +8 -0
  409. package/resources/skills/director-skills/upstream/skills/style-cinematography-director/examples/genre_to_visual_direction.md +15 -0
  410. package/resources/skills/director-skills/upstream/skills/style-cinematography-director/references/camera_lens_movement.md +26 -0
  411. package/resources/skills/director-skills/upstream/skills/style-cinematography-director/references/cinematic_language.md +32 -0
  412. package/resources/skills/director-skills/upstream/skills/style-cinematography-director/references/genre_style_tone.md +17 -0
  413. package/resources/skills/director-skills/upstream/skills/style-cinematography-director/references/lighting_and_photorealism.md +19 -0
  414. package/resources/skills/director-skills/upstream/skills/style-cinematography-director/templates/visual_style_bible.md +33 -0
  415. package/resources/skills/director-skills/upstream/skills/style-cinematography-director/tests/style_translation_tests.md +5 -0
  416. package/resources/skills/director-skills/upstream/tests/master_router_tests.md +80 -0
  417. package/resources/skills/drama-production/manifest.json +933 -0
  418. package/resources/skills/drama-production/upstream/LICENSE +21 -0
  419. package/resources/skills/drama-production/upstream/skills/short-drama/SKILL.md +153 -0
  420. package/resources/skills/drama-production/upstream/skills/short-drama/agents/openai.yaml +4 -0
  421. package/resources/skills/drama-production/upstream/skills/short-drama/assets/creator-decision.example.jsonl +4 -0
  422. package/resources/skills/drama-production/upstream/skills/short-drama/assets/dashboard/app.js +1263 -0
  423. package/resources/skills/drama-production/upstream/skills/short-drama/assets/dashboard/index.html +114 -0
  424. package/resources/skills/drama-production/upstream/skills/short-drama/assets/dashboard/styles.css +297 -0
  425. package/resources/skills/drama-production/upstream/skills/short-drama/assets/production-observation.example.jsonl +2 -0
  426. package/resources/skills/drama-production/upstream/skills/short-drama/assets/project-template/short-drama.json +36 -0
  427. package/resources/skills/drama-production/upstream/skills/short-drama/assets/reference-observation.example.jsonl +2 -0
  428. package/resources/skills/drama-production/upstream/skills/short-drama/references/audience-reveal.md +21 -0
  429. package/resources/skills/drama-production/upstream/skills/short-drama/references/contract-and-ownership.md +45 -0
  430. package/resources/skills/drama-production/upstream/skills/short-drama/references/creator-documents.md +230 -0
  431. package/resources/skills/drama-production/upstream/skills/short-drama/references/creator-workflow.md +98 -0
  432. package/resources/skills/drama-production/upstream/skills/short-drama/references/form-cards/Q/347/211/210/350/241/250/350/276/276.md +73 -0
  433. package/resources/skills/drama-production/upstream/skills/short-drama/references/form-cards//344/272/214/347/273/264/345/212/250/346/200/201/346/274/253.md +74 -0
  434. package/resources/skills/drama-production/upstream/skills/short-drama/references/form-cards//345/233/275/346/274/253/344/272/214/346/254/241/345/205/203.md +79 -0
  435. package/resources/skills/drama-production/upstream/skills/short-drama/references/form-cards//345/256/236/346/213/215.md +75 -0
  436. package/resources/skills/drama-production/upstream/skills/short-drama/references/form-cards//346/260/264/345/242/250/347/254/224/350/247/246.md +85 -0
  437. package/resources/skills/drama-production/upstream/skills/short-drama/references/form-cards//351/243/216/346/240/274/345/214/226/344/270/211/347/273/264.md +72 -0
  438. package/resources/skills/drama-production/upstream/skills/short-drama/references/knowhow-index.md +85 -0
  439. package/resources/skills/drama-production/upstream/skills/short-drama/references/look-development.md +51 -0
  440. package/resources/skills/drama-production/upstream/skills/short-drama/references/pickup-and-alternate.md +24 -0
  441. package/resources/skills/drama-production/upstream/skills/short-drama/references/production-form-profiles.md +126 -0
  442. package/resources/skills/drama-production/upstream/skills/short-drama/references/reference-roles.md +53 -0
  443. package/resources/skills/drama-production/upstream/skills/short-drama/references/runtime-preflight.md +24 -0
  444. package/resources/skills/drama-production/upstream/skills/short-drama/scripts/creator_markdown_check.py +1413 -0
  445. package/resources/skills/drama-production/upstream/skills/short-drama/scripts/dashboard_server.py +1798 -0
  446. package/resources/skills/drama-production/upstream/skills/short-drama/scripts/project_tool.py +2454 -0
  447. package/resources/skills/drama-production/upstream/skills/short-drama/scripts/selftest.py +287 -0
  448. package/resources/skills/drama-production/upstream/skills/short-drama-assets/SKILL.md +76 -0
  449. package/resources/skills/drama-production/upstream/skills/short-drama-assets/agents/openai.yaml +4 -0
  450. package/resources/skills/drama-production/upstream/skills/short-drama-assets/assets/character-look.example.jsonl +3 -0
  451. package/resources/skills/drama-production/upstream/skills/short-drama-assets/assets/continuity.example.jsonl +4 -0
  452. package/resources/skills/drama-production/upstream/skills/short-drama-assets/assets/decisions.example.jsonl +5 -0
  453. package/resources/skills/drama-production/upstream/skills/short-drama-assets/assets/location-view.example.jsonl +3 -0
  454. package/resources/skills/drama-production/upstream/skills/short-drama-assets/assets/occurrences.example.jsonl +5 -0
  455. package/resources/skills/drama-production/upstream/skills/short-drama-assets/assets/prop-state.example.jsonl +3 -0
  456. package/resources/skills/drama-production/upstream/skills/short-drama-assets/examples/minimal/characters.jsonl +2 -0
  457. package/resources/skills/drama-production/upstream/skills/short-drama-assets/examples/minimal/looks.jsonl +2 -0
  458. package/resources/skills/drama-production/upstream/skills/short-drama-assets/references/asset-review-checklist.md +82 -0
  459. package/resources/skills/drama-production/upstream/skills/short-drama-assets/references/character-and-look.md +190 -0
  460. package/resources/skills/drama-production/upstream/skills/short-drama-assets/references/continuity-delta.md +120 -0
  461. package/resources/skills/drama-production/upstream/skills/short-drama-assets/references/continuity-lock.md +122 -0
  462. package/resources/skills/drama-production/upstream/skills/short-drama-assets/references/identity-vs-variant.md +140 -0
  463. package/resources/skills/drama-production/upstream/skills/short-drama-assets/references/location-and-view.md +84 -0
  464. package/resources/skills/drama-production/upstream/skills/short-drama-assets/references/occurrence-extraction.md +108 -0
  465. package/resources/skills/drama-production/upstream/skills/short-drama-assets/references/prop-and-state.md +126 -0
  466. package/resources/skills/drama-production/upstream/skills/short-drama-assets/references/stage-contract.md +43 -0
  467. package/resources/skills/drama-production/upstream/skills/short-drama-assets/references/voice-direction.md +179 -0
  468. package/resources/skills/drama-production/upstream/skills/short-drama-assets/scripts/asset_check.py +267 -0
  469. package/resources/skills/drama-production/upstream/skills/short-drama-assets/scripts/selftest.py +89 -0
  470. package/resources/skills/drama-production/upstream/skills/short-drama-develop/SKILL.md +157 -0
  471. package/resources/skills/drama-production/upstream/skills/short-drama-develop/agents/openai.yaml +4 -0
  472. package/resources/skills/drama-production/upstream/skills/short-drama-develop/assets/adaptation-map.example.jsonl +1 -0
  473. package/resources/skills/drama-production/upstream/skills/short-drama-develop/assets/creative-brief.md +120 -0
  474. package/resources/skills/drama-production/upstream/skills/short-drama-develop/assets/episode-map.jsonl +1 -0
  475. package/resources/skills/drama-production/upstream/skills/short-drama-develop/assets/story-engine.md +181 -0
  476. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/adaptation-craft.md +219 -0
  477. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/creative-reference-intake.md +57 -0
  478. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/director-brief-craft.md +113 -0
  479. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/episode-design.md +509 -0
  480. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-and-hook-playbook.md +240 -0
  481. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//344/272/262/345/255/220/351/232/220/347/247/230.md +112 -0
  482. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//344/273/231/344/276/240/344/277/256/347/234/237.md +51 -0
  483. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//345/212/250/344/275/234/344/273/273/345/212/241.md +51 -0
  484. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//345/217/244/350/243/205/346/235/203/350/260/213.md +51 -0
  485. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//345/244/215/344/273/207/346/211/223/350/204/270.md +51 -0
  486. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//345/256/266/345/272/255/345/205/263/347/263/273.md +51 -0
  487. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//346/202/254/347/226/221/350/247/204/345/210/231.md +51 -0
  488. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//347/224/237/346/264/273/346/265/201.md +51 -0
  489. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//347/240/264/351/225/234/351/207/215/345/234/206.md +109 -0
  490. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//350/201/214/345/234/272/345/226/234/345/211/247.md +51 -0
  491. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//350/261/252/351/227/250/345/251/232/346/201/213.md +51 -0
  492. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards//350/272/253/344/273/275/351/224/231/344/275/215.md +51 -0
  493. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/genre-cards.md +92 -0
  494. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/mechanism-loop.md +282 -0
  495. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/multi-episode-intake.md +128 -0
  496. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/premise-devices.md +211 -0
  497. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/reveal-reversal-payoff.md +99 -0
  498. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/serial-character-and-memory.md +293 -0
  499. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/stage-contract.md +88 -0
  500. package/resources/skills/drama-production/upstream/skills/short-drama-develop/references/story-craft.md +359 -0
  501. package/resources/skills/drama-production/upstream/skills/short-drama-develop/scripts/episode_intake.py +699 -0
  502. package/resources/skills/drama-production/upstream/skills/short-drama-develop/scripts/selftest.py +47 -0
  503. package/resources/skills/drama-production/upstream/skills/short-drama-edit/SKILL.md +146 -0
  504. package/resources/skills/drama-production/upstream/skills/short-drama-edit/agents/openai.yaml +4 -0
  505. package/resources/skills/drama-production/upstream/skills/short-drama-edit/assets/remotion/README.md +60 -0
  506. package/resources/skills/drama-production/upstream/skills/short-drama-edit/assets/remotion/package.json +15 -0
  507. package/resources/skills/drama-production/upstream/skills/short-drama-edit/assets/remotion/remotion.config.ts +6 -0
  508. package/resources/skills/drama-production/upstream/skills/short-drama-edit/assets/remotion/src/Root.tsx +26 -0
  509. package/resources/skills/drama-production/upstream/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +88 -0
  510. package/resources/skills/drama-production/upstream/skills/short-drama-edit/assets/remotion/src/font.ts +46 -0
  511. package/resources/skills/drama-production/upstream/skills/short-drama-edit/assets/remotion/src/index.ts +4 -0
  512. package/resources/skills/drama-production/upstream/skills/short-drama-edit/assets/remotion/src/schema.ts +32 -0
  513. package/resources/skills/drama-production/upstream/skills/short-drama-edit/assets/remotion/tsconfig.json +13 -0
  514. package/resources/skills/drama-production/upstream/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +43 -0
  515. package/resources/skills/drama-production/upstream/skills/short-drama-edit/references/cut-craft.md +38 -0
  516. package/resources/skills/drama-production/upstream/skills/short-drama-edit/references/delivery-verify.md +29 -0
  517. package/resources/skills/drama-production/upstream/skills/short-drama-edit/references/generated-footage.md +54 -0
  518. package/resources/skills/drama-production/upstream/skills/short-drama-edit/references/sound-and-subtitles.md +60 -0
  519. package/resources/skills/drama-production/upstream/skills/short-drama-edit/references/stage-contract.md +51 -0
  520. package/resources/skills/drama-production/upstream/skills/short-drama-edit/scripts/edit_tool.py +1156 -0
  521. package/resources/skills/drama-production/upstream/skills/short-drama-edit/scripts/selftest.py +520 -0
  522. package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/SKILL.md +73 -0
  523. package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/agents/openai.yaml +4 -0
  524. package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/assets/image-prompt-spec.jsonl.md +187 -0
  525. package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/assets/image-prompts.md +42 -0
  526. package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/assets/lookdev-frame-spec.jsonl.md +128 -0
  527. package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/assets/lookdev-prompts.md +34 -0
  528. package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/examples/minimal-image-prompt-specs.jsonl +2 -0
  529. package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/references/character-and-look.md +153 -0
  530. package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/references/common-recipe.md +241 -0
  531. package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/references/edit-and-revision.md +110 -0
  532. package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/references/location-plate.md +151 -0
  533. package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/references/look-and-state-variant.md +97 -0
  534. package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/references/lookdev-frame.md +34 -0
  535. package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/references/production-sheet-recipes.md +177 -0
  536. package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/references/prop-plate.md +137 -0
  537. package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/references/review-and-fixtures.md +92 -0
  538. package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/references/stage-contract.md +32 -0
  539. package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/scripts/image_prompt_check.py +443 -0
  540. package/resources/skills/drama-production/upstream/skills/short-drama-image-prompts/scripts/selftest.py +95 -0
  541. package/resources/skills/drama-production/upstream/skills/short-drama-novel-analyze/SKILL.md +245 -0
  542. package/resources/skills/drama-production/upstream/skills/short-drama-novel-analyze/agents/openai.yaml +4 -0
  543. package/resources/skills/drama-production/upstream/skills/short-drama-novel-analyze/assets/episode-candidate.example.jsonl +3 -0
  544. package/resources/skills/drama-production/upstream/skills/short-drama-novel-analyze/references/adaptation-triage.md +116 -0
  545. package/resources/skills/drama-production/upstream/skills/short-drama-novel-analyze/references/adaptation-value.md +124 -0
  546. package/resources/skills/drama-production/upstream/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +155 -0
  547. package/resources/skills/drama-production/upstream/skills/short-drama-novel-analyze/references/chapter-extraction.md +144 -0
  548. package/resources/skills/drama-production/upstream/skills/short-drama-novel-analyze/references/stage-contract.md +67 -0
  549. package/resources/skills/drama-production/upstream/skills/short-drama-novel-analyze/scripts/novel_index.py +651 -0
  550. package/resources/skills/drama-production/upstream/skills/short-drama-novel-analyze/scripts/selftest.py +51 -0
  551. package/resources/skills/drama-production/upstream/skills/short-drama-produce/SKILL.md +183 -0
  552. package/resources/skills/drama-production/upstream/skills/short-drama-produce/agents/openai.yaml +4 -0
  553. package/resources/skills/drama-production/upstream/skills/short-drama-produce/references/adapter-contract.md +219 -0
  554. package/resources/skills/drama-production/upstream/skills/short-drama-produce/references/providers/gpt-image-2.md +24 -0
  555. package/resources/skills/drama-production/upstream/skills/short-drama-produce/references/providers/minimax-h3-video.md +86 -0
  556. package/resources/skills/drama-production/upstream/skills/short-drama-produce/references/providers/minimax-music.md +25 -0
  557. package/resources/skills/drama-production/upstream/skills/short-drama-produce/references/providers/minimax-speech.md +41 -0
  558. package/resources/skills/drama-production/upstream/skills/short-drama-produce/references/providers/seedance.md +64 -0
  559. package/resources/skills/drama-production/upstream/skills/short-drama-produce/scripts/fixture_adapter.py +77 -0
  560. package/resources/skills/drama-production/upstream/skills/short-drama-produce/scripts/production_tool.py +2205 -0
  561. package/resources/skills/drama-production/upstream/skills/short-drama-produce/scripts/provider_adapters.py +1747 -0
  562. package/resources/skills/drama-production/upstream/skills/short-drama-produce/scripts/selftest.py +135 -0
  563. package/resources/skills/drama-production/upstream/skills/short-drama-review/SKILL.md +118 -0
  564. package/resources/skills/drama-production/upstream/skills/short-drama-review/agents/openai.yaml +4 -0
  565. package/resources/skills/drama-production/upstream/skills/short-drama-review/assets/finding-template.jsonl +2 -0
  566. package/resources/skills/drama-production/upstream/skills/short-drama-review/assets/supersession-decision.example.json +42 -0
  567. package/resources/skills/drama-production/upstream/skills/short-drama-review/assets/verdict-template.json +32 -0
  568. package/resources/skills/drama-production/upstream/skills/short-drama-review/examples/minimal-findings.jsonl +2 -0
  569. package/resources/skills/drama-production/upstream/skills/short-drama-review/examples/minimal-verdict.json +34 -0
  570. package/resources/skills/drama-production/upstream/skills/short-drama-review/references/anti-template-repair.md +109 -0
  571. package/resources/skills/drama-production/upstream/skills/short-drama-review/references/production-quality-gates.md +276 -0
  572. package/resources/skills/drama-production/upstream/skills/short-drama-review/references/project-calibration.md +83 -0
  573. package/resources/skills/drama-production/upstream/skills/short-drama-review/references/review-method.md +81 -0
  574. package/resources/skills/drama-production/upstream/skills/short-drama-review/references/rubric-assets-prompts.md +111 -0
  575. package/resources/skills/drama-production/upstream/skills/short-drama-review/references/rubric-source-analysis.md +108 -0
  576. package/resources/skills/drama-production/upstream/skills/short-drama-review/references/rubric-story-script.md +264 -0
  577. package/resources/skills/drama-production/upstream/skills/short-drama-review/references/rubric-visual-motion.md +184 -0
  578. package/resources/skills/drama-production/upstream/skills/short-drama-review/references/stage-contract.md +28 -0
  579. package/resources/skills/drama-production/upstream/skills/short-drama-review/scripts/review_check.py +325 -0
  580. package/resources/skills/drama-production/upstream/skills/short-drama-review/scripts/selftest.py +87 -0
  581. package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/SKILL.md +175 -0
  582. package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/agents/openai.yaml +4 -0
  583. package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/assets/coverage-audition.example.jsonl +2 -0
  584. package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/assets/coverage-template.json +49 -0
  585. package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/assets/keyframe-prompts.md +36 -0
  586. package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/assets/keyframe-template.jsonl +2 -0
  587. package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/assets/revision-lineage.fragment.json +12 -0
  588. package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/assets/scene-visual-plan.example.jsonl +2 -0
  589. package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/assets/shot-template.jsonl +2 -0
  590. package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/blocking-playbooks.md +216 -0
  591. package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/comic-keyframe-lexicon.md +156 -0
  592. package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/coverage-audition.md +34 -0
  593. package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/keyframe-craft.md +174 -0
  594. package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/lighting-craft.md +39 -0
  595. package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/production-shot-grammar.md +279 -0
  596. package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/review-and-fixtures.md +36 -0
  597. package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/scene-visual-plan.md +30 -0
  598. package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/screenplay-to-keyframe-example.md +155 -0
  599. package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/shot-craft.md +266 -0
  600. package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/shot-revision-identity.md +25 -0
  601. package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/references/stage-contract.md +57 -0
  602. package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/scripts/selftest.py +191 -0
  603. package/resources/skills/drama-production/upstream/skills/short-drama-storyboard/scripts/storyboard_check.py +824 -0
  604. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/SKILL.md +154 -0
  605. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/agents/openai.yaml +4 -0
  606. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/assets/coverage-scope.fragment.json +21 -0
  607. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/assets/delivery-container.jsonl.md +101 -0
  608. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/assets/motion-spec.jsonl.md +217 -0
  609. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/assets/motion-terminal.example.jsonl +2 -0
  610. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/assets/music-spec.jsonl.md +31 -0
  611. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/assets/performance.fragment.json +38 -0
  612. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/assets/video-prompts.md +67 -0
  613. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/examples/minimal-music-specs.jsonl +2 -0
  614. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/camera-audio-continuity.md +259 -0
  615. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/delivery-profile.md +273 -0
  616. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/generability.md +93 -0
  617. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/minimax-h3.md +253 -0
  618. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/motion-recipe.md +315 -0
  619. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/performance-action-timing.md +209 -0
  620. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/production-prompt-grammar.md +304 -0
  621. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/review-and-fixtures.md +139 -0
  622. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/seedance-2.0.md +50 -0
  623. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/seedance-2.5.md +122 -0
  624. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/stage-contract.md +60 -0
  625. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/references/target-model-profile.md +116 -0
  626. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/scripts/container_check.py +329 -0
  627. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/scripts/motion_timing_check.py +450 -0
  628. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/scripts/music_spec_check.py +327 -0
  629. package/resources/skills/drama-production/upstream/skills/short-drama-video-prompts/scripts/selftest.py +82 -0
  630. package/resources/skills/drama-production/upstream/skills/short-drama-write/SKILL.md +99 -0
  631. package/resources/skills/drama-production/upstream/skills/short-drama-write/agents/openai.yaml +4 -0
  632. package/resources/skills/drama-production/upstream/skills/short-drama-write/assets/beats.jsonl +2 -0
  633. package/resources/skills/drama-production/upstream/skills/short-drama-write/assets/episode-card-standalone.json +120 -0
  634. package/resources/skills/drama-production/upstream/skills/short-drama-write/assets/episode-card.json +55 -0
  635. package/resources/skills/drama-production/upstream/skills/short-drama-write/assets/screenplay.md +36 -0
  636. package/resources/skills/drama-production/upstream/skills/short-drama-write/assets/voice-record-sheet.jsonl.md +89 -0
  637. package/resources/skills/drama-production/upstream/skills/short-drama-write/references/dialogue-craft.md +526 -0
  638. package/resources/skills/drama-production/upstream/skills/short-drama-write/references/production-format-dialect.md +273 -0
  639. package/resources/skills/drama-production/upstream/skills/short-drama-write/references/scene-handoff-capsule.md +25 -0
  640. package/resources/skills/drama-production/upstream/skills/short-drama-write/references/scene-sound-dramaturgy.md +43 -0
  641. package/resources/skills/drama-production/upstream/skills/short-drama-write/references/screenplay-format.md +266 -0
  642. package/resources/skills/drama-production/upstream/skills/short-drama-write/references/script-craft.md +745 -0
  643. package/resources/skills/drama-production/upstream/skills/short-drama-write/references/stage-contract.md +36 -0
  644. package/resources/skills/drama-production/upstream/skills/short-drama-write/references/substitutable-realization.md +107 -0
  645. package/resources/skills/drama-production/upstream/skills/short-drama-write/scripts/duration_estimate.py +309 -0
  646. package/resources/skills/drama-production/upstream/skills/short-drama-write/scripts/screenplay_index.py +1091 -0
  647. package/resources/skills/drama-production/upstream/skills/short-drama-write/scripts/selftest.py +229 -0
  648. package/resources/skills/drama-production/upstream/skills/short-drama-write/scripts/voice_sheet_check.py +416 -0
  649. package/resources/skills/viora-canvas-production/manifest.json +23 -0
  650. package/resources/skills/viora-canvas-production/references/canvas-contract.md +50 -0
  651. package/resources/skills/viora-canvas-production/references/creative-production.md +50 -0
  652. package/resources/skills/viora-canvas-production/references/generation-delivery.md +36 -0
  653. package/resources/skills/viora-canvas-production/references/production-ledger.md +25 -0
@@ -0,0 +1,1747 @@
1
+ #!/usr/bin/env python3
2
+ """Stdlib-only production adapters for supported media providers.
3
+
4
+ The public ``compile_*`` functions are deterministic and perform no I/O. The
5
+ CLI reads the confirmed production job from stdin and writes only the adapter
6
+ contract JSON to stdout after a provider result has been saved to a temporary
7
+ regular file.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import base64
14
+ import binascii
15
+ import json
16
+ import mimetypes
17
+ import os
18
+ import re
19
+ import secrets
20
+ import stat
21
+ import sys
22
+ import time
23
+ import urllib.error
24
+ import urllib.parse
25
+ import urllib.request
26
+ from collections.abc import Collection, Mapping, Sequence
27
+ from pathlib import Path
28
+ from typing import Any
29
+
30
+ OPENAI_MODEL = "gpt-image-2"
31
+ MINIMAX_MUSIC_MODEL = "music-3.0"
32
+ OPENAI_BASE_URL = "https://api.openai.com/v1"
33
+ SEEDANCE_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3"
34
+ MINIMAX_BASE_URL = "https://api.minimax.io/v1"
35
+ MINIMAX_VIDEO_BASE_URL = "https://api.minimax.io/v2"
36
+ MAX_JSON_RESPONSE = 128 * 1024 * 1024
37
+ MAX_OUTPUT_BYTES = 512 * 1024 * 1024
38
+ MAX_REFERENCE_BYTES = 50 * 1024 * 1024
39
+ MAX_MULTIPART_BYTES = 200 * 1024 * 1024
40
+ MAX_ERROR_BODY_BYTES = 64 * 1024
41
+ TERMINAL_FAILURES = {"failed", "cancelled", "canceled", "timeout", "expired"}
42
+ SEEDANCE_RATIOS = {"adaptive", "1:1", "3:4", "4:3", "9:16", "16:9", "21:9"}
43
+ SEEDANCE_REFERENCE_ROLES = {
44
+ "first_frame": "image_url",
45
+ "last_frame": "image_url",
46
+ "reference_image": "image_url",
47
+ "reference_video": "video_url",
48
+ "reference_audio": "audio_url",
49
+ }
50
+ # One opening frame and one closing frame, because each names a single position
51
+ # in the take. Note what is deliberately absent: the sibling adapter forbids
52
+ # mixing frame conditioning with reference conditioning, and that rule belongs
53
+ # to that provider's reference, not to this one. Do not carry it across.
54
+ SEEDANCE_SINGULAR_ROLES = {"first_frame", "last_frame"}
55
+ MINIMAX_VIDEO_RATIOS = {"adaptive", "1:1", "3:4", "4:3", "9:16", "16:9", "21:9"}
56
+ MINIMAX_VIDEO_RESOLUTIONS = {"480P", "768P", "2K"}
57
+ MINIMAX_VIDEO_PROMPT_LIMIT = 7000
58
+ MINIMAX_SPEECH_TEXT_LIMIT = 5000
59
+ MINIMAX_VIDEO_ROLES = {
60
+ "first_frame": "image_url",
61
+ "last_frame": "image_url",
62
+ "reference_image": "image_url",
63
+ "reference_video": "video_url",
64
+ "reference_audio": "audio_url",
65
+ }
66
+ # Both providers document a `data:<mime>;base64,<...>` URI as an accepted media
67
+ # input alongside a public URL, so a local project reference needs no upload
68
+ # service to reach them. The caps are MiniMax's published per-modality limits;
69
+ # Seedance publishes no numbers, so the same conservative guard is applied there
70
+ # and can be raised per deployment. Base64 inflates bytes by about a third, so
71
+ # the request cap is checked against the encoded size.
72
+ INLINE_REFERENCE_LIMITS = {
73
+ "image_url": 30 * 1024 * 1024,
74
+ "video_url": 50 * 1024 * 1024,
75
+ "audio_url": 15 * 1024 * 1024,
76
+ }
77
+ INLINE_REFERENCE_BODY_LIMIT = 64 * 1024 * 1024
78
+ INLINE_REFERENCE_MIME = {
79
+ ".png": "image/png",
80
+ ".jpg": "image/jpeg",
81
+ ".jpeg": "image/jpeg",
82
+ ".webp": "image/webp",
83
+ ".mp4": "video/mp4",
84
+ ".mov": "video/quicktime",
85
+ ".webm": "video/webm",
86
+ ".wav": "audio/wav",
87
+ ".mp3": "audio/mpeg",
88
+ ".m4a": "audio/mp4",
89
+ ".aac": "audio/aac",
90
+ ".flac": "audio/flac",
91
+ }
92
+ GPT_IMAGE_MIN_PIXELS = 655_360
93
+ GPT_IMAGE_MAX_PIXELS = 8_294_400
94
+ MINIMUM_PYTHON = (3, 9)
95
+ if sys.version_info < MINIMUM_PYTHON:
96
+ raise SystemExit("provider_adapters.py requires Python 3.9 or newer")
97
+
98
+
99
+ class AdapterFailure(RuntimeError):
100
+ """A safe-to-report adapter failure without provider response contents."""
101
+
102
+ def __init__(
103
+ self,
104
+ message: str,
105
+ *,
106
+ category: str = "provider_response",
107
+ code: str = "adapter_failure",
108
+ http_status: int | None = None,
109
+ request_id: str | None = None,
110
+ retryable: bool = False,
111
+ ) -> None:
112
+ super().__init__(message)
113
+ self.category = category
114
+ self.code = _safe_token(code) or "adapter_failure"
115
+ self.http_status = http_status
116
+ self.request_id = _safe_token(request_id)
117
+ self.retryable = retryable
118
+
119
+ def public(self, provider: str) -> dict[str, Any]:
120
+ result: dict[str, Any] = {
121
+ "provider": provider,
122
+ "category": self.category,
123
+ "code": self.code,
124
+ "retryable": self.retryable,
125
+ }
126
+ if self.http_status is not None:
127
+ result["http_status"] = self.http_status
128
+ if self.request_id is not None:
129
+ result["request_id"] = self.request_id
130
+ return result
131
+
132
+
133
+ def _safe_token(value: object) -> str | None:
134
+ if isinstance(value, str) and re.fullmatch(
135
+ r"[A-Za-z0-9][A-Za-z0-9._:-]{0,199}", value
136
+ ):
137
+ return value
138
+ if isinstance(value, int) and not isinstance(value, bool):
139
+ return str(value)
140
+ return None
141
+
142
+
143
+ def _request_id(headers: Mapping[str, Any]) -> str | None:
144
+ folded = {str(key).casefold(): value for key, value in headers.items()}
145
+ for name in ("x-request-id", "x-tt-logid", "x-trace-id", "trace-id"):
146
+ value = _safe_token(folded.get(name))
147
+ if value is not None:
148
+ return value
149
+ return None
150
+
151
+
152
+ def _provider_code(document: object) -> str | None:
153
+ if not isinstance(document, Mapping):
154
+ return None
155
+ error = document.get("error")
156
+ if isinstance(error, Mapping):
157
+ value = _safe_token(error.get("code"))
158
+ if value is not None:
159
+ return value
160
+ base_resp = document.get("base_resp")
161
+ if isinstance(base_resp, Mapping):
162
+ value = _safe_token(base_resp.get("status_code"))
163
+ if value not in {None, "0"}:
164
+ return value
165
+ return _safe_token(document.get("code"))
166
+
167
+
168
+ def _http_failure(provider: str, error: urllib.error.HTTPError) -> AdapterFailure:
169
+ status = error.code
170
+ try:
171
+ raw = error.read(MAX_ERROR_BODY_BYTES + 1)
172
+ document = json.loads(raw) if len(raw) <= MAX_ERROR_BODY_BYTES else None
173
+ except (OSError, UnicodeError, json.JSONDecodeError):
174
+ document = None
175
+ if status == 401:
176
+ category = "authentication"
177
+ elif status == 403:
178
+ category = "permission"
179
+ elif status == 429:
180
+ category = "rate_limit"
181
+ elif 500 <= status <= 599:
182
+ category = "server"
183
+ elif 400 <= status <= 499:
184
+ category = "invalid_request"
185
+ else:
186
+ category = "provider_response"
187
+ return AdapterFailure(
188
+ f"{provider} HTTP request failed",
189
+ category=category,
190
+ code=_provider_code(document) or f"http_{status}",
191
+ http_status=status,
192
+ request_id=_request_id(
193
+ dict(error.headers.items()) if error.headers is not None else {}
194
+ ),
195
+ retryable=status == 429 or 500 <= status <= 599,
196
+ )
197
+
198
+
199
+ def _require_job(job: Mapping[str, Any], modality: str) -> tuple[str, dict[str, Any]]:
200
+ if not isinstance(job, Mapping) or job.get("modality") != modality:
201
+ raise ValueError(f"adapter requires a {modality} job")
202
+ prompt = job.get("prompt")
203
+ if not isinstance(prompt, str) or not prompt.strip():
204
+ raise ValueError("job prompt must be non-empty")
205
+ parameters = job.get("parameters", {})
206
+ if not isinstance(parameters, Mapping):
207
+ raise ValueError("job parameters must be an object")
208
+ outputs = job.get("outputs")
209
+ if not isinstance(outputs, list) or len(outputs) != 1 or not isinstance(outputs[0], str):
210
+ raise ValueError("provider adapter requires exactly one output")
211
+ return prompt, dict(parameters)
212
+
213
+
214
+ def _prompt_with_reference_contract(
215
+ prompt: str,
216
+ job: Mapping[str, Any],
217
+ *,
218
+ prompt_language: str | None = None,
219
+ reference_tokens: Sequence[str] = (),
220
+ zh_reference_prefix: str = "参考",
221
+ zh_heading: str = "参考约束:",
222
+ ) -> str:
223
+ bindings = job.get("reference_bindings", [])
224
+ references = job.get("references", [])
225
+ if not bindings:
226
+ return prompt
227
+ if (
228
+ not isinstance(bindings, list)
229
+ or not isinstance(references, list)
230
+ or len(bindings) != len(references)
231
+ ):
232
+ raise ValueError("reference bindings must match job references")
233
+ if reference_tokens and len(reference_tokens) != len(references):
234
+ raise ValueError("reference tokens must match job references")
235
+ language = (prompt_language or "en").casefold()
236
+ instructions: list[str] = []
237
+ for index, binding in enumerate(bindings, 1):
238
+ if not isinstance(binding, Mapping) or binding.get("order") != index:
239
+ raise ValueError("reference binding order is invalid")
240
+ if binding.get("path") != references[index - 1]:
241
+ raise ValueError("reference binding path does not match job references")
242
+ label = binding.get("label")
243
+ role = binding.get("role")
244
+ may_control = binding.get("may_control")
245
+ must_not_control = binding.get("must_not_control")
246
+ if (
247
+ not isinstance(label, str)
248
+ or not label.strip()
249
+ or not isinstance(role, str)
250
+ or not role.strip()
251
+ or not isinstance(may_control, list)
252
+ or not may_control
253
+ or not all(isinstance(item, str) and item.strip() for item in may_control)
254
+ or not isinstance(must_not_control, list)
255
+ or not must_not_control
256
+ or not all(isinstance(item, str) and item.strip() for item in must_not_control)
257
+ ):
258
+ raise ValueError("reference binding semantics are invalid")
259
+ values = {
260
+ "order": index,
261
+ "reference": reference_tokens[index - 1] if reference_tokens else str(index),
262
+ "label": label.strip(),
263
+ "role": role.strip(),
264
+ "may": ", ".join(item.strip() for item in may_control),
265
+ "must": ", ".join(item.strip() for item in must_not_control),
266
+ }
267
+ if language.startswith("zh"):
268
+ instructions.append(
269
+ f"{zh_reference_prefix} "
270
+ + (
271
+ "{reference}({label}),用途 {role}。允许控制:{may}。"
272
+ "不得控制:{must}。".format(**values)
273
+ )
274
+ )
275
+ elif language.startswith("en"):
276
+ instructions.append(
277
+ "Reference {reference} ({label}), role {role}. May control: {may}. "
278
+ "Must not control: {must}.".format(**values)
279
+ )
280
+ else:
281
+ instructions.append(
282
+ "[REF {order} | {label} | {role}] [+] {may} [-] {must}".format(
283
+ **values
284
+ )
285
+ )
286
+ if language.startswith("zh"):
287
+ heading = zh_heading
288
+ elif language.startswith("en"):
289
+ heading = "Reference contract:"
290
+ else:
291
+ heading = "<REF_CONTRACT>"
292
+ return f"{prompt}\n\n{heading}\n" + "\n".join(instructions)
293
+
294
+
295
+ def _pop_prompt_language(parameters: dict[str, Any]) -> str | None:
296
+ value = parameters.pop("prompt_language", None)
297
+ if value is None:
298
+ return None
299
+ if not isinstance(value, str) or not value.strip() or len(value) > 64:
300
+ raise ValueError("prompt_language must be a non-empty bounded language tag")
301
+ return value.strip()
302
+
303
+
304
+ def _take(parameters: dict[str, Any], allowed: set[str]) -> dict[str, Any]:
305
+ unknown = set(parameters) - allowed
306
+ if unknown:
307
+ raise ValueError("unsupported provider parameters: " + ", ".join(sorted(unknown)))
308
+ return parameters
309
+
310
+
311
+ def compile_seedance_payload(
312
+ job: Mapping[str, Any],
313
+ *,
314
+ model: str,
315
+ reference_urls: Sequence[str] = (),
316
+ reference_roles: Sequence[str] = (),
317
+ allowed_ratios: Collection[str] | None = None,
318
+ duration_range: tuple[int, int] | None = None,
319
+ ) -> dict[str, Any]:
320
+ """Compile a video production job into the official Seedance task body.
321
+
322
+ ``model`` is deliberately mandatory: callers must obtain it from explicit
323
+ runtime configuration rather than assuming any Seedance release.
324
+ """
325
+ prompt, parameters = _require_job(job, "video")
326
+ if not isinstance(model, str) or not model.strip():
327
+ raise ValueError("Seedance model must be explicitly configured")
328
+ references = job.get("references", [])
329
+ if not isinstance(references, list) or len(reference_urls) != len(references):
330
+ raise ValueError("Seedance reference URLs must match job references")
331
+ if len(reference_roles) != len(reference_urls):
332
+ raise ValueError("Seedance reference roles must match job references")
333
+ if Path(job["outputs"][0]).suffix.casefold() != ".mp4":
334
+ raise ValueError("Seedance adapter requires an MP4 target")
335
+ parameters = _take(
336
+ parameters,
337
+ {
338
+ "duration",
339
+ "ratio",
340
+ "generate_audio",
341
+ "omni_reference_task_type",
342
+ "prompt_language",
343
+ },
344
+ )
345
+ prompt_language = _pop_prompt_language(parameters)
346
+ duration = parameters.get("duration")
347
+ if duration is not None and (
348
+ not isinstance(duration, int)
349
+ or isinstance(duration, bool)
350
+ or (duration != -1 and not 1 <= duration <= 30)
351
+ ):
352
+ raise ValueError("Seedance duration must be -1 or an integer from 1 to 30")
353
+ if duration is not None and duration != -1:
354
+ if duration_range is None:
355
+ raise ValueError("Seedance duration needs an explicit model profile")
356
+ minimum, maximum = duration_range
357
+ if not 1 <= minimum <= maximum <= 30 or not minimum <= duration <= maximum:
358
+ raise ValueError("Seedance duration is outside the configured model profile")
359
+ ratio = parameters.get("ratio")
360
+ if ratio is not None and (
361
+ not isinstance(ratio, str) or ratio.strip() not in SEEDANCE_RATIOS
362
+ ):
363
+ raise ValueError("Seedance ratio is outside the supported profile")
364
+ if ratio is not None:
365
+ configured_ratios = set(allowed_ratios or ())
366
+ if not configured_ratios:
367
+ raise ValueError("Seedance ratio needs an explicit model profile")
368
+ if not configured_ratios <= SEEDANCE_RATIOS or ratio.strip() not in configured_ratios:
369
+ raise ValueError("Seedance ratio is outside the configured model profile")
370
+ ratio = ratio.strip()
371
+ generate_audio = parameters.get("generate_audio")
372
+ if generate_audio is not None and not isinstance(generate_audio, bool):
373
+ raise ValueError("Seedance generate_audio must be a boolean")
374
+ task_type = parameters.get("omni_reference_task_type")
375
+ if task_type is not None and task_type not in {"auto", "reference", "edit", "extend"}:
376
+ raise ValueError("Seedance omni reference task type is invalid")
377
+ counters = {"image_url": 0, "video_url": 0, "audio_url": 0}
378
+ labels = {"image_url": "图片", "video_url": "视频", "audio_url": "音频"}
379
+ reference_tokens: list[str] = []
380
+ singular_seen: set[str] = set()
381
+ for role in reference_roles:
382
+ field = SEEDANCE_REFERENCE_ROLES.get(role)
383
+ if field is None:
384
+ raise ValueError(f"unsupported Seedance reference role: {role}")
385
+ if role in SEEDANCE_SINGULAR_ROLES:
386
+ if role in singular_seen:
387
+ raise ValueError(f"Seedance accepts one {role} reference")
388
+ singular_seen.add(role)
389
+ # A frame role still occupies a picture slot, so it keeps counting: the
390
+ # tokens are positional, and skipping one would point every later
391
+ # `@图片N` in the prose at the wrong picture.
392
+ counters[field] += 1
393
+ reference_tokens.append(f"@{labels[field]}{counters[field]}")
394
+ if task_type in {"edit", "extend"} and "reference_video" not in reference_roles:
395
+ raise ValueError(f"Seedance {task_type} requires a reference video")
396
+ if task_type == "edit" and (ratio != "adaptive" or duration != -1):
397
+ raise ValueError("Seedance edit requires adaptive ratio and duration -1")
398
+ if task_type == "extend" and ratio != "adaptive":
399
+ raise ValueError("Seedance extend requires an adaptive ratio")
400
+ text = _prompt_with_reference_contract(
401
+ prompt,
402
+ job,
403
+ prompt_language=prompt_language,
404
+ reference_tokens=reference_tokens,
405
+ zh_reference_prefix=(
406
+ "输入素材" if task_type in {"edit", "extend"} else "参考"
407
+ ),
408
+ zh_heading=(
409
+ "输入素材约束:" if task_type in {"edit", "extend"} else "参考约束:"
410
+ ),
411
+ )
412
+ content: list[dict[str, Any]] = [{"type": "text", "text": text}]
413
+ for index, url in enumerate(reference_urls):
414
+ if not isinstance(url, str) or not url:
415
+ raise ValueError("Seedance reference URL must be non-empty")
416
+ role = reference_roles[index]
417
+ field = SEEDANCE_REFERENCE_ROLES[role]
418
+ suffix = Path(str(references[index])).suffix.casefold()
419
+ expected = {
420
+ "image_url": {".png", ".jpg", ".jpeg", ".webp"},
421
+ "video_url": {".mp4", ".mov", ".webm"},
422
+ "audio_url": {".wav", ".mp3", ".m4a", ".aac", ".flac"},
423
+ }[field]
424
+ if suffix not in expected:
425
+ raise ValueError(f"Seedance {role} does not match the reference file type")
426
+ parsed = urllib.parse.urlparse(url)
427
+ if not (
428
+ (parsed.scheme == "https" and parsed.netloc)
429
+ or (
430
+ parsed.scheme == "asset"
431
+ and parsed.netloc.startswith("asset-")
432
+ and not parsed.path
433
+ )
434
+ or _is_inline_reference(url)
435
+ ):
436
+ raise ValueError(
437
+ "Seedance reference URL must be HTTPS, asset:// or a base64 data URI"
438
+ )
439
+ content.append({"type": field, field: {"url": url}, "role": role})
440
+ body: dict[str, Any] = {"model": model.strip(), "content": content}
441
+ if ratio is not None:
442
+ body["ratio"] = ratio
443
+ if duration is not None:
444
+ body["duration"] = duration
445
+ if generate_audio is not None:
446
+ body["generate_audio"] = generate_audio
447
+ if task_type is not None:
448
+ body["omni_reference_task_type"] = task_type
449
+ return body
450
+
451
+
452
+ def _seedance_runtime_profile(
453
+ ) -> tuple[frozenset[str] | None, tuple[int, int] | None]:
454
+ raw_ratios = os.environ.get("SEEDANCE_ALLOWED_RATIOS")
455
+ allowed_ratios = (
456
+ frozenset(item.strip() for item in raw_ratios.split(",") if item.strip())
457
+ if raw_ratios is not None
458
+ else None
459
+ )
460
+ minimum_raw = os.environ.get("SEEDANCE_MIN_DURATION")
461
+ maximum_raw = os.environ.get("SEEDANCE_MAX_DURATION")
462
+ if (minimum_raw is None) != (maximum_raw is None):
463
+ raise AdapterFailure(
464
+ "Seedance duration profile is incomplete",
465
+ category="configuration",
466
+ code="invalid_model_profile",
467
+ )
468
+ try:
469
+ duration_range = (
470
+ (int(minimum_raw), int(maximum_raw))
471
+ if minimum_raw is not None and maximum_raw is not None
472
+ else None
473
+ )
474
+ except ValueError as exc:
475
+ raise AdapterFailure(
476
+ "Seedance duration profile is invalid",
477
+ category="configuration",
478
+ code="invalid_model_profile",
479
+ ) from exc
480
+ if allowed_ratios is not None and (
481
+ not allowed_ratios or not allowed_ratios <= SEEDANCE_RATIOS
482
+ ):
483
+ raise AdapterFailure(
484
+ "Seedance ratio profile is invalid",
485
+ category="configuration",
486
+ code="invalid_model_profile",
487
+ )
488
+ if duration_range is not None and not (
489
+ 1 <= duration_range[0] <= duration_range[1] <= 30
490
+ ):
491
+ raise AdapterFailure(
492
+ "Seedance duration profile is invalid",
493
+ category="configuration",
494
+ code="invalid_model_profile",
495
+ )
496
+ return allowed_ratios, duration_range
497
+
498
+
499
+ def compile_minimax_h3_payload(
500
+ job: Mapping[str, Any],
501
+ *,
502
+ model: str,
503
+ reference_urls: Sequence[str] = (),
504
+ reference_roles: Sequence[str] = (),
505
+ allowed_ratios: Collection[str] | None = None,
506
+ allowed_resolutions: Collection[str] | None = None,
507
+ duration_range: tuple[int, int] | None = None,
508
+ ) -> dict[str, Any]:
509
+ """Compile a video job into the official MiniMax video-generation task body.
510
+
511
+ ``model`` is mandatory for the same reason it is for Seedance: which release
512
+ is enabled, and what it accepts, is deployment configuration, never an
513
+ assumption made here.
514
+ """
515
+ prompt, parameters = _require_job(job, "video")
516
+ if not isinstance(model, str) or not model.strip():
517
+ raise ValueError("MiniMax video model must be explicitly configured")
518
+ references = job.get("references", [])
519
+ if not isinstance(references, list) or len(reference_urls) != len(references):
520
+ raise ValueError("MiniMax reference URLs must match job references")
521
+ if len(reference_roles) != len(reference_urls):
522
+ raise ValueError("MiniMax reference roles must match job references")
523
+ if Path(job["outputs"][0]).suffix.casefold() != ".mp4":
524
+ raise ValueError("MiniMax video adapter requires an MP4 target")
525
+ parameters = _take(
526
+ parameters, {"duration", "ratio", "resolution", "prompt_language"}
527
+ )
528
+ prompt_language = _pop_prompt_language(parameters)
529
+
530
+ duration = parameters.get("duration")
531
+ if not isinstance(duration, int) or isinstance(duration, bool):
532
+ raise ValueError("MiniMax video duration must be an integer number of seconds")
533
+ if duration_range is None:
534
+ raise ValueError("MiniMax video duration needs an explicit model profile")
535
+ minimum, maximum = duration_range
536
+ if not 1 <= minimum <= maximum or not minimum <= duration <= maximum:
537
+ raise ValueError("MiniMax video duration is outside the configured model profile")
538
+
539
+ resolution = parameters.get("resolution")
540
+ if not isinstance(resolution, str) or resolution.strip() not in MINIMAX_VIDEO_RESOLUTIONS:
541
+ raise ValueError("MiniMax video resolution is outside the supported profile")
542
+ configured_resolutions = set(allowed_resolutions or ())
543
+ if not configured_resolutions:
544
+ raise ValueError("MiniMax video resolution needs an explicit model profile")
545
+ if (
546
+ not configured_resolutions <= MINIMAX_VIDEO_RESOLUTIONS
547
+ or resolution.strip() not in configured_resolutions
548
+ ):
549
+ raise ValueError("MiniMax video resolution is outside the configured model profile")
550
+
551
+ ratio = parameters.get("ratio")
552
+ if ratio is not None:
553
+ if not isinstance(ratio, str) or ratio.strip() not in MINIMAX_VIDEO_RATIOS:
554
+ raise ValueError("MiniMax video ratio is outside the supported profile")
555
+ configured_ratios = set(allowed_ratios or ())
556
+ if not configured_ratios:
557
+ raise ValueError("MiniMax video ratio needs an explicit model profile")
558
+ if (
559
+ not configured_ratios <= MINIMAX_VIDEO_RATIOS
560
+ or ratio.strip() not in configured_ratios
561
+ ):
562
+ raise ValueError("MiniMax video ratio is outside the configured model profile")
563
+
564
+ counters = {"image_url": 0, "video_url": 0, "audio_url": 0}
565
+ labels = {"image_url": "Picture", "video_url": "Video", "audio_url": "Audio"}
566
+ reference_tokens: list[str] = []
567
+ for role in reference_roles:
568
+ field = MINIMAX_VIDEO_ROLES.get(role)
569
+ if field is None:
570
+ raise ValueError(f"unsupported MiniMax reference role: {role}")
571
+ counters[field] += 1
572
+ reference_tokens.append(f"<{labels[field]} {counters[field]}>")
573
+ text = _prompt_with_reference_contract(
574
+ prompt,
575
+ job,
576
+ prompt_language=prompt_language,
577
+ reference_tokens=reference_tokens,
578
+ )
579
+ if len(text) > MINIMAX_VIDEO_PROMPT_LIMIT:
580
+ raise ValueError("MiniMax video prompt exceeds the provider limit")
581
+ content: list[dict[str, Any]] = [{"type": "text", "text": text}]
582
+ seen_roles: list[str] = []
583
+ for index, url in enumerate(reference_urls):
584
+ role = reference_roles[index]
585
+ if role not in MINIMAX_VIDEO_ROLES:
586
+ raise ValueError(f"unsupported MiniMax reference role: {role}")
587
+ if role in {"first_frame", "last_frame"} and role in seen_roles:
588
+ raise ValueError(f"MiniMax accepts one {role} reference")
589
+ seen_roles.append(role)
590
+ if not isinstance(url, str) or not url:
591
+ raise ValueError("MiniMax reference URL must be non-empty")
592
+ parsed = urllib.parse.urlparse(url)
593
+ if not (
594
+ (parsed.scheme == "https" and parsed.netloc)
595
+ or (parsed.scheme == "mm_file" and parsed.netloc and not parsed.path)
596
+ or _is_inline_reference(url)
597
+ ):
598
+ raise ValueError(
599
+ "MiniMax reference URL must be HTTPS, mm_file:// or a base64 data URI"
600
+ )
601
+ field = MINIMAX_VIDEO_ROLES[role]
602
+ content.append({"type": field, field: {"url": url}, "role": role})
603
+ frame_roles = {"first_frame", "last_frame"}
604
+ reference_roles_present = {"reference_image", "reference_video", "reference_audio"}
605
+ if frame_roles.intersection(seen_roles) and reference_roles_present.intersection(
606
+ seen_roles
607
+ ):
608
+ raise ValueError(
609
+ "MiniMax frame conditioning cannot be mixed with reference conditioning"
610
+ )
611
+ if ratio is None and len(content) == 1:
612
+ raise ValueError("MiniMax text-to-video requires an explicit ratio")
613
+ body: dict[str, Any] = {
614
+ "model": model.strip(),
615
+ "content": content,
616
+ "duration": duration,
617
+ "resolution": resolution.strip(),
618
+ }
619
+ if ratio is not None:
620
+ if ratio.strip() == "adaptive" and len(content) == 1:
621
+ raise ValueError("MiniMax text-to-video cannot use an adaptive ratio")
622
+ body["ratio"] = ratio.strip()
623
+ return body
624
+
625
+
626
+ def _minimax_video_runtime_profile() -> tuple[
627
+ frozenset[str] | None, frozenset[str] | None, tuple[int, int] | None
628
+ ]:
629
+ def _set(name: str, supported: Collection[str]) -> frozenset[str] | None:
630
+ raw = os.environ.get(name)
631
+ if raw is None:
632
+ return None
633
+ values = frozenset(item.strip() for item in raw.split(",") if item.strip())
634
+ if not values or not values <= set(supported):
635
+ raise AdapterFailure(
636
+ f"MiniMax video profile is invalid: {name}",
637
+ category="configuration",
638
+ code="invalid_model_profile",
639
+ )
640
+ return values
641
+
642
+ allowed_ratios = _set("MINIMAX_VIDEO_RATIOS", MINIMAX_VIDEO_RATIOS)
643
+ allowed_resolutions = _set("MINIMAX_VIDEO_RESOLUTIONS", MINIMAX_VIDEO_RESOLUTIONS)
644
+ minimum_raw = os.environ.get("MINIMAX_VIDEO_MIN_DURATION")
645
+ maximum_raw = os.environ.get("MINIMAX_VIDEO_MAX_DURATION")
646
+ if (minimum_raw is None) != (maximum_raw is None):
647
+ raise AdapterFailure(
648
+ "MiniMax video duration profile is incomplete",
649
+ category="configuration",
650
+ code="invalid_model_profile",
651
+ )
652
+ try:
653
+ duration_range = (
654
+ (int(minimum_raw), int(maximum_raw))
655
+ if minimum_raw is not None and maximum_raw is not None
656
+ else None
657
+ )
658
+ except ValueError as exc:
659
+ raise AdapterFailure(
660
+ "MiniMax video duration profile is invalid",
661
+ category="configuration",
662
+ code="invalid_model_profile",
663
+ ) from exc
664
+ if duration_range is not None and not 1 <= duration_range[0] <= duration_range[1]:
665
+ raise AdapterFailure(
666
+ "MiniMax video duration profile is invalid",
667
+ category="configuration",
668
+ code="invalid_model_profile",
669
+ )
670
+ return allowed_ratios, allowed_resolutions, duration_range
671
+
672
+
673
+ def compile_gpt_image_2_payload(job: Mapping[str, Any]) -> dict[str, Any]:
674
+ """Compile an image job into GPT Image 2 generation/edit fields."""
675
+ prompt, parameters = _require_job(job, "image")
676
+ parameters = _take(
677
+ parameters,
678
+ {
679
+ "width",
680
+ "height",
681
+ "size",
682
+ "quality",
683
+ "background",
684
+ "moderation",
685
+ "prompt_language",
686
+ },
687
+ )
688
+ prompt_language = _pop_prompt_language(parameters)
689
+ prompt = _prompt_with_reference_contract(
690
+ prompt, job, prompt_language=prompt_language
691
+ )
692
+ if len(prompt) > 32000:
693
+ raise ValueError("GPT Image prompt exceeds the provider limit")
694
+ width = parameters.pop("width", None)
695
+ height = parameters.pop("height", None)
696
+ if (width is None) != (height is None) or (width is not None and "size" in parameters):
697
+ raise ValueError("use either both width/height or size")
698
+ if width is not None:
699
+ if not isinstance(width, int) or not isinstance(height, int) or width <= 0 or height <= 0:
700
+ raise ValueError("image dimensions must be positive integers")
701
+ if width % 16 or height % 16 or not (1 / 3 <= width / height <= 3):
702
+ raise ValueError("GPT Image dimensions must be divisible by 16 with ratio between 1:3 and 3:1")
703
+ parameters["size"] = f"{width}x{height}"
704
+ size = parameters.get("size")
705
+ if size != "auto" and size is not None:
706
+ if not isinstance(size, str) or "x" not in size:
707
+ raise ValueError("GPT Image size is invalid")
708
+ try:
709
+ parsed_width, parsed_height = (int(part) for part in size.split("x", 1))
710
+ except ValueError as exc:
711
+ raise ValueError("GPT Image size is invalid") from exc
712
+ if (
713
+ parsed_width <= 0
714
+ or parsed_height <= 0
715
+ or parsed_width % 16
716
+ or parsed_height % 16
717
+ or not (1 / 3 <= parsed_width / parsed_height <= 3)
718
+ or parsed_width > 3840
719
+ or parsed_height > 3840
720
+ or not (
721
+ GPT_IMAGE_MIN_PIXELS
722
+ <= parsed_width * parsed_height
723
+ <= GPT_IMAGE_MAX_PIXELS
724
+ )
725
+ ):
726
+ raise ValueError("GPT Image size violates official dimension constraints")
727
+ if parameters.get("background") == "transparent":
728
+ raise ValueError("GPT Image 2 transparent background is unsupported")
729
+ if "background" in parameters and parameters["background"] not in {"auto", "opaque"}:
730
+ raise ValueError("GPT Image background is invalid")
731
+ if "quality" in parameters and parameters["quality"] not in {"auto", "low", "medium", "high"}:
732
+ raise ValueError("GPT Image quality is invalid")
733
+ if "moderation" in parameters and parameters["moderation"] not in {"auto", "low"}:
734
+ raise ValueError("GPT Image moderation is invalid")
735
+ suffix = Path(job["outputs"][0]).suffix.casefold()
736
+ formats = {".png": "png", ".jpg": "jpeg", ".jpeg": "jpeg", ".webp": "webp"}
737
+ if suffix not in formats:
738
+ raise ValueError("unsupported GPT Image output extension")
739
+ references = job.get("references", [])
740
+ if not isinstance(references, list) or len(references) > 16:
741
+ raise ValueError("GPT Image accepts at most sixteen references")
742
+ if any(
743
+ not isinstance(reference, str)
744
+ or Path(reference).suffix.casefold() not in {".png", ".jpg", ".jpeg", ".webp"}
745
+ for reference in references
746
+ ):
747
+ raise ValueError("GPT Image references must be supported image files")
748
+ return {
749
+ "model": OPENAI_MODEL,
750
+ "prompt": prompt,
751
+ "n": 1,
752
+ "output_format": formats[suffix],
753
+ **parameters,
754
+ }
755
+
756
+
757
+ MINIMAX_SPEECH_EMOTIONS = {
758
+ "happy", "sad", "angry", "fearful", "disgusted", "surprised", "neutral",
759
+ }
760
+
761
+
762
+ def compile_minimax_speech_payload(job: Mapping[str, Any]) -> dict[str, Any]:
763
+ """Compile a tts job into the official MiniMax speech JSON body.
764
+
765
+ The voice catalogue is deliberately not enumerated here. Which preset voices
766
+ an account can reach depends on the model and the account, no published list
767
+ is authoritative for both, and a list frozen into this file would either
768
+ refuse a voice that works or vouch for one that does not. The document owns
769
+ the value and a reviewer reads it; the adapter only checks its shape.
770
+ """
771
+
772
+ text, parameters = _require_job(job, "tts")
773
+ parameters = _take(
774
+ parameters,
775
+ {"model", "voice_id", "speed", "vol", "pitch", "emotion",
776
+ "sample_rate", "bitrate", "format"},
777
+ )
778
+ model = parameters.pop("model", "")
779
+ if not isinstance(model, str) or not model.strip():
780
+ raise ValueError("MiniMax speech model must be explicitly configured")
781
+ voice_id = parameters.pop("voice_id", "")
782
+ if not isinstance(voice_id, str) or not voice_id.strip() or voice_id != voice_id.strip():
783
+ raise ValueError("MiniMax speech requires a confirmed voice_id")
784
+ if len(text) > MINIMAX_SPEECH_TEXT_LIMIT:
785
+ raise ValueError("MiniMax speech text exceeds the provider limit")
786
+
787
+ target_format = Path(job["outputs"][0]).suffix.casefold().lstrip(".")
788
+ requested_format = parameters.pop("format", target_format)
789
+ if requested_format != target_format or requested_format not in {"mp3", "wav"}:
790
+ raise ValueError("MiniMax output format must match a supported target extension")
791
+ sample_rate = parameters.pop("sample_rate", 32000)
792
+ bitrate = parameters.pop("bitrate", 128000)
793
+ if sample_rate not in {16000, 24000, 32000, 44100}:
794
+ raise ValueError("unsupported MiniMax sample rate")
795
+ if bitrate not in {32000, 64000, 128000, 256000}:
796
+ raise ValueError("unsupported MiniMax bitrate")
797
+
798
+ voice_setting: dict[str, Any] = {"voice_id": voice_id}
799
+ emotion = parameters.pop("emotion", None)
800
+ if emotion is not None:
801
+ if emotion not in MINIMAX_SPEECH_EMOTIONS:
802
+ raise ValueError("unsupported MiniMax speech emotion")
803
+ voice_setting["emotion"] = emotion
804
+ for name, low, high in (("speed", 0.5, 2.0), ("vol", 0.1, 10.0)):
805
+ value = parameters.pop(name, None)
806
+ if value is None:
807
+ continue
808
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
809
+ raise ValueError(f"MiniMax speech {name} must be a number")
810
+ if not low <= float(value) <= high:
811
+ raise ValueError(f"MiniMax speech {name} is outside the supported range")
812
+ voice_setting[name] = float(value)
813
+ pitch = parameters.pop("pitch", None)
814
+ if pitch is not None:
815
+ if isinstance(pitch, bool) or not isinstance(pitch, int) or not -12 <= pitch <= 12:
816
+ raise ValueError("MiniMax speech pitch is outside the supported range")
817
+ voice_setting["pitch"] = pitch
818
+
819
+ return {
820
+ "model": model.strip(),
821
+ "text": text,
822
+ "stream": False,
823
+ "output_format": "hex",
824
+ "voice_setting": voice_setting,
825
+ "audio_setting": {
826
+ "sample_rate": sample_rate,
827
+ "bitrate": bitrate,
828
+ "format": requested_format,
829
+ },
830
+ }
831
+
832
+
833
+ def compile_minimax_music_payload(job: Mapping[str, Any]) -> dict[str, Any]:
834
+ """Compile an audio job into the official MiniMax Music 3.0 JSON body."""
835
+ prompt, parameters = _require_job(job, "music")
836
+ parameters = _take(
837
+ parameters,
838
+ {"lyrics", "sample_rate", "bitrate", "format", "lyrics_optimizer", "is_instrumental"},
839
+ )
840
+ target_format = Path(job["outputs"][0]).suffix.casefold().lstrip(".")
841
+ requested_format = parameters.pop("format", target_format)
842
+ if requested_format != target_format or requested_format not in {"mp3", "wav"}:
843
+ raise ValueError("MiniMax output format must match a supported target extension")
844
+ lyrics = parameters.pop("lyrics", None)
845
+ instrumental = parameters.pop("is_instrumental", False)
846
+ optimizer = parameters.pop("lyrics_optimizer", False)
847
+ if not isinstance(instrumental, bool) or not isinstance(optimizer, bool):
848
+ raise ValueError("MiniMax boolean parameters must be booleans")
849
+ if optimizer:
850
+ raise ValueError("MiniMax lyrics_optimizer is forbidden for confirmed production")
851
+ if instrumental and lyrics not in {None, ""}:
852
+ raise ValueError("MiniMax instrumental music must not carry lyrics")
853
+ if not instrumental and (not isinstance(lyrics, str) or not lyrics.strip()):
854
+ raise ValueError("MiniMax vocal music requires confirmed lyrics")
855
+ if len(prompt) > 2000 or (isinstance(lyrics, str) and len(lyrics) > 3500):
856
+ raise ValueError("MiniMax prompt or lyrics exceeds the provider limit")
857
+ sample_rate = parameters.pop("sample_rate", 44100)
858
+ bitrate = parameters.pop("bitrate", 256000)
859
+ if sample_rate not in {16000, 24000, 32000, 44100}:
860
+ raise ValueError("unsupported MiniMax sample rate")
861
+ if bitrate not in {32000, 64000, 128000, 256000}:
862
+ raise ValueError("unsupported MiniMax bitrate")
863
+ body: dict[str, Any] = {
864
+ "model": MINIMAX_MUSIC_MODEL,
865
+ "prompt": prompt,
866
+ "stream": False,
867
+ "output_format": "hex",
868
+ "audio_setting": {
869
+ "sample_rate": sample_rate,
870
+ "bitrate": bitrate,
871
+ "format": requested_format,
872
+ },
873
+ "lyrics_optimizer": False,
874
+ "is_instrumental": instrumental,
875
+ }
876
+ if lyrics is not None:
877
+ if not isinstance(lyrics, str):
878
+ raise ValueError("MiniMax lyrics must be a string")
879
+ body["lyrics"] = lyrics
880
+ return body
881
+
882
+
883
+ def _base_url(env_name: str, default: str) -> str:
884
+ value = os.environ.get(env_name, default).rstrip("/")
885
+ parsed = urllib.parse.urlparse(value)
886
+ if parsed.scheme != "https" or not parsed.netloc or parsed.username or parsed.password:
887
+ raise AdapterFailure(
888
+ "provider base URL is invalid",
889
+ category="configuration",
890
+ code="invalid_base_url",
891
+ )
892
+ return value
893
+
894
+
895
+ def _credential(name: str) -> str:
896
+ value = os.environ.get(name)
897
+ if not value:
898
+ raise AdapterFailure(
899
+ "provider credential is not configured",
900
+ category="configuration",
901
+ code="missing_credential",
902
+ )
903
+ return value
904
+
905
+
906
+ def _request_json(
907
+ url: str,
908
+ *,
909
+ provider: str,
910
+ method: str = "POST",
911
+ body: Mapping[str, Any] | None = None,
912
+ token: str,
913
+ ) -> tuple[dict[str, Any], Mapping[str, str]]:
914
+ data = None if body is None else json.dumps(body, ensure_ascii=False).encode("utf-8")
915
+ request = urllib.request.Request(url, data=data, method=method)
916
+ request.add_header("Authorization", f"Bearer {token}")
917
+ if body is not None:
918
+ request.add_header("Content-Type", "application/json")
919
+ try:
920
+ with urllib.request.urlopen(request, timeout=180) as response:
921
+ raw = response.read(MAX_JSON_RESPONSE + 1)
922
+ headers = dict(response.headers.items())
923
+ except urllib.error.HTTPError as exc:
924
+ raise _http_failure(provider, exc) from exc
925
+ except TimeoutError as exc:
926
+ raise AdapterFailure(
927
+ f"{provider} HTTP request timed out",
928
+ category="timeout",
929
+ code="request_timeout",
930
+ retryable=True,
931
+ ) from exc
932
+ except (urllib.error.URLError, OSError) as exc:
933
+ raise AdapterFailure(
934
+ f"{provider} HTTP request failed",
935
+ category="network",
936
+ code="network_error",
937
+ retryable=True,
938
+ ) from exc
939
+ if len(raw) > MAX_JSON_RESPONSE:
940
+ raise AdapterFailure(
941
+ f"{provider} response is too large", code="response_too_large"
942
+ )
943
+ try:
944
+ document = json.loads(raw)
945
+ except (UnicodeError, json.JSONDecodeError) as exc:
946
+ raise AdapterFailure(
947
+ f"{provider} returned invalid JSON", code="invalid_json"
948
+ ) from exc
949
+ if not isinstance(document, dict):
950
+ raise AdapterFailure(
951
+ f"{provider} returned an invalid response", code="invalid_response"
952
+ )
953
+ return document, headers
954
+
955
+
956
+ def _read_reference(path: Path) -> bytes:
957
+ before = path.lstat()
958
+ if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode):
959
+ raise ValueError("reference is not a regular file")
960
+ if before.st_size > MAX_REFERENCE_BYTES:
961
+ raise ValueError("reference exceeds the provider input size limit")
962
+ # Windows opens files in text mode unless told otherwise, which stops the
963
+ # read at the first 0x1A byte -- the seventh byte of every PNG signature.
964
+ flags = os.O_RDONLY | getattr(os, "O_BINARY", 0) | getattr(os, "O_NOFOLLOW", 0)
965
+ descriptor = os.open(path, flags)
966
+ try:
967
+ opened = os.fstat(descriptor)
968
+ if (
969
+ not stat.S_ISREG(opened.st_mode)
970
+ or (opened.st_dev, opened.st_ino) != (before.st_dev, before.st_ino)
971
+ ):
972
+ raise ValueError("reference changed while opening")
973
+ chunks: list[bytes] = []
974
+ size = 0
975
+ while chunk := os.read(descriptor, 1024 * 1024):
976
+ size += len(chunk)
977
+ if size > MAX_REFERENCE_BYTES:
978
+ raise ValueError("reference exceeds the provider input size limit")
979
+ chunks.append(chunk)
980
+ return b"".join(chunks)
981
+ finally:
982
+ os.close(descriptor)
983
+
984
+
985
+ def _multipart(fields: Mapping[str, Any], paths: Sequence[Path]) -> tuple[bytes, str]:
986
+ boundary = "short-drama-" + secrets.token_hex(16)
987
+ chunks: list[bytes] = []
988
+ for name, value in fields.items():
989
+ chunks.extend(
990
+ [
991
+ f"--{boundary}\r\n".encode(),
992
+ f'Content-Disposition: form-data; name="{name}"\r\n\r\n'.encode(),
993
+ str(value).lower().encode() if isinstance(value, bool) else str(value).encode("utf-8"),
994
+ b"\r\n",
995
+ ]
996
+ )
997
+ for path in paths:
998
+ content = _read_reference(path)
999
+ media_type = mimetypes.guess_type(path.name)[0] or "application/octet-stream"
1000
+ chunks.extend(
1001
+ [
1002
+ f"--{boundary}\r\n".encode(),
1003
+ f'Content-Disposition: form-data; name="image[]"; filename="{path.name}"\r\n'.encode(),
1004
+ f"Content-Type: {media_type}\r\n\r\n".encode(),
1005
+ content,
1006
+ b"\r\n",
1007
+ ]
1008
+ )
1009
+ chunks.append(f"--{boundary}--\r\n".encode())
1010
+ encoded = b"".join(chunks)
1011
+ if len(encoded) > MAX_MULTIPART_BYTES:
1012
+ raise ValueError("multipart provider input exceeds the total size limit")
1013
+ return encoded, f"multipart/form-data; boundary={boundary}"
1014
+
1015
+
1016
+ def _reference_paths(job: Mapping[str, Any]) -> list[Path]:
1017
+ raw_root = job.get("project_root")
1018
+ if not isinstance(raw_root, str) or not Path(raw_root).is_absolute():
1019
+ raise ValueError("project_root is invalid")
1020
+ root = Path(raw_root).resolve()
1021
+ if not root.is_dir():
1022
+ raise ValueError("project_root is invalid")
1023
+ result: list[Path] = []
1024
+ for reference in job.get("references", []):
1025
+ if not isinstance(reference, str):
1026
+ raise ValueError("reference path is invalid")
1027
+ path = (root / reference).resolve()
1028
+ try:
1029
+ path.relative_to(root)
1030
+ except ValueError as exc:
1031
+ raise ValueError("reference escapes project root") from exc
1032
+ if not path.is_file() or path.is_symlink():
1033
+ raise ValueError("reference is not a regular file")
1034
+ result.append(path)
1035
+ return result
1036
+
1037
+
1038
+ def _binding_roles(
1039
+ job: Mapping[str, Any], *, allowed: Mapping[str, str], provider: str
1040
+ ) -> list[str]:
1041
+ """The provider role of each reference, taken from the confirmed job.
1042
+
1043
+ `role` is the production-side translation of the creator document's 用途, so
1044
+ it is the job -- not this adapter -- that decides what a picture is for.
1045
+ """
1046
+ bindings = job.get("reference_bindings", [])
1047
+ references = job.get("references", [])
1048
+ if not isinstance(bindings, list) or len(bindings) != len(references):
1049
+ raise AdapterFailure(
1050
+ f"{provider} references need one reference_bindings entry each, "
1051
+ "carrying the provider role for that file",
1052
+ category="configuration",
1053
+ code="missing_reference_roles",
1054
+ )
1055
+ roles: list[str] = []
1056
+ for binding in bindings:
1057
+ role = binding.get("role") if isinstance(binding, Mapping) else None
1058
+ if not isinstance(role, str) or role not in allowed:
1059
+ raise AdapterFailure(
1060
+ f"{provider} reference role must be one of "
1061
+ + ", ".join(sorted(allowed))
1062
+ + f"; got {role!r}",
1063
+ category="configuration",
1064
+ code="invalid_reference_role",
1065
+ )
1066
+ roles.append(role)
1067
+ return roles
1068
+
1069
+
1070
+ def _inline_reference_urls(
1071
+ paths: Sequence[Path], roles: Sequence[str], *, allowed: Mapping[str, str], provider: str
1072
+ ) -> list[str]:
1073
+ """Encode local project references as `data:` URIs the provider accepts."""
1074
+ urls: list[str] = []
1075
+ total = 0
1076
+ for path, role in zip(paths, roles):
1077
+ field = allowed[role]
1078
+ suffix = path.suffix.casefold()
1079
+ mime = INLINE_REFERENCE_MIME.get(suffix)
1080
+ expected = field.split("_", 1)[0]
1081
+ if mime is None or not mime.startswith(expected):
1082
+ raise AdapterFailure(
1083
+ f"{provider} {role} does not accept a {suffix or 'suffixless'} file",
1084
+ category="configuration",
1085
+ code="invalid_reference_type",
1086
+ )
1087
+ try:
1088
+ content = path.read_bytes()
1089
+ except OSError as exc:
1090
+ raise AdapterFailure(
1091
+ f"{provider} could not read a project reference",
1092
+ category="configuration",
1093
+ code="unreadable_reference",
1094
+ ) from exc
1095
+ if not content:
1096
+ raise AdapterFailure(
1097
+ f"{provider} project reference is empty",
1098
+ category="configuration",
1099
+ code="empty_reference",
1100
+ )
1101
+ # The bytes have to be the media type the extension claims, or the
1102
+ # provider rejects a request this adapter said was well formed.
1103
+ _validate_media_content(path.name, content)
1104
+ limit = INLINE_REFERENCE_LIMITS[field]
1105
+ if len(content) > limit:
1106
+ raise AdapterFailure(
1107
+ f"{provider} reference exceeds the {limit // (1024 * 1024)}MB inline "
1108
+ "limit; host it and bind an HTTPS URL instead",
1109
+ category="configuration",
1110
+ code="reference_too_large",
1111
+ )
1112
+ encoded = base64.b64encode(content).decode("ascii")
1113
+ total += len(encoded)
1114
+ if total > INLINE_REFERENCE_BODY_LIMIT:
1115
+ raise AdapterFailure(
1116
+ f"{provider} inline references exceed the request body limit; "
1117
+ "host the largest ones and bind HTTPS URLs instead",
1118
+ category="configuration",
1119
+ code="reference_body_too_large",
1120
+ )
1121
+ urls.append(f"data:{mime};base64,{encoded}")
1122
+ return urls
1123
+
1124
+
1125
+ def _is_inline_reference(url: str) -> bool:
1126
+ return bool(re.fullmatch(r"data:[\w.+-]+/[\w.+-]+;base64,[A-Za-z0-9+/]+=*", url))
1127
+
1128
+
1129
+ def _validate_media_content(target: str, content: bytes) -> None:
1130
+ suffix = Path(target).suffix.casefold()
1131
+ signatures = {
1132
+ ".png": content.startswith(b"\x89PNG\r\n\x1a\n"),
1133
+ ".jpg": content.startswith(b"\xff\xd8\xff"),
1134
+ ".jpeg": content.startswith(b"\xff\xd8\xff"),
1135
+ ".webp": len(content) >= 12
1136
+ and content.startswith(b"RIFF")
1137
+ and content[8:12] == b"WEBP",
1138
+ ".mp3": content.startswith(b"ID3")
1139
+ or (
1140
+ len(content) >= 2
1141
+ and content[0] == 0xFF
1142
+ and content[1] & 0xE0 == 0xE0
1143
+ ),
1144
+ ".wav": len(content) >= 12
1145
+ and content.startswith(b"RIFF")
1146
+ and content[8:12] == b"WAVE",
1147
+ ".mp4": len(content) >= 12 and content[4:8] == b"ftyp",
1148
+ }
1149
+ if not signatures.get(suffix, False):
1150
+ raise AdapterFailure("provider output does not match the target media type")
1151
+
1152
+
1153
+ def _output_root(job: Mapping[str, Any]) -> Path:
1154
+ raw = job.get("output_root")
1155
+ if not isinstance(raw, str):
1156
+ raise ValueError("output_root is invalid")
1157
+ root = Path(raw)
1158
+ if not root.is_absolute():
1159
+ raise ValueError("output_root is invalid")
1160
+ try:
1161
+ details = root.lstat()
1162
+ except OSError as exc:
1163
+ raise ValueError("output_root is missing") from exc
1164
+ if stat.S_ISLNK(details.st_mode) or not stat.S_ISDIR(details.st_mode):
1165
+ raise ValueError("output_root is unsafe")
1166
+ return root
1167
+
1168
+
1169
+ def _temporary_output(
1170
+ job: Mapping[str, Any], target: str, content: bytes
1171
+ ) -> Path:
1172
+ if not content or len(content) > MAX_OUTPUT_BYTES:
1173
+ raise AdapterFailure("provider output is too large")
1174
+ _validate_media_content(target, content)
1175
+ path = _output_root(job) / ("result" + Path(target).suffix.casefold())
1176
+ with path.open("xb") as handle:
1177
+ handle.write(content)
1178
+ return path
1179
+
1180
+
1181
+ def _download(
1182
+ job: Mapping[str, Any],
1183
+ url: str,
1184
+ target: str,
1185
+ *,
1186
+ provider: str = "seedance",
1187
+ ) -> Path:
1188
+ parsed = urllib.parse.urlparse(url)
1189
+ if parsed.scheme != "https" or not parsed.netloc:
1190
+ raise AdapterFailure("provider output URL is invalid")
1191
+ path = _output_root(job) / ("result" + Path(target).suffix.casefold())
1192
+ size = 0
1193
+ try:
1194
+ with urllib.request.urlopen(url, timeout=180) as response, path.open("xb") as handle:
1195
+ while chunk := response.read(1024 * 1024):
1196
+ size += len(chunk)
1197
+ if size > MAX_OUTPUT_BYTES:
1198
+ raise AdapterFailure("provider output is too large")
1199
+ handle.write(chunk)
1200
+ except AdapterFailure:
1201
+ path.unlink(missing_ok=True)
1202
+ raise
1203
+ except urllib.error.HTTPError as exc:
1204
+ path.unlink(missing_ok=True)
1205
+ raise _http_failure(provider, exc) from exc
1206
+ except TimeoutError as exc:
1207
+ path.unlink(missing_ok=True)
1208
+ raise AdapterFailure(
1209
+ "provider output download timed out",
1210
+ category="timeout",
1211
+ code="download_timeout",
1212
+ retryable=True,
1213
+ ) from exc
1214
+ except (urllib.error.URLError, OSError) as exc:
1215
+ path.unlink(missing_ok=True)
1216
+ raise AdapterFailure(
1217
+ "provider output download failed",
1218
+ category="network",
1219
+ code="download_failed",
1220
+ retryable=True,
1221
+ ) from exc
1222
+ if size == 0:
1223
+ raise AdapterFailure("provider output is empty")
1224
+ with path.open("rb") as handle:
1225
+ _validate_media_content(target, handle.read(16))
1226
+ return path
1227
+
1228
+
1229
+ def _record_handle(job: Mapping[str, Any], provider_job_id: str) -> None:
1230
+ """Write the provider task id where the caller can find it after a crash.
1231
+
1232
+ A video task is billed at submission. Everything after that — polling,
1233
+ downloading — can be interrupted, and without this the caller is left with a
1234
+ live, already-paid task it has no id for. Written before the first poll, and
1235
+ deliberately best-effort: failing to record the handle must not fail a task
1236
+ that was submitted successfully.
1237
+ """
1238
+
1239
+ destination = job.get("handle_path")
1240
+ if not isinstance(destination, str) or not destination:
1241
+ return
1242
+ try:
1243
+ path = Path(destination)
1244
+ path.parent.mkdir(parents=True, exist_ok=True)
1245
+ temporary = path.with_name(f".{path.name}.{os.getpid()}.tmp")
1246
+ temporary.write_text(
1247
+ json.dumps({"provider_job_id": provider_job_id}, ensure_ascii=True),
1248
+ encoding="utf-8",
1249
+ )
1250
+ temporary.replace(path)
1251
+ except OSError:
1252
+ return
1253
+
1254
+
1255
+ def _collect_target(job: Mapping[str, Any]) -> str | None:
1256
+ value = job.get("collect_provider_job_id")
1257
+ if isinstance(value, str) and value:
1258
+ return value
1259
+ return None
1260
+
1261
+
1262
+ def _run_seedance(job: Mapping[str, Any]) -> tuple[Path, str]:
1263
+ token = _credential("ARK_API_KEY")
1264
+ base = _base_url("SEEDANCE_BASE_URL", SEEDANCE_BASE_URL)
1265
+ collecting = _collect_target(job)
1266
+ if collecting is not None:
1267
+ return _poll_seedance(job, base=base, token=token, task_id=collecting)
1268
+ model = os.environ.get("SEEDANCE_MODEL", "")
1269
+ references = _reference_paths(job)
1270
+ reference_roles = (
1271
+ _binding_roles(job, allowed=SEEDANCE_REFERENCE_ROLES, provider="Seedance")
1272
+ if references
1273
+ else []
1274
+ )
1275
+ reference_urls = _inline_reference_urls(
1276
+ references, reference_roles, allowed=SEEDANCE_REFERENCE_ROLES, provider="Seedance"
1277
+ )
1278
+ allowed_ratios, duration_range = _seedance_runtime_profile()
1279
+ body = compile_seedance_payload(
1280
+ job,
1281
+ model=model,
1282
+ reference_urls=reference_urls,
1283
+ reference_roles=reference_roles,
1284
+ allowed_ratios=allowed_ratios,
1285
+ duration_range=duration_range,
1286
+ )
1287
+ created, _ = _request_json(
1288
+ f"{base}/contents/generations/tasks",
1289
+ provider="seedance",
1290
+ body=body,
1291
+ token=token,
1292
+ )
1293
+ task_id = created.get("id")
1294
+ if not isinstance(task_id, str) or not task_id:
1295
+ raise AdapterFailure(
1296
+ "Seedance did not return a task id", code="missing_task_id"
1297
+ )
1298
+ # Billed from here on. Record the id before the first poll.
1299
+ _record_handle(job, task_id)
1300
+ return _poll_seedance(job, base=base, token=token, task_id=task_id)
1301
+
1302
+
1303
+ def _poll_seedance(
1304
+ job: Mapping[str, Any], *, base: str, token: str, task_id: str
1305
+ ) -> tuple[Path, str]:
1306
+ try:
1307
+ interval = float(os.environ.get("SEEDANCE_POLL_INTERVAL", "5"))
1308
+ deadline = time.monotonic() + float(os.environ.get("SEEDANCE_TIMEOUT_SECONDS", "1800"))
1309
+ except ValueError as exc:
1310
+ raise AdapterFailure("Seedance polling configuration is invalid") from exc
1311
+ if interval <= 0 or deadline <= time.monotonic():
1312
+ raise AdapterFailure("Seedance polling configuration is invalid")
1313
+ while time.monotonic() < deadline:
1314
+ status_doc, _ = _request_json(
1315
+ f"{base}/contents/generations/tasks/{urllib.parse.quote(task_id, safe='')}",
1316
+ provider="seedance",
1317
+ method="GET",
1318
+ token=token,
1319
+ )
1320
+ status = status_doc.get("status")
1321
+ if status == "succeeded":
1322
+ content = status_doc.get("content")
1323
+ url = content.get("video_url") if isinstance(content, Mapping) else None
1324
+ if not isinstance(url, str):
1325
+ raise AdapterFailure(
1326
+ "Seedance succeeded without a video URL",
1327
+ code="missing_video_url",
1328
+ request_id=task_id,
1329
+ )
1330
+ return _download(
1331
+ job,
1332
+ url,
1333
+ job["outputs"][0],
1334
+ provider="seedance",
1335
+ ), task_id
1336
+ if isinstance(status, str) and status.casefold() in TERMINAL_FAILURES:
1337
+ raise AdapterFailure(
1338
+ "Seedance task failed",
1339
+ code="task_" + status.casefold(),
1340
+ request_id=task_id,
1341
+ )
1342
+ if status not in {"queued", "in_progress", "running", "processing", "pending"}:
1343
+ raise AdapterFailure(
1344
+ "Seedance returned an unknown task status",
1345
+ code="unknown_task_status",
1346
+ request_id=task_id,
1347
+ )
1348
+ time.sleep(interval)
1349
+ raise AdapterFailure(
1350
+ "Seedance task polling timed out",
1351
+ category="timeout",
1352
+ code="task_poll_timeout",
1353
+ request_id=task_id,
1354
+ retryable=True,
1355
+ )
1356
+
1357
+
1358
+ def _run_openai(job: Mapping[str, Any]) -> tuple[Path, str | None]:
1359
+ token = _credential("OPENAI_API_KEY")
1360
+ body = compile_gpt_image_2_payload(job)
1361
+ base = _base_url("OPENAI_BASE_URL", OPENAI_BASE_URL)
1362
+ references = _reference_paths(job)
1363
+ if references:
1364
+ encoded, content_type = _multipart(body, references)
1365
+ request = urllib.request.Request(f"{base}/images/edits", data=encoded, method="POST")
1366
+ request.add_header("Authorization", f"Bearer {token}")
1367
+ request.add_header("Content-Type", content_type)
1368
+ try:
1369
+ with urllib.request.urlopen(request, timeout=300) as response:
1370
+ raw = response.read(MAX_JSON_RESPONSE + 1)
1371
+ request_id = response.headers.get("x-request-id")
1372
+ except urllib.error.HTTPError as exc:
1373
+ raise _http_failure("gpt-image-2", exc) from exc
1374
+ except TimeoutError as exc:
1375
+ raise AdapterFailure(
1376
+ "OpenAI HTTP request timed out",
1377
+ category="timeout",
1378
+ code="request_timeout",
1379
+ retryable=True,
1380
+ ) from exc
1381
+ except (urllib.error.URLError, OSError) as exc:
1382
+ raise AdapterFailure(
1383
+ "OpenAI HTTP request failed",
1384
+ category="network",
1385
+ code="network_error",
1386
+ retryable=True,
1387
+ ) from exc
1388
+ if len(raw) > MAX_JSON_RESPONSE:
1389
+ raise AdapterFailure(
1390
+ "OpenAI response is too large",
1391
+ code="response_too_large",
1392
+ request_id=request_id,
1393
+ )
1394
+ try:
1395
+ result = json.loads(raw)
1396
+ except (UnicodeError, json.JSONDecodeError) as exc:
1397
+ raise AdapterFailure(
1398
+ "OpenAI returned invalid JSON",
1399
+ code="invalid_json",
1400
+ request_id=request_id,
1401
+ ) from exc
1402
+ else:
1403
+ result, headers = _request_json(
1404
+ f"{base}/images/generations",
1405
+ provider="gpt-image-2",
1406
+ body=body,
1407
+ token=token,
1408
+ )
1409
+ request_id = headers.get("x-request-id")
1410
+ data = result.get("data") if isinstance(result, Mapping) else None
1411
+ image = data[0].get("b64_json") if isinstance(data, list) and len(data) == 1 and isinstance(data[0], Mapping) else None
1412
+ if not isinstance(image, str):
1413
+ raise AdapterFailure(
1414
+ "OpenAI did not return exactly one image",
1415
+ code="missing_image",
1416
+ request_id=request_id,
1417
+ )
1418
+ try:
1419
+ content = base64.b64decode(image, validate=True)
1420
+ except (ValueError, binascii.Error) as exc:
1421
+ raise AdapterFailure(
1422
+ "OpenAI returned invalid image data",
1423
+ code="invalid_image_data",
1424
+ request_id=request_id,
1425
+ ) from exc
1426
+ return _temporary_output(job, job["outputs"][0], content), request_id
1427
+
1428
+
1429
+ def _run_minimax(job: Mapping[str, Any]) -> tuple[Path, str | None]:
1430
+ token = _credential("MINIMAX_API_KEY")
1431
+ body = compile_minimax_music_payload(job)
1432
+ base = _base_url("MINIMAX_BASE_URL", MINIMAX_BASE_URL)
1433
+ result, _ = _request_json(
1434
+ f"{base}/music_generation",
1435
+ provider="minimax-music",
1436
+ body=body,
1437
+ token=token,
1438
+ )
1439
+ base_resp = result.get("base_resp")
1440
+ if not isinstance(base_resp, Mapping) or base_resp.get("status_code") != 0:
1441
+ raise AdapterFailure(
1442
+ "MiniMax music generation failed",
1443
+ code=_provider_code(result) or "generation_failed",
1444
+ request_id=_safe_token(result.get("trace_id")),
1445
+ )
1446
+ data = result.get("data")
1447
+ audio = data.get("audio") if isinstance(data, Mapping) else None
1448
+ if not isinstance(data, Mapping) or data.get("status") != 2 or not isinstance(audio, str):
1449
+ raise AdapterFailure(
1450
+ "MiniMax did not return audio data",
1451
+ code="missing_audio",
1452
+ request_id=_safe_token(result.get("trace_id")),
1453
+ )
1454
+ try:
1455
+ content = bytes.fromhex(audio)
1456
+ except ValueError as exc:
1457
+ raise AdapterFailure("MiniMax returned invalid audio data") from exc
1458
+ if not content:
1459
+ raise AdapterFailure("MiniMax returned empty audio data")
1460
+ trace_id = result.get("trace_id")
1461
+ return (
1462
+ _temporary_output(job, job["outputs"][0], content),
1463
+ trace_id if isinstance(trace_id, str) else None,
1464
+ )
1465
+
1466
+
1467
+ def _run_minimax_speech(job: Mapping[str, Any]) -> tuple[Path, str | None]:
1468
+ token = _credential("MINIMAX_API_KEY")
1469
+ body = compile_minimax_speech_payload(job)
1470
+ base = _base_url("MINIMAX_BASE_URL", MINIMAX_BASE_URL)
1471
+ result, _ = _request_json(
1472
+ f"{base}/t2a_v2",
1473
+ provider="minimax-speech",
1474
+ body=body,
1475
+ token=token,
1476
+ )
1477
+ base_resp = result.get("base_resp")
1478
+ if not isinstance(base_resp, Mapping) or base_resp.get("status_code") != 0:
1479
+ raise AdapterFailure(
1480
+ "MiniMax speech synthesis failed",
1481
+ code=_provider_code(result) or "generation_failed",
1482
+ request_id=_safe_token(result.get("trace_id")),
1483
+ )
1484
+ data = result.get("data")
1485
+ audio = data.get("audio") if isinstance(data, Mapping) else None
1486
+ if not isinstance(audio, str) or not audio:
1487
+ raise AdapterFailure(
1488
+ "MiniMax did not return audio data",
1489
+ code="missing_audio",
1490
+ request_id=_safe_token(result.get("trace_id")),
1491
+ )
1492
+ try:
1493
+ content = bytes.fromhex(audio)
1494
+ except ValueError as exc:
1495
+ raise AdapterFailure("MiniMax returned invalid audio data") from exc
1496
+ if not content:
1497
+ raise AdapterFailure("MiniMax returned empty audio data")
1498
+ trace_id = result.get("trace_id")
1499
+ return (
1500
+ _temporary_output(job, job["outputs"][0], content),
1501
+ trace_id if isinstance(trace_id, str) else None,
1502
+ )
1503
+
1504
+
1505
+ def _run_minimax_video(job: Mapping[str, Any]) -> tuple[Path, str]:
1506
+ token = _credential("MINIMAX_API_KEY")
1507
+ base = _base_url("MINIMAX_VIDEO_BASE_URL", MINIMAX_VIDEO_BASE_URL)
1508
+ collecting = _collect_target(job)
1509
+ if collecting is not None:
1510
+ return _poll_minimax_video(job, base=base, token=token, task_id=collecting)
1511
+ model = os.environ.get("MINIMAX_VIDEO_MODEL", "")
1512
+ references = _reference_paths(job)
1513
+ reference_roles = (
1514
+ _binding_roles(job, allowed=MINIMAX_VIDEO_ROLES, provider="MiniMax")
1515
+ if references
1516
+ else []
1517
+ )
1518
+ reference_urls = _inline_reference_urls(
1519
+ references, reference_roles, allowed=MINIMAX_VIDEO_ROLES, provider="MiniMax"
1520
+ )
1521
+ ratios, resolutions, duration_range = _minimax_video_runtime_profile()
1522
+ body = compile_minimax_h3_payload(
1523
+ job,
1524
+ model=model,
1525
+ reference_urls=reference_urls,
1526
+ reference_roles=reference_roles,
1527
+ allowed_ratios=ratios,
1528
+ allowed_resolutions=resolutions,
1529
+ duration_range=duration_range,
1530
+ )
1531
+ created, _ = _request_json(
1532
+ f"{base}/video_generation",
1533
+ provider="minimax-h3",
1534
+ body=body,
1535
+ token=token,
1536
+ )
1537
+ task_id = created.get("task_id")
1538
+ if not isinstance(task_id, str) or not task_id:
1539
+ raise AdapterFailure(
1540
+ "MiniMax did not return a task id",
1541
+ code=_provider_code(created) or "missing_task_id",
1542
+ )
1543
+ # Billed from here on. Record the id before the first poll.
1544
+ _record_handle(job, task_id)
1545
+ return _poll_minimax_video(job, base=base, token=token, task_id=task_id)
1546
+
1547
+
1548
+ def _poll_minimax_video(
1549
+ job: Mapping[str, Any], *, base: str, token: str, task_id: str
1550
+ ) -> tuple[Path, str]:
1551
+ try:
1552
+ interval = float(os.environ.get("MINIMAX_VIDEO_POLL_INTERVAL", "5"))
1553
+ deadline = time.monotonic() + float(
1554
+ os.environ.get("MINIMAX_VIDEO_TIMEOUT_SECONDS", "1800")
1555
+ )
1556
+ except ValueError as exc:
1557
+ raise AdapterFailure("MiniMax polling configuration is invalid") from exc
1558
+ if interval <= 0 or deadline <= time.monotonic():
1559
+ raise AdapterFailure("MiniMax polling configuration is invalid")
1560
+ while time.monotonic() < deadline:
1561
+ document, _ = _request_json(
1562
+ f"{base}/query/video_generation/"
1563
+ + urllib.parse.quote(task_id, safe=""),
1564
+ provider="minimax-h3",
1565
+ method="GET",
1566
+ token=token,
1567
+ )
1568
+ task = document.get("task")
1569
+ status = task.get("status") if isinstance(task, Mapping) else None
1570
+ if status == "succeeded":
1571
+ content = task.get("content") if isinstance(task, Mapping) else None
1572
+ url = content.get("url") if isinstance(content, Mapping) else None
1573
+ if not isinstance(url, str):
1574
+ raise AdapterFailure(
1575
+ "MiniMax succeeded without a video URL",
1576
+ code="missing_video_url",
1577
+ request_id=task_id,
1578
+ )
1579
+ return _download(
1580
+ job, url, job["outputs"][0], provider="minimax-h3"
1581
+ ), task_id
1582
+ if isinstance(status, str) and status.casefold() in TERMINAL_FAILURES:
1583
+ raise AdapterFailure(
1584
+ "MiniMax video task failed",
1585
+ code="task_" + status.casefold(),
1586
+ request_id=task_id,
1587
+ )
1588
+ if status not in {"queued", "running", "processing", "pending", "in_progress"}:
1589
+ raise AdapterFailure(
1590
+ "MiniMax returned an unknown task status",
1591
+ code="unknown_task_status",
1592
+ request_id=task_id,
1593
+ )
1594
+ time.sleep(interval)
1595
+ raise AdapterFailure(
1596
+ "MiniMax video task polling timed out",
1597
+ category="timeout",
1598
+ code="task_poll_timeout",
1599
+ request_id=task_id,
1600
+ retryable=True,
1601
+ )
1602
+
1603
+
1604
+ def _selftest() -> None:
1605
+ image = {
1606
+ "modality": "image", "prompt": "portrait", "references": [],
1607
+ "outputs": ["制作成果/a.png"], "parameters": {"width": 1024, "height": 1536},
1608
+ }
1609
+ video = {
1610
+ "modality": "video", "prompt": "slow push in", "references": [],
1611
+ "outputs": ["制作成果/a.mp4"], "parameters": {"duration": 5, "ratio": "9:16"},
1612
+ }
1613
+ music = {
1614
+ "modality": "music", "prompt": "cinematic", "references": [],
1615
+ "outputs": ["制作成果/a.mp3"], "parameters": {"lyrics": "[Verse]\nHello"},
1616
+ }
1617
+ speech = {
1618
+ "modality": "tts", "prompt": "你们做了多久?", "references": [],
1619
+ "outputs": ["制作成果/a.mp3"],
1620
+ "parameters": {"model": "configured-speech-model", "voice_id": "a-preset"},
1621
+ }
1622
+ if compile_gpt_image_2_payload(image)["model"] != OPENAI_MODEL:
1623
+ raise RuntimeError("GPT Image 2 self-test failed")
1624
+ if compile_minimax_speech_payload(speech)["voice_setting"]["voice_id"] != "a-preset":
1625
+ raise RuntimeError("MiniMax speech self-test failed")
1626
+ seedance = compile_seedance_payload(
1627
+ video,
1628
+ model="configured-model",
1629
+ allowed_ratios={"9:16"},
1630
+ duration_range=(5, 10),
1631
+ )
1632
+ if seedance["model"] != "configured-model":
1633
+ raise RuntimeError("Seedance model self-test failed")
1634
+ if seedance.get("ratio") != "9:16" or seedance.get("duration") != 5:
1635
+ raise RuntimeError("Seedance parameter self-test failed")
1636
+ minimax_video = compile_minimax_h3_payload(
1637
+ {**video, "parameters": {"duration": 6, "ratio": "9:16", "resolution": "768P"}},
1638
+ model="configured-video-model",
1639
+ allowed_ratios={"9:16"},
1640
+ allowed_resolutions={"768P"},
1641
+ duration_range=(4, 15),
1642
+ )
1643
+ if minimax_video["model"] != "configured-video-model":
1644
+ raise RuntimeError("MiniMax video model self-test failed")
1645
+ if minimax_video["duration"] != 6 or minimax_video["resolution"] != "768P":
1646
+ raise RuntimeError("MiniMax video parameter self-test failed")
1647
+ try:
1648
+ compile_minimax_h3_payload(
1649
+ {**video, "parameters": {"duration": 6, "resolution": "768P"}},
1650
+ model="configured-video-model",
1651
+ allowed_resolutions={"768P"},
1652
+ duration_range=(4, 15),
1653
+ )
1654
+ except ValueError:
1655
+ pass
1656
+ else:
1657
+ raise AssertionError("MiniMax text-to-video without a ratio was accepted")
1658
+ compiled_music = compile_minimax_music_payload(music)
1659
+ if compiled_music["model"] != MINIMAX_MUSIC_MODEL:
1660
+ raise RuntimeError("MiniMax model self-test failed")
1661
+ if compiled_music["output_format"] != "hex":
1662
+ raise RuntimeError("MiniMax output self-test failed")
1663
+ for invalid_image in (
1664
+ {**image, "parameters": {"background": "transparent"}},
1665
+ {**image, "parameters": {"input_fidelity": "high"}},
1666
+ {**image, "parameters": {"size": "1023x1024"}},
1667
+ ):
1668
+ try:
1669
+ compile_gpt_image_2_payload(invalid_image)
1670
+ except ValueError:
1671
+ pass
1672
+ else:
1673
+ raise AssertionError("invalid GPT Image payload was accepted")
1674
+ invalid_reference = {**video, "references": ["clip.mp4"]}
1675
+ try:
1676
+ compile_seedance_payload(
1677
+ invalid_reference,
1678
+ model="configured-model",
1679
+ reference_urls=["asset://asset-example-clip"],
1680
+ reference_roles=["reference_image"],
1681
+ )
1682
+ except ValueError:
1683
+ pass
1684
+ else:
1685
+ raise AssertionError("mismatched Seedance reference role was accepted")
1686
+
1687
+
1688
+ def main() -> int:
1689
+ parser = argparse.ArgumentParser(description=__doc__)
1690
+ parser.add_argument(
1691
+ "provider",
1692
+ nargs="?",
1693
+ choices=(
1694
+ "seedance", "gpt-image-2", "minimax-music", "minimax-h3",
1695
+ "minimax-speech",
1696
+ ),
1697
+ )
1698
+ parser.add_argument("--selftest", action="store_true")
1699
+ args = parser.parse_args()
1700
+ if args.selftest:
1701
+ _selftest()
1702
+ return 0
1703
+ if args.provider is None:
1704
+ parser.error("provider is required unless --selftest is used")
1705
+ try:
1706
+ job = json.load(sys.stdin.buffer)
1707
+ if not isinstance(job, Mapping):
1708
+ raise ValueError("adapter input must be an object")
1709
+ runners = {
1710
+ "seedance": _run_seedance,
1711
+ "gpt-image-2": _run_openai,
1712
+ "minimax-music": _run_minimax,
1713
+ "minimax-h3": _run_minimax_video,
1714
+ "minimax-speech": _run_minimax_speech,
1715
+ }
1716
+ path, provider_job_id = runners[args.provider](job)
1717
+ response: dict[str, Any] = {
1718
+ "outputs": [{"target": job["outputs"][0], "source": str(path)}]
1719
+ }
1720
+ if provider_job_id:
1721
+ response["provider_job_id"] = provider_job_id
1722
+ json.dump(response, sys.stdout, ensure_ascii=True)
1723
+ return 0
1724
+ except AdapterFailure as exc:
1725
+ # Provider bodies and credentials are intentionally never reflected.
1726
+ json.dump({"error": exc.public(args.provider)}, sys.stdout, ensure_ascii=True)
1727
+ print("provider adapter failed safely", file=sys.stderr)
1728
+ return 1
1729
+ except (ValueError, KeyError, TypeError, json.JSONDecodeError):
1730
+ json.dump(
1731
+ {
1732
+ "error": {
1733
+ "provider": args.provider,
1734
+ "category": "invalid_request",
1735
+ "code": "invalid_job",
1736
+ "retryable": False,
1737
+ }
1738
+ },
1739
+ sys.stdout,
1740
+ ensure_ascii=True,
1741
+ )
1742
+ print("provider adapter failed safely", file=sys.stderr)
1743
+ return 1
1744
+
1745
+
1746
+ if __name__ == "__main__":
1747
+ raise SystemExit(main())