@oh-story/dsh 0.1.4 → 0.1.6

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 (479) hide show
  1. package/README.md +56 -12
  2. package/lib/client.js +2707 -268
  3. package/lib/client.js.map +4 -4
  4. package/lib/drama/manifest.json +136 -101
  5. package/lib/drama/skills/short-drama/SKILL.md +61 -1
  6. package/lib/drama/skills/short-drama/references/contract-and-ownership.md +7 -4
  7. package/lib/drama/skills/short-drama/references/creator-documents.md +133 -8
  8. package/lib/drama/skills/short-drama/references/creator-workflow.md +38 -2
  9. package/lib/drama/skills/short-drama/references/knowhow-index.md +3 -0
  10. package/lib/drama/skills/short-drama/references/reference-roles.md +10 -7
  11. package/lib/drama/skills/short-drama/references/runtime-preflight.md +3 -0
  12. package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +1002 -0
  13. package/lib/drama/skills/short-drama/scripts/project_tool.py +485 -5
  14. package/lib/drama/skills/short-drama-assets/SKILL.md +13 -3
  15. package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +116 -0
  16. package/lib/drama/skills/short-drama-assets/references/identity-vs-variant.md +2 -1
  17. package/lib/drama/skills/short-drama-assets/references/prop-and-state.md +19 -0
  18. package/lib/drama/skills/short-drama-assets/references/stage-contract.md +2 -0
  19. package/lib/drama/skills/short-drama-assets/scripts/asset_check.py +1 -1
  20. package/lib/drama/skills/short-drama-image-prompts/SKILL.md +6 -2
  21. package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +4 -3
  22. package/lib/drama/skills/short-drama-image-prompts/references/lookdev-frame.md +1 -1
  23. package/lib/drama/skills/short-drama-image-prompts/references/review-and-fixtures.md +1 -1
  24. package/lib/drama/skills/short-drama-image-prompts/references/stage-contract.md +3 -1
  25. package/lib/drama/skills/short-drama-image-prompts/scripts/image_prompt_check.py +8 -8
  26. package/lib/drama/skills/short-drama-image-prompts/scripts/selftest.py +4 -2
  27. package/lib/drama/skills/short-drama-produce/SKILL.md +46 -10
  28. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +65 -8
  29. package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +85 -0
  30. package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +27 -10
  31. package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +362 -3
  32. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +627 -40
  33. package/lib/drama/skills/short-drama-produce/scripts/selftest.py +5 -4
  34. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +12 -0
  35. package/lib/drama/skills/short-drama-review/scripts/review_check.py +1 -1
  36. package/lib/drama/skills/short-drama-storyboard/SKILL.md +76 -7
  37. package/lib/drama/skills/short-drama-storyboard/assets/shot-template.jsonl +1 -1
  38. package/lib/drama/skills/short-drama-storyboard/references/keyframe-craft.md +6 -2
  39. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +19 -4
  40. package/lib/drama/skills/short-drama-storyboard/references/review-and-fixtures.md +5 -2
  41. package/lib/drama/skills/short-drama-storyboard/references/screenplay-to-keyframe-example.md +16 -0
  42. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +8 -3
  43. package/lib/drama/skills/short-drama-storyboard/scripts/storyboard_check.py +1 -1
  44. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +81 -13
  45. package/lib/drama/skills/short-drama-video-prompts/assets/motion-spec.jsonl.md +6 -1
  46. package/lib/drama/skills/short-drama-video-prompts/assets/video-prompts.md +17 -1
  47. package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +21 -1
  48. package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +154 -0
  49. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +24 -6
  50. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +11 -7
  51. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +41 -0
  52. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +66 -0
  53. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +5 -2
  54. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +115 -0
  55. package/lib/drama/skills/short-drama-video-prompts/scripts/container_check.py +1 -1
  56. package/lib/drama/skills/short-drama-video-prompts/scripts/music_spec_check.py +1 -1
  57. package/lib/drama/skills/short-drama-write/SKILL.md +3 -2
  58. package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +1 -1
  59. package/lib/drama/skills/short-drama-write/scripts/screenplay_index.py +2 -2
  60. package/lib/drama/skills/short-drama-write/scripts/voice_sheet_check.py +1 -1
  61. package/lib/index.js +865 -33
  62. package/lib/index.js.map +4 -4
  63. package/lib/novel-to-game/LICENSE +21 -0
  64. package/lib/novel-to-game/README.md +180 -0
  65. package/lib/novel-to-game/README_ZH.md +178 -0
  66. package/lib/novel-to-game/VERSION +1 -0
  67. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +217 -0
  68. package/lib/novel-to-game/examples/jin-ping-mei/build/app/.vercelignore +3 -0
  69. package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +130 -0
  70. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/compound_new_guofeng.webp +0 -0
  71. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/fate_coda.webp +0 -0
  72. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/meng.webp +0 -0
  73. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pan.webp +0 -0
  74. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pinger.webp +0 -0
  75. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/xuee.webp +0 -0
  76. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/yue.webp +0 -0
  77. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_accord_five.webp +0 -0
  78. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_afterglow_five.webp +0 -0
  79. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_alliance.webp +0 -0
  80. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day10.webp +0 -0
  81. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day15.webp +0 -0
  82. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day5.webp +0 -0
  83. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/title_new_guofeng.webp +0 -0
  84. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_pinger.webp +0 -0
  85. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_xuee.webp +0 -0
  86. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_meng.webp +0 -0
  87. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pan.webp +0 -0
  88. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pinger.webp +0 -0
  89. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/explicit.webp +0 -0
  90. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/prelude.webp +0 -0
  91. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/meng.webp +0 -0
  92. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pan.webp +0 -0
  93. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pinger.webp +0 -0
  94. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/xuee.webp +0 -0
  95. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/yue.webp +0 -0
  96. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/explicit.webp +0 -0
  97. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/prelude.webp +0 -0
  98. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/explicit.webp +0 -0
  99. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/prelude.webp +0 -0
  100. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/explicit.webp +0 -0
  101. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/prelude.webp +0 -0
  102. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/explicit.webp +0 -0
  103. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/prelude.webp +0 -0
  104. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/meng/night.webp +0 -0
  105. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pan/night.webp +0 -0
  106. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pinger/night.webp +0 -0
  107. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/xuee/night.webp +0 -0
  108. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/yue/night.webp +0 -0
  109. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/household/li_jiaoer.webp +0 -0
  110. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/scene/gate_collector.webp +0 -0
  111. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +1105 -0
  112. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +2539 -0
  113. package/lib/novel-to-game/examples/jin-ping-mei/build/app/index.html +18 -0
  114. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/assets.js +147 -0
  115. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +332 -0
  116. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +7472 -0
  117. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +13077 -0
  118. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +3028 -0
  119. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +32 -0
  120. package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +10 -0
  121. package/lib/novel-to-game/examples/jin-ping-mei/build/app/vercel.json +23 -0
  122. package/lib/novel-to-game/examples/jin-ping-mei/build/art/generated-art.json +161 -0
  123. package/lib/novel-to-game/examples/jin-ping-mei/example.json +12 -0
  124. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +31 -0
  125. package/lib/novel-to-game/examples/jin-ping-mei/qa/playable-model.json +99 -0
  126. package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +41 -0
  127. package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +129 -0
  128. package/lib/novel-to-game/manifest.json +508 -0
  129. package/lib/novel-to-game/skills/game-art-direction/SKILL.md +73 -0
  130. package/lib/novel-to-game/skills/game-art-direction/agents/openai.yaml +4 -0
  131. package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +119 -0
  132. package/lib/novel-to-game/skills/game-build/SKILL.md +84 -0
  133. package/lib/novel-to-game/skills/game-build/agents/openai.yaml +4 -0
  134. package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +109 -0
  135. package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +56 -0
  136. package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +81 -0
  137. package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +37 -0
  138. package/lib/novel-to-game/skills/game-concept/SKILL.md +72 -0
  139. package/lib/novel-to-game/skills/game-concept/agents/openai.yaml +4 -0
  140. package/lib/novel-to-game/skills/game-concept/references/concept-method.md +94 -0
  141. package/lib/novel-to-game/skills/game-qa/SKILL.md +50 -0
  142. package/lib/novel-to-game/skills/game-qa/agents/openai.yaml +4 -0
  143. package/lib/novel-to-game/skills/game-qa/references/qa-contract.md +66 -0
  144. package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +45 -0
  145. package/lib/novel-to-game/skills/game-world-design/SKILL.md +63 -0
  146. package/lib/novel-to-game/skills/game-world-design/agents/openai.yaml +4 -0
  147. package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +59 -0
  148. package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +53 -0
  149. package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +84 -0
  150. package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +56 -0
  151. package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +124 -0
  152. package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +77 -0
  153. package/lib/novel-to-game/skills/novel-game-analyze/agents/openai.yaml +4 -0
  154. package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +65 -0
  155. package/lib/novel-to-game/skills/novel-to-game/SKILL.md +71 -0
  156. package/lib/novel-to-game/skills/novel-to-game/agents/openai.yaml +4 -0
  157. package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +42 -0
  158. package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +51 -0
  159. package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +65 -0
  160. package/lib/oh-story/manifest.json +415 -305
  161. package/lib/oh-story/roles/consistency-checker.md +1 -1
  162. package/lib/oh-story/roles/narrative-writer.md +32 -33
  163. package/lib/oh-story/roles/story-architect.md +16 -26
  164. package/lib/oh-story/skills/browser-cdp/SKILL.md +1 -1
  165. package/lib/oh-story/skills/story/SKILL.md +16 -9
  166. package/lib/oh-story/skills/story/VERSION +1 -1
  167. package/lib/oh-story/skills/story/references/author-memory.md +163 -0
  168. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +871 -0
  169. package/lib/oh-story/skills/story-cover/SKILL.md +1 -1
  170. package/lib/oh-story/skills/story-deslop/SKILL.md +10 -5
  171. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +2 -1
  172. package/lib/oh-story/skills/story-deslop/references/author-memory.md +163 -0
  173. package/lib/oh-story/skills/story-deslop/references/banned-words.md +6 -4
  174. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +871 -0
  175. package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +64 -1
  176. package/lib/oh-story/skills/story-import/SKILL.md +19 -10
  177. package/lib/oh-story/skills/story-import/references/format-and-structure.md +9 -10
  178. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +2 -2
  179. package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +1 -1
  180. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +14 -6
  181. package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +251 -0
  182. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +98 -9
  183. package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +246 -0
  184. package/lib/oh-story/skills/story-long-analyze/SKILL.md +4 -4
  185. package/lib/oh-story/skills/story-long-scan/SKILL.md +1 -1
  186. package/lib/oh-story/skills/story-long-write/SKILL.md +35 -21
  187. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +2 -1
  188. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +7 -3
  189. package/lib/oh-story/skills/story-long-write/references/author-memory.md +163 -0
  190. package/lib/oh-story/skills/story-long-write/references/banned-words.md +6 -4
  191. package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +6 -6
  192. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
  193. package/lib/oh-story/skills/story-long-write/references/genre-readers.md +1 -1
  194. package/lib/oh-story/skills/{story-setup/references/agent-references/hooks-chapter.md → story-long-write/references/long-chapter-hooks.md} +5 -5
  195. package/lib/oh-story/skills/story-long-write/references/{quality-checklist.md → long-chapter-quality.md} +2 -1
  196. package/lib/oh-story/skills/story-long-write/references/long-format.md +42 -0
  197. package/lib/oh-story/skills/{story-setup/references/agent-references/genre-catalog.md → story-long-write/references/long-genre-catalog.md} +11 -97
  198. package/lib/oh-story/skills/{story-setup/references/agent-references/genre-core-mechanics.md → story-long-write/references/long-genre-mechanics.md} +1 -1
  199. package/lib/oh-story/skills/{story-short-write/references/reversal-toolkit.md → story-long-write/references/long-reversal.md} +18 -32
  200. package/lib/oh-story/skills/story-long-write/references/{hooks-suspense.md → long-suspense.md} +6 -6
  201. package/lib/oh-story/skills/story-long-write/references/opening-design.md +1 -1
  202. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +1 -1
  203. package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
  204. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +14 -6
  205. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +32 -27
  206. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +4 -4
  207. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +2 -2
  208. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +14 -14
  209. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +54 -54
  210. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +871 -0
  211. package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +64 -1
  212. package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +251 -0
  213. package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +303 -0
  214. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +98 -9
  215. package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +246 -0
  216. package/lib/oh-story/skills/story-review/SKILL.md +26 -17
  217. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +2 -1
  218. package/lib/oh-story/skills/story-review/references/author-memory.md +163 -0
  219. package/lib/oh-story/skills/story-review/references/banned-words.md +6 -4
  220. package/lib/oh-story/skills/story-review/references/{quality-checklist.md → review-quality.md} +22 -21
  221. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +14 -6
  222. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +871 -0
  223. package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +64 -1
  224. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +98 -9
  225. package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +246 -0
  226. package/lib/oh-story/skills/story-setup/SKILL.md +74 -33
  227. package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +72 -0
  228. package/lib/oh-story/skills/story-setup/references/agent-references/agent-reference-profiles.md +55 -0
  229. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +2 -1
  230. package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +6 -4
  231. package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +9 -10
  232. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +1 -1
  233. package/lib/oh-story/skills/story-setup/references/agent-references/genre-readers.md +1 -1
  234. package/lib/oh-story/skills/{story-short-analyze/references/hooks-chapter.md → story-setup/references/agent-references/long-chapter-hooks.md} +5 -5
  235. package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +209 -0
  236. package/lib/oh-story/skills/{story-long-write/references/genre-catalog.md → story-setup/references/agent-references/long-genre-catalog.md} +11 -97
  237. package/lib/oh-story/skills/{story-long-write/references/genre-core-mechanics.md → story-setup/references/agent-references/long-genre-mechanics.md} +1 -1
  238. package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +40 -0
  239. package/lib/oh-story/skills/story-setup/references/agent-references/{reversal-toolkit.md → long-reversal.md} +18 -32
  240. package/lib/oh-story/skills/{story-short-write/references/hooks-suspense.md → story-setup/references/agent-references/long-suspense.md} +6 -6
  241. package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +1 -1
  242. package/lib/oh-story/skills/story-setup/references/agent-references/{emotional-methods.md → short-emotional-methods.md} +4 -4
  243. package/lib/oh-story/skills/{story-long-write/references/genre-writing-formulas.md → story-setup/references/agent-references/short-genre-formulas.md} +32 -44
  244. package/lib/oh-story/skills/story-setup/references/agent-references/short-quality.md +39 -0
  245. package/lib/oh-story/skills/{story-long-write/references/reversal-toolkit.md → story-setup/references/agent-references/short-reversal.md} +29 -23
  246. package/lib/oh-story/skills/story-setup/references/agent-references/{hooks-suspense.md → short-suspense.md} +31 -31
  247. package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
  248. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +55 -55
  249. package/lib/oh-story/skills/story-short-analyze/SKILL.md +28 -28
  250. package/lib/oh-story/skills/story-short-analyze/references/{character-basics.md → analysis-character-basics.md} +1 -1
  251. package/lib/oh-story/skills/story-short-analyze/references/{character-design-methods.md → analysis-character-design.md} +1 -1
  252. package/lib/oh-story/skills/story-short-analyze/references/{character-relations.md → analysis-character-relations.md} +1 -1
  253. package/lib/oh-story/skills/story-short-analyze/references/{hooks-paragraph.md → analysis-paragraph-hooks.md} +1 -1
  254. package/lib/oh-story/skills/story-short-analyze/references/{genre-readers.md → analysis-reader-profile.md} +2 -2
  255. package/lib/oh-story/skills/story-short-analyze/references/analysis-report-style.md +45 -0
  256. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-genres.md +65 -0
  257. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-hooks.md +77 -0
  258. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-mechanics.md +82 -0
  259. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-patterns.md +50 -0
  260. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-suspense.md +98 -0
  261. package/lib/oh-story/skills/story-short-analyze/references/{genre-writing-techniques.md → analysis-writing-techniques.md} +21 -19
  262. package/lib/oh-story/skills/story-short-analyze/references/deconstruction-examples.md +1 -1
  263. package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +21 -28
  264. package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +1 -1
  265. package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +17 -17
  266. package/lib/oh-story/skills/story-short-analyze/references/{quality-checklist.md → source-story-quality.md} +28 -49
  267. package/lib/oh-story/skills/story-short-analyze/references/zhihu-style.md +2 -2
  268. package/lib/oh-story/skills/story-short-scan/SKILL.md +1 -1
  269. package/lib/oh-story/skills/story-short-write/SKILL.md +78 -55
  270. package/lib/oh-story/skills/story-short-write/references/author-memory.md +163 -0
  271. package/lib/oh-story/skills/story-short-write/references/banned-words.md +6 -4
  272. package/lib/oh-story/skills/story-short-write/references/cross-book-recall.md +3 -3
  273. package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +4 -4
  274. package/lib/oh-story/skills/story-short-write/references/genre-styles//344/270/226/346/203/205/346/211/223/350/204/270.md +3 -3
  275. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +2 -2
  276. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/244/215/344/273/207/346/211/223/350/204/270.md +3 -3
  277. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/256/205/346/226/227/345/256/253/346/226/227.md +6 -6
  278. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/200/273/350/243/201/350/261/252/351/227/250.md +3 -3
  279. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/202/254/347/226/221.md +4 -4
  280. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/260/221/344/277/227/346/200/252/350/260/210.md +7 -7
  281. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/262/231/351/233/225/350/204/221/346/264/236.md +5 -5
  282. package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +4 -4
  283. package/lib/oh-story/skills/story-short-write/references/genre-styles//350/277/275/345/246/273/347/201/253/350/221/254/345/234/272.md +3 -3
  284. package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +32 -44
  285. package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +20 -18
  286. package/lib/oh-story/skills/story-short-write/references/output-contract.md +1 -1
  287. package/lib/oh-story/skills/story-short-write/references/short-craft.md +18 -18
  288. package/lib/oh-story/skills/story-short-write/references/short-deslop.md +1 -1
  289. package/lib/oh-story/skills/story-short-write/references/short-format.md +38 -8
  290. package/lib/oh-story/skills/story-short-write/references/{quality-checklist.md → short-prose-quality.md} +22 -21
  291. package/lib/oh-story/skills/story-short-write/references/short-reversal.md +355 -0
  292. package/lib/oh-story/skills/{story-short-analyze/references/hooks-suspense.md → story-short-write/references/short-suspense.md} +31 -31
  293. package/lib/oh-story/skills/story-short-write/references/submission-craft.md +2 -2
  294. package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +31 -22
  295. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +871 -0
  296. package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +64 -1
  297. package/lib/oh-story/skills/story-short-write/scripts/check-delivery-contract.js +212 -0
  298. package/lib/oh-story/skills/story-short-write/scripts/check-phase2-contract.js +363 -0
  299. package/lib/types/client/drama-production-view.d.ts +34 -0
  300. package/lib/types/client/drama-production-view.d.ts.map +1 -0
  301. package/lib/types/client/drama-production.d.ts +92 -0
  302. package/lib/types/client/drama-production.d.ts.map +1 -0
  303. package/lib/types/client/file-activity.d.ts +4 -2
  304. package/lib/types/client/file-activity.d.ts.map +1 -1
  305. package/lib/types/client/index.d.ts +1 -1
  306. package/lib/types/client/index.d.ts.map +1 -1
  307. package/lib/types/client/production-intents.d.ts +9 -0
  308. package/lib/types/client/production-intents.d.ts.map +1 -0
  309. package/lib/types/client/production-prompts.d.ts +9 -0
  310. package/lib/types/client/production-prompts.d.ts.map +1 -0
  311. package/lib/types/client/production-runtime.d.ts +58 -0
  312. package/lib/types/client/production-runtime.d.ts.map +1 -0
  313. package/lib/types/client/video-studio.d.ts +41 -0
  314. package/lib/types/client/video-studio.d.ts.map +1 -0
  315. package/lib/types/client/workbench-ui.d.ts +5 -0
  316. package/lib/types/client/workbench-ui.d.ts.map +1 -0
  317. package/lib/types/game-verification.d.ts +16 -0
  318. package/lib/types/game-verification.d.ts.map +1 -0
  319. package/lib/types/index.d.ts +3 -1
  320. package/lib/types/index.d.ts.map +1 -1
  321. package/lib/types/production-intent.d.ts +21 -0
  322. package/lib/types/production-intent.d.ts.map +1 -0
  323. package/lib/types/production-tool.d.ts +5 -0
  324. package/lib/types/production-tool.d.ts.map +1 -0
  325. package/lib/types/role-tool.d.ts.map +1 -1
  326. package/lib/types/skill-provider.d.ts +7 -0
  327. package/lib/types/skill-provider.d.ts.map +1 -1
  328. package/lib/types/video-project.d.ts +42 -0
  329. package/lib/types/video-project.d.ts.map +1 -0
  330. package/lib/types/workspace-request-trust.d.ts +6 -0
  331. package/lib/types/workspace-request-trust.d.ts.map +1 -1
  332. package/lib/types/workspace-route.d.ts +20 -0
  333. package/lib/types/workspace-route.d.ts.map +1 -1
  334. package/lib/video-recap/CHANGELOG.md +282 -0
  335. package/lib/video-recap/LICENSE +21 -0
  336. package/lib/video-recap/README.en.md +257 -0
  337. package/lib/video-recap/README.md +255 -0
  338. package/lib/video-recap/manifest.json +684 -0
  339. package/lib/video-recap/skills/video-assemble/SKILL.md +97 -0
  340. package/lib/video-recap/skills/video-assemble/references/jianying/LICENSE.duo-video +21 -0
  341. package/lib/video-recap/skills/video-assemble/references/jianying/SOURCE.md +15 -0
  342. package/lib/video-recap/skills/video-assemble/references/jianying/empty_draft_meta_info.json +69 -0
  343. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_segment.json +84 -0
  344. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_video_material.json +98 -0
  345. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_draft.json +204 -0
  346. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_material_video.json +83 -0
  347. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_meta_material_value.json +23 -0
  348. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_project_info.json +133 -0
  349. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_segment.json +70 -0
  350. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_text_styles.json +30 -0
  351. package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_audio.json +41 -0
  352. package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_text.json +109 -0
  353. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +88 -0
  354. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +345 -0
  355. package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +34 -0
  356. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +305 -0
  357. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +94 -0
  358. package/lib/video-recap/skills/video-assemble/scripts/audio_automation.py +205 -0
  359. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +555 -0
  360. package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +261 -0
  361. package/lib/video-recap/skills/video-assemble/scripts/jianying_builders.py +767 -0
  362. package/lib/video-recap/skills/video-assemble/scripts/jianying_model.py +111 -0
  363. package/lib/video-recap/skills/video-assemble/scripts/jianying_optional.py +24 -0
  364. package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +109 -0
  365. package/lib/video-recap/skills/video-assemble/scripts/jianying_templates.py +37 -0
  366. package/lib/video-recap/skills/video-assemble/scripts/jianying_timeline_contract.py +191 -0
  367. package/lib/video-recap/skills/video-assemble/scripts/jianying_tracks.py +75 -0
  368. package/lib/video-recap/skills/video-assemble/scripts/jianying_writer.py +394 -0
  369. package/lib/video-recap/skills/video-assemble/scripts/lib.py +239 -0
  370. package/lib/video-recap/skills/video-assemble/scripts/media.py +243 -0
  371. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +477 -0
  372. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +46 -0
  373. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +486 -0
  374. package/lib/video-recap/skills/video-assemble/scripts/subtitle_core.py +376 -0
  375. package/lib/video-recap/skills/video-assemble/scripts/subtitle_render.py +88 -0
  376. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +248 -0
  377. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +89 -0
  378. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +512 -0
  379. package/lib/video-recap/skills/video-cut/SKILL.md +90 -0
  380. package/lib/video-recap/skills/video-cut/scripts/cut.py +67 -0
  381. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +320 -0
  382. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +545 -0
  383. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +348 -0
  384. package/lib/video-recap/skills/video-cut/scripts/lib.py +77 -0
  385. package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +341 -0
  386. package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +249 -0
  387. package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +745 -0
  388. package/lib/video-recap/skills/video-recap/SKILL.md +216 -0
  389. package/lib/video-recap/skills/video-recap/references/config-playbook.md +61 -0
  390. package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +225 -0
  391. package/lib/video-recap/skills/video-recap/references/data-schema.md +534 -0
  392. package/lib/video-recap/skills/video-recap/references/research-guide.md +74 -0
  393. package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +23 -0
  394. package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +251 -0
  395. package/lib/video-recap/skills/video-recap/scripts/doctor.py +483 -0
  396. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +737 -0
  397. package/lib/video-recap/skills/video-recap/scripts/lib.py +242 -0
  398. package/lib/video-recap/skills/video-recap/scripts/materials.py +457 -0
  399. package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +95 -0
  400. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_client.py +18 -0
  401. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_contract.py +9 -0
  402. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_evidence.py +371 -0
  403. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_observations.py +201 -0
  404. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_payload.py +227 -0
  405. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_report.py +362 -0
  406. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_runner.py +129 -0
  407. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +338 -0
  408. package/lib/video-recap/skills/video-recap/scripts/recap.py +9 -0
  409. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +140 -0
  410. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +550 -0
  411. package/lib/video-recap/skills/video-recap/scripts/recap_review.py +77 -0
  412. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +654 -0
  413. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +335 -0
  414. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +217 -0
  415. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +795 -0
  416. package/lib/video-recap/skills/video-script/SKILL.md +248 -0
  417. package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +225 -0
  418. package/lib/video-recap/skills/video-script/references/research-guide.md +75 -0
  419. package/lib/video-recap/skills/video-script/scripts/agent_brief.py +399 -0
  420. package/lib/video-recap/skills/video-script/scripts/agent_text.py +410 -0
  421. package/lib/video-recap/skills/video-script/scripts/brief_context.py +403 -0
  422. package/lib/video-recap/skills/video-script/scripts/brief_inputs.py +332 -0
  423. package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +442 -0
  424. package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +237 -0
  425. package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +492 -0
  426. package/lib/video-recap/skills/video-script/scripts/lib.py +327 -0
  427. package/lib/video-recap/skills/video-script/scripts/narration.py +12 -0
  428. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +678 -0
  429. package/lib/video-recap/skills/video-script/scripts/review.py +42 -0
  430. package/lib/video-recap/skills/video-script/scripts/review_grounding.py +262 -0
  431. package/lib/video-recap/skills/video-script/scripts/review_response.py +761 -0
  432. package/lib/video-recap/skills/video-script/scripts/review_runner.py +200 -0
  433. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +204 -0
  434. package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +199 -0
  435. package/lib/video-recap/skills/video-script/scripts/validate.py +164 -0
  436. package/lib/video-recap/skills/video-understanding/SKILL.md +75 -0
  437. package/lib/video-recap/skills/video-understanding/references/data-schema.md +299 -0
  438. package/lib/video-recap/skills/video-understanding/references/prompt-templates.md +37 -0
  439. package/lib/video-recap/skills/video-understanding/references/research-guide.md +74 -0
  440. package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +399 -0
  441. package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +410 -0
  442. package/lib/video-recap/skills/video-understanding/scripts/asr.py +260 -0
  443. package/lib/video-recap/skills/video-understanding/scripts/brief.py +12 -0
  444. package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +403 -0
  445. package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +332 -0
  446. package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +442 -0
  447. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +647 -0
  448. package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +237 -0
  449. package/lib/video-recap/skills/video-understanding/scripts/detect.py +513 -0
  450. package/lib/video-recap/skills/video-understanding/scripts/extract.py +62 -0
  451. package/lib/video-recap/skills/video-understanding/scripts/lib.py +470 -0
  452. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +678 -0
  453. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +204 -0
  454. package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +507 -0
  455. package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +199 -0
  456. package/lib/video-recap/skills/video-understanding/scripts/understand.py +9 -0
  457. package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +216 -0
  458. package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +327 -0
  459. package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +367 -0
  460. package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +162 -0
  461. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +705 -0
  462. package/lib/video-recap/skills/video-voiceover/SKILL.md +81 -0
  463. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +637 -0
  464. package/lib/video-recap/skills/video-voiceover/scripts/fish_audio.py +109 -0
  465. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +421 -0
  466. package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +113 -0
  467. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +782 -0
  468. package/package.json +55 -40
  469. package/lib/oh-story/skills/story-deslop/scripts/check-outline-copy.js +0 -241
  470. package/lib/oh-story/skills/story-long-write/references/format-and-structure.md +0 -169
  471. package/lib/oh-story/skills/story-setup/references/agent-references/genre-writing-formulas.md +0 -444
  472. package/lib/oh-story/skills/story-setup/references/agent-references/quality-checklist.md +0 -247
  473. package/lib/oh-story/skills/story-short-analyze/references/anti-ai-writing.md +0 -582
  474. package/lib/oh-story/skills/story-short-analyze/references/banned-words.md +0 -147
  475. package/lib/oh-story/skills/story-short-analyze/references/genre-catalog.md +0 -490
  476. package/lib/oh-story/skills/story-short-analyze/references/genre-core-mechanics.md +0 -425
  477. package/lib/oh-story/skills/story-short-analyze/references/genre-writing-formulas.md +0 -453
  478. /package/lib/oh-story/skills/{story-long-write/references/hooks-chapter.md → story-setup/references/agent-references/short-chapter-hooks.md} +0 -0
  479. /package/lib/oh-story/skills/{story-long-write/references/hooks-paragraph.md → story-setup/references/agent-references/short-paragraph-hooks.md} +0 -0
@@ -32,6 +32,7 @@ MINIMAX_MUSIC_MODEL = "music-3.0"
32
32
  OPENAI_BASE_URL = "https://api.openai.com/v1"
33
33
  SEEDANCE_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3"
34
34
  MINIMAX_BASE_URL = "https://api.minimax.io/v1"
35
+ MINIMAX_VIDEO_BASE_URL = "https://api.minimax.io/v2"
35
36
  MAX_JSON_RESPONSE = 128 * 1024 * 1024
36
37
  MAX_OUTPUT_BYTES = 512 * 1024 * 1024
37
38
  MAX_REFERENCE_BYTES = 50 * 1024 * 1024
@@ -39,6 +40,47 @@ MAX_MULTIPART_BYTES = 200 * 1024 * 1024
39
40
  MAX_ERROR_BODY_BYTES = 64 * 1024
40
41
  TERMINAL_FAILURES = {"failed", "cancelled", "canceled", "timeout", "expired"}
41
42
  SEEDANCE_RATIOS = {"adaptive", "1:1", "3:4", "4:3", "9:16", "16:9", "21:9"}
43
+ SEEDANCE_REFERENCE_ROLES = {
44
+ "reference_image": "image_url",
45
+ "reference_video": "video_url",
46
+ "reference_audio": "audio_url",
47
+ }
48
+ MINIMAX_VIDEO_RATIOS = {"adaptive", "1:1", "3:4", "4:3", "9:16", "16:9", "21:9"}
49
+ MINIMAX_VIDEO_RESOLUTIONS = {"480P", "768P", "2K"}
50
+ MINIMAX_VIDEO_PROMPT_LIMIT = 7000
51
+ MINIMAX_VIDEO_ROLES = {
52
+ "first_frame": "image_url",
53
+ "last_frame": "image_url",
54
+ "reference_image": "image_url",
55
+ "reference_video": "video_url",
56
+ "reference_audio": "audio_url",
57
+ }
58
+ # Both providers document a `data:<mime>;base64,<...>` URI as an accepted media
59
+ # input alongside a public URL, so a local project reference needs no upload
60
+ # service to reach them. The caps are MiniMax's published per-modality limits;
61
+ # Seedance publishes no numbers, so the same conservative guard is applied there
62
+ # and can be raised per deployment. Base64 inflates bytes by about a third, so
63
+ # the request cap is checked against the encoded size.
64
+ INLINE_REFERENCE_LIMITS = {
65
+ "image_url": 30 * 1024 * 1024,
66
+ "video_url": 50 * 1024 * 1024,
67
+ "audio_url": 15 * 1024 * 1024,
68
+ }
69
+ INLINE_REFERENCE_BODY_LIMIT = 64 * 1024 * 1024
70
+ INLINE_REFERENCE_MIME = {
71
+ ".png": "image/png",
72
+ ".jpg": "image/jpeg",
73
+ ".jpeg": "image/jpeg",
74
+ ".webp": "image/webp",
75
+ ".mp4": "video/mp4",
76
+ ".mov": "video/quicktime",
77
+ ".webm": "video/webm",
78
+ ".wav": "audio/wav",
79
+ ".mp3": "audio/mpeg",
80
+ ".m4a": "audio/mp4",
81
+ ".aac": "audio/aac",
82
+ ".flac": "audio/flac",
83
+ }
42
84
  GPT_IMAGE_MIN_PIXELS = 655_360
43
85
  GPT_IMAGE_MAX_PIXELS = 8_294_400
44
86
  MINIMUM_PYTHON = (3, 9)
@@ -161,6 +203,96 @@ def _require_job(job: Mapping[str, Any], modality: str) -> tuple[str, dict[str,
161
203
  return prompt, dict(parameters)
162
204
 
163
205
 
206
+ def _prompt_with_reference_contract(
207
+ prompt: str,
208
+ job: Mapping[str, Any],
209
+ *,
210
+ prompt_language: str | None = None,
211
+ reference_tokens: Sequence[str] = (),
212
+ zh_reference_prefix: str = "参考",
213
+ zh_heading: str = "参考约束:",
214
+ ) -> str:
215
+ bindings = job.get("reference_bindings", [])
216
+ references = job.get("references", [])
217
+ if not bindings:
218
+ return prompt
219
+ if (
220
+ not isinstance(bindings, list)
221
+ or not isinstance(references, list)
222
+ or len(bindings) != len(references)
223
+ ):
224
+ raise ValueError("reference bindings must match job references")
225
+ if reference_tokens and len(reference_tokens) != len(references):
226
+ raise ValueError("reference tokens must match job references")
227
+ language = (prompt_language or "en").casefold()
228
+ instructions: list[str] = []
229
+ for index, binding in enumerate(bindings, 1):
230
+ if not isinstance(binding, Mapping) or binding.get("order") != index:
231
+ raise ValueError("reference binding order is invalid")
232
+ if binding.get("path") != references[index - 1]:
233
+ raise ValueError("reference binding path does not match job references")
234
+ label = binding.get("label")
235
+ role = binding.get("role")
236
+ may_control = binding.get("may_control")
237
+ must_not_control = binding.get("must_not_control")
238
+ if (
239
+ not isinstance(label, str)
240
+ or not label.strip()
241
+ or not isinstance(role, str)
242
+ or not role.strip()
243
+ or not isinstance(may_control, list)
244
+ or not may_control
245
+ or not all(isinstance(item, str) and item.strip() for item in may_control)
246
+ or not isinstance(must_not_control, list)
247
+ or not must_not_control
248
+ or not all(isinstance(item, str) and item.strip() for item in must_not_control)
249
+ ):
250
+ raise ValueError("reference binding semantics are invalid")
251
+ values = {
252
+ "order": index,
253
+ "reference": reference_tokens[index - 1] if reference_tokens else str(index),
254
+ "label": label.strip(),
255
+ "role": role.strip(),
256
+ "may": ", ".join(item.strip() for item in may_control),
257
+ "must": ", ".join(item.strip() for item in must_not_control),
258
+ }
259
+ if language.startswith("zh"):
260
+ instructions.append(
261
+ f"{zh_reference_prefix} "
262
+ + (
263
+ "{reference}({label}),用途 {role}。允许控制:{may}。"
264
+ "不得控制:{must}。".format(**values)
265
+ )
266
+ )
267
+ elif language.startswith("en"):
268
+ instructions.append(
269
+ "Reference {reference} ({label}), role {role}. May control: {may}. "
270
+ "Must not control: {must}.".format(**values)
271
+ )
272
+ else:
273
+ instructions.append(
274
+ "[REF {order} | {label} | {role}] [+] {may} [-] {must}".format(
275
+ **values
276
+ )
277
+ )
278
+ if language.startswith("zh"):
279
+ heading = zh_heading
280
+ elif language.startswith("en"):
281
+ heading = "Reference contract:"
282
+ else:
283
+ heading = "<REF_CONTRACT>"
284
+ return f"{prompt}\n\n{heading}\n" + "\n".join(instructions)
285
+
286
+
287
+ def _pop_prompt_language(parameters: dict[str, Any]) -> str | None:
288
+ value = parameters.pop("prompt_language", None)
289
+ if value is None:
290
+ return None
291
+ if not isinstance(value, str) or not value.strip() or len(value) > 64:
292
+ raise ValueError("prompt_language must be a non-empty bounded language tag")
293
+ return value.strip()
294
+
295
+
164
296
  def _take(parameters: dict[str, Any], allowed: set[str]) -> dict[str, Any]:
165
297
  unknown = set(parameters) - allowed
166
298
  if unknown:
@@ -173,6 +305,7 @@ def compile_seedance_payload(
173
305
  *,
174
306
  model: str,
175
307
  reference_urls: Sequence[str] = (),
308
+ reference_roles: Sequence[str] = (),
176
309
  allowed_ratios: Collection[str] | None = None,
177
310
  duration_range: tuple[int, int] | None = None,
178
311
  ) -> dict[str, Any]:
@@ -187,24 +320,33 @@ def compile_seedance_payload(
187
320
  references = job.get("references", [])
188
321
  if not isinstance(references, list) or len(reference_urls) != len(references):
189
322
  raise ValueError("Seedance reference URLs must match job references")
323
+ if len(reference_roles) != len(reference_urls):
324
+ raise ValueError("Seedance reference roles must match job references")
190
325
  if Path(job["outputs"][0]).suffix.casefold() != ".mp4":
191
326
  raise ValueError("Seedance adapter requires an MP4 target")
192
327
  parameters = _take(
193
328
  parameters,
194
- {"duration", "ratio"},
329
+ {
330
+ "duration",
331
+ "ratio",
332
+ "generate_audio",
333
+ "omni_reference_task_type",
334
+ "prompt_language",
335
+ },
195
336
  )
337
+ prompt_language = _pop_prompt_language(parameters)
196
338
  duration = parameters.get("duration")
197
339
  if duration is not None and (
198
340
  not isinstance(duration, int)
199
341
  or isinstance(duration, bool)
200
- or not 1 <= duration <= 15
342
+ or (duration != -1 and not 1 <= duration <= 30)
201
343
  ):
202
- raise ValueError("Seedance duration must be an integer from 1 to 15")
203
- if duration is not None:
344
+ raise ValueError("Seedance duration must be -1 or an integer from 1 to 30")
345
+ if duration is not None and duration != -1:
204
346
  if duration_range is None:
205
347
  raise ValueError("Seedance duration needs an explicit model profile")
206
348
  minimum, maximum = duration_range
207
- if not 1 <= minimum <= maximum <= 15 or not minimum <= duration <= maximum:
349
+ if not 1 <= minimum <= maximum <= 30 or not minimum <= duration <= maximum:
208
350
  raise ValueError("Seedance duration is outside the configured model profile")
209
351
  ratio = parameters.get("ratio")
210
352
  if ratio is not None and (
@@ -217,38 +359,78 @@ def compile_seedance_payload(
217
359
  raise ValueError("Seedance ratio needs an explicit model profile")
218
360
  if not configured_ratios <= SEEDANCE_RATIOS or ratio.strip() not in configured_ratios:
219
361
  raise ValueError("Seedance ratio is outside the configured model profile")
220
- text = prompt
221
- if ratio is not None:
222
- text += f" --ratio {ratio.strip()}"
223
- if duration is not None:
224
- text += f" --dur {duration}"
362
+ ratio = ratio.strip()
363
+ generate_audio = parameters.get("generate_audio")
364
+ if generate_audio is not None and not isinstance(generate_audio, bool):
365
+ raise ValueError("Seedance generate_audio must be a boolean")
366
+ task_type = parameters.get("omni_reference_task_type")
367
+ if task_type is not None and task_type not in {"auto", "reference", "edit", "extend"}:
368
+ raise ValueError("Seedance omni reference task type is invalid")
369
+ counters = {"image_url": 0, "video_url": 0, "audio_url": 0}
370
+ labels = {"image_url": "图片", "video_url": "视频", "audio_url": "音频"}
371
+ reference_tokens: list[str] = []
372
+ for role in reference_roles:
373
+ field = SEEDANCE_REFERENCE_ROLES.get(role)
374
+ if field is None:
375
+ raise ValueError(f"unsupported Seedance reference role: {role}")
376
+ counters[field] += 1
377
+ reference_tokens.append(f"@{labels[field]}{counters[field]}")
378
+ if task_type in {"edit", "extend"} and "reference_video" not in reference_roles:
379
+ raise ValueError(f"Seedance {task_type} requires a reference video")
380
+ if task_type == "edit" and (ratio != "adaptive" or duration != -1):
381
+ raise ValueError("Seedance edit requires adaptive ratio and duration -1")
382
+ if task_type == "extend" and ratio != "adaptive":
383
+ raise ValueError("Seedance extend requires an adaptive ratio")
384
+ text = _prompt_with_reference_contract(
385
+ prompt,
386
+ job,
387
+ prompt_language=prompt_language,
388
+ reference_tokens=reference_tokens,
389
+ zh_reference_prefix=(
390
+ "输入素材" if task_type in {"edit", "extend"} else "参考"
391
+ ),
392
+ zh_heading=(
393
+ "输入素材约束:" if task_type in {"edit", "extend"} else "参考约束:"
394
+ ),
395
+ )
225
396
  content: list[dict[str, Any]] = [{"type": "text", "text": text}]
226
397
  for index, url in enumerate(reference_urls):
227
398
  if not isinstance(url, str) or not url:
228
399
  raise ValueError("Seedance reference URL must be non-empty")
400
+ role = reference_roles[index]
401
+ field = SEEDANCE_REFERENCE_ROLES[role]
229
402
  suffix = Path(str(references[index])).suffix.casefold()
230
- if suffix in {".png", ".jpg", ".jpeg", ".webp"}:
231
- parsed = urllib.parse.urlparse(url)
232
- if (
233
- parsed.scheme == "https" and parsed.netloc
234
- ) or (
403
+ expected = {
404
+ "image_url": {".png", ".jpg", ".jpeg", ".webp"},
405
+ "video_url": {".mp4", ".mov", ".webm"},
406
+ "audio_url": {".wav", ".mp3", ".m4a", ".aac", ".flac"},
407
+ }[field]
408
+ if suffix not in expected:
409
+ raise ValueError(f"Seedance {role} does not match the reference file type")
410
+ parsed = urllib.parse.urlparse(url)
411
+ if not (
412
+ (parsed.scheme == "https" and parsed.netloc)
413
+ or (
235
414
  parsed.scheme == "asset"
236
415
  and parsed.netloc.startswith("asset-")
237
416
  and not parsed.path
238
- ):
239
- pass
240
- else:
241
- raise ValueError("Seedance reference URL must be HTTPS or asset://")
242
- content.append(
243
- {
244
- "type": "image_url",
245
- "image_url": {"url": url},
246
- "role": "reference_image",
247
- }
248
417
  )
249
- else:
250
- raise ValueError("Seedance adapter accepts image references only")
251
- return {"model": model.strip(), "content": content}
418
+ or _is_inline_reference(url)
419
+ ):
420
+ raise ValueError(
421
+ "Seedance reference URL must be HTTPS, asset:// or a base64 data URI"
422
+ )
423
+ content.append({"type": field, field: {"url": url}, "role": role})
424
+ body: dict[str, Any] = {"model": model.strip(), "content": content}
425
+ if ratio is not None:
426
+ body["ratio"] = ratio
427
+ if duration is not None:
428
+ body["duration"] = duration
429
+ if generate_audio is not None:
430
+ body["generate_audio"] = generate_audio
431
+ if task_type is not None:
432
+ body["omni_reference_task_type"] = task_type
433
+ return body
252
434
 
253
435
 
254
436
  def _seedance_runtime_profile(
@@ -288,7 +470,7 @@ def _seedance_runtime_profile(
288
470
  code="invalid_model_profile",
289
471
  )
290
472
  if duration_range is not None and not (
291
- 1 <= duration_range[0] <= duration_range[1] <= 15
473
+ 1 <= duration_range[0] <= duration_range[1] <= 30
292
474
  ):
293
475
  raise AdapterFailure(
294
476
  "Seedance duration profile is invalid",
@@ -298,12 +480,198 @@ def _seedance_runtime_profile(
298
480
  return allowed_ratios, duration_range
299
481
 
300
482
 
483
+ def compile_minimax_h3_payload(
484
+ job: Mapping[str, Any],
485
+ *,
486
+ model: str,
487
+ reference_urls: Sequence[str] = (),
488
+ reference_roles: Sequence[str] = (),
489
+ allowed_ratios: Collection[str] | None = None,
490
+ allowed_resolutions: Collection[str] | None = None,
491
+ duration_range: tuple[int, int] | None = None,
492
+ ) -> dict[str, Any]:
493
+ """Compile a video job into the official MiniMax video-generation task body.
494
+
495
+ ``model`` is mandatory for the same reason it is for Seedance: which release
496
+ is enabled, and what it accepts, is deployment configuration, never an
497
+ assumption made here.
498
+ """
499
+ prompt, parameters = _require_job(job, "video")
500
+ if not isinstance(model, str) or not model.strip():
501
+ raise ValueError("MiniMax video model must be explicitly configured")
502
+ references = job.get("references", [])
503
+ if not isinstance(references, list) or len(reference_urls) != len(references):
504
+ raise ValueError("MiniMax reference URLs must match job references")
505
+ if len(reference_roles) != len(reference_urls):
506
+ raise ValueError("MiniMax reference roles must match job references")
507
+ if Path(job["outputs"][0]).suffix.casefold() != ".mp4":
508
+ raise ValueError("MiniMax video adapter requires an MP4 target")
509
+ parameters = _take(
510
+ parameters, {"duration", "ratio", "resolution", "prompt_language"}
511
+ )
512
+ prompt_language = _pop_prompt_language(parameters)
513
+
514
+ duration = parameters.get("duration")
515
+ if not isinstance(duration, int) or isinstance(duration, bool):
516
+ raise ValueError("MiniMax video duration must be an integer number of seconds")
517
+ if duration_range is None:
518
+ raise ValueError("MiniMax video duration needs an explicit model profile")
519
+ minimum, maximum = duration_range
520
+ if not 1 <= minimum <= maximum or not minimum <= duration <= maximum:
521
+ raise ValueError("MiniMax video duration is outside the configured model profile")
522
+
523
+ resolution = parameters.get("resolution")
524
+ if not isinstance(resolution, str) or resolution.strip() not in MINIMAX_VIDEO_RESOLUTIONS:
525
+ raise ValueError("MiniMax video resolution is outside the supported profile")
526
+ configured_resolutions = set(allowed_resolutions or ())
527
+ if not configured_resolutions:
528
+ raise ValueError("MiniMax video resolution needs an explicit model profile")
529
+ if (
530
+ not configured_resolutions <= MINIMAX_VIDEO_RESOLUTIONS
531
+ or resolution.strip() not in configured_resolutions
532
+ ):
533
+ raise ValueError("MiniMax video resolution is outside the configured model profile")
534
+
535
+ ratio = parameters.get("ratio")
536
+ if ratio is not None:
537
+ if not isinstance(ratio, str) or ratio.strip() not in MINIMAX_VIDEO_RATIOS:
538
+ raise ValueError("MiniMax video ratio is outside the supported profile")
539
+ configured_ratios = set(allowed_ratios or ())
540
+ if not configured_ratios:
541
+ raise ValueError("MiniMax video ratio needs an explicit model profile")
542
+ if (
543
+ not configured_ratios <= MINIMAX_VIDEO_RATIOS
544
+ or ratio.strip() not in configured_ratios
545
+ ):
546
+ raise ValueError("MiniMax video ratio is outside the configured model profile")
547
+
548
+ counters = {"image_url": 0, "video_url": 0, "audio_url": 0}
549
+ labels = {"image_url": "Picture", "video_url": "Video", "audio_url": "Audio"}
550
+ reference_tokens: list[str] = []
551
+ for role in reference_roles:
552
+ field = MINIMAX_VIDEO_ROLES.get(role)
553
+ if field is None:
554
+ raise ValueError(f"unsupported MiniMax reference role: {role}")
555
+ counters[field] += 1
556
+ reference_tokens.append(f"<{labels[field]} {counters[field]}>")
557
+ text = _prompt_with_reference_contract(
558
+ prompt,
559
+ job,
560
+ prompt_language=prompt_language,
561
+ reference_tokens=reference_tokens,
562
+ )
563
+ if len(text) > MINIMAX_VIDEO_PROMPT_LIMIT:
564
+ raise ValueError("MiniMax video prompt exceeds the provider limit")
565
+ content: list[dict[str, Any]] = [{"type": "text", "text": text}]
566
+ seen_roles: list[str] = []
567
+ for index, url in enumerate(reference_urls):
568
+ role = reference_roles[index]
569
+ if role not in MINIMAX_VIDEO_ROLES:
570
+ raise ValueError(f"unsupported MiniMax reference role: {role}")
571
+ if role in {"first_frame", "last_frame"} and role in seen_roles:
572
+ raise ValueError(f"MiniMax accepts one {role} reference")
573
+ seen_roles.append(role)
574
+ if not isinstance(url, str) or not url:
575
+ raise ValueError("MiniMax reference URL must be non-empty")
576
+ parsed = urllib.parse.urlparse(url)
577
+ if not (
578
+ (parsed.scheme == "https" and parsed.netloc)
579
+ or (parsed.scheme == "mm_file" and parsed.netloc and not parsed.path)
580
+ or _is_inline_reference(url)
581
+ ):
582
+ raise ValueError(
583
+ "MiniMax reference URL must be HTTPS, mm_file:// or a base64 data URI"
584
+ )
585
+ field = MINIMAX_VIDEO_ROLES[role]
586
+ content.append({"type": field, field: {"url": url}, "role": role})
587
+ frame_roles = {"first_frame", "last_frame"}
588
+ reference_roles_present = {"reference_image", "reference_video", "reference_audio"}
589
+ if frame_roles.intersection(seen_roles) and reference_roles_present.intersection(
590
+ seen_roles
591
+ ):
592
+ raise ValueError(
593
+ "MiniMax frame conditioning cannot be mixed with reference conditioning"
594
+ )
595
+ if ratio is None and len(content) == 1:
596
+ raise ValueError("MiniMax text-to-video requires an explicit ratio")
597
+ body: dict[str, Any] = {
598
+ "model": model.strip(),
599
+ "content": content,
600
+ "duration": duration,
601
+ "resolution": resolution.strip(),
602
+ }
603
+ if ratio is not None:
604
+ if ratio.strip() == "adaptive" and len(content) == 1:
605
+ raise ValueError("MiniMax text-to-video cannot use an adaptive ratio")
606
+ body["ratio"] = ratio.strip()
607
+ return body
608
+
609
+
610
+ def _minimax_video_runtime_profile() -> tuple[
611
+ frozenset[str] | None, frozenset[str] | None, tuple[int, int] | None
612
+ ]:
613
+ def _set(name: str, supported: Collection[str]) -> frozenset[str] | None:
614
+ raw = os.environ.get(name)
615
+ if raw is None:
616
+ return None
617
+ values = frozenset(item.strip() for item in raw.split(",") if item.strip())
618
+ if not values or not values <= set(supported):
619
+ raise AdapterFailure(
620
+ f"MiniMax video profile is invalid: {name}",
621
+ category="configuration",
622
+ code="invalid_model_profile",
623
+ )
624
+ return values
625
+
626
+ allowed_ratios = _set("MINIMAX_VIDEO_RATIOS", MINIMAX_VIDEO_RATIOS)
627
+ allowed_resolutions = _set("MINIMAX_VIDEO_RESOLUTIONS", MINIMAX_VIDEO_RESOLUTIONS)
628
+ minimum_raw = os.environ.get("MINIMAX_VIDEO_MIN_DURATION")
629
+ maximum_raw = os.environ.get("MINIMAX_VIDEO_MAX_DURATION")
630
+ if (minimum_raw is None) != (maximum_raw is None):
631
+ raise AdapterFailure(
632
+ "MiniMax video duration profile is incomplete",
633
+ category="configuration",
634
+ code="invalid_model_profile",
635
+ )
636
+ try:
637
+ duration_range = (
638
+ (int(minimum_raw), int(maximum_raw))
639
+ if minimum_raw is not None and maximum_raw is not None
640
+ else None
641
+ )
642
+ except ValueError as exc:
643
+ raise AdapterFailure(
644
+ "MiniMax video duration profile is invalid",
645
+ category="configuration",
646
+ code="invalid_model_profile",
647
+ ) from exc
648
+ if duration_range is not None and not 1 <= duration_range[0] <= duration_range[1]:
649
+ raise AdapterFailure(
650
+ "MiniMax video duration profile is invalid",
651
+ category="configuration",
652
+ code="invalid_model_profile",
653
+ )
654
+ return allowed_ratios, allowed_resolutions, duration_range
655
+
656
+
301
657
  def compile_gpt_image_2_payload(job: Mapping[str, Any]) -> dict[str, Any]:
302
658
  """Compile an image job into GPT Image 2 generation/edit fields."""
303
659
  prompt, parameters = _require_job(job, "image")
304
660
  parameters = _take(
305
661
  parameters,
306
- {"width", "height", "size", "quality", "background", "moderation"},
662
+ {
663
+ "width",
664
+ "height",
665
+ "size",
666
+ "quality",
667
+ "background",
668
+ "moderation",
669
+ "prompt_language",
670
+ },
671
+ )
672
+ prompt_language = _pop_prompt_language(parameters)
673
+ prompt = _prompt_with_reference_contract(
674
+ prompt, job, prompt_language=prompt_language
307
675
  )
308
676
  if len(prompt) > 32000:
309
677
  raise ValueError("GPT Image prompt exceeds the provider limit")
@@ -499,7 +867,9 @@ def _read_reference(path: Path) -> bytes:
499
867
  raise ValueError("reference is not a regular file")
500
868
  if before.st_size > MAX_REFERENCE_BYTES:
501
869
  raise ValueError("reference exceeds the provider input size limit")
502
- flags = os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0)
870
+ # Windows opens files in text mode unless told otherwise, which stops the
871
+ # read at the first 0x1A byte -- the seventh byte of every PNG signature.
872
+ flags = os.O_RDONLY | getattr(os, "O_BINARY", 0) | getattr(os, "O_NOFOLLOW", 0)
503
873
  descriptor = os.open(path, flags)
504
874
  try:
505
875
  opened = os.fstat(descriptor)
@@ -573,6 +943,97 @@ def _reference_paths(job: Mapping[str, Any]) -> list[Path]:
573
943
  return result
574
944
 
575
945
 
946
+ def _binding_roles(
947
+ job: Mapping[str, Any], *, allowed: Mapping[str, str], provider: str
948
+ ) -> list[str]:
949
+ """The provider role of each reference, taken from the confirmed job.
950
+
951
+ `role` is the production-side translation of the creator document's 用途, so
952
+ it is the job -- not this adapter -- that decides what a picture is for.
953
+ """
954
+ bindings = job.get("reference_bindings", [])
955
+ references = job.get("references", [])
956
+ if not isinstance(bindings, list) or len(bindings) != len(references):
957
+ raise AdapterFailure(
958
+ f"{provider} references need one reference_bindings entry each, "
959
+ "carrying the provider role for that file",
960
+ category="configuration",
961
+ code="missing_reference_roles",
962
+ )
963
+ roles: list[str] = []
964
+ for binding in bindings:
965
+ role = binding.get("role") if isinstance(binding, Mapping) else None
966
+ if not isinstance(role, str) or role not in allowed:
967
+ raise AdapterFailure(
968
+ f"{provider} reference role must be one of "
969
+ + ", ".join(sorted(allowed))
970
+ + f"; got {role!r}",
971
+ category="configuration",
972
+ code="invalid_reference_role",
973
+ )
974
+ roles.append(role)
975
+ return roles
976
+
977
+
978
+ def _inline_reference_urls(
979
+ paths: Sequence[Path], roles: Sequence[str], *, allowed: Mapping[str, str], provider: str
980
+ ) -> list[str]:
981
+ """Encode local project references as `data:` URIs the provider accepts."""
982
+ urls: list[str] = []
983
+ total = 0
984
+ for path, role in zip(paths, roles):
985
+ field = allowed[role]
986
+ suffix = path.suffix.casefold()
987
+ mime = INLINE_REFERENCE_MIME.get(suffix)
988
+ expected = field.split("_", 1)[0]
989
+ if mime is None or not mime.startswith(expected):
990
+ raise AdapterFailure(
991
+ f"{provider} {role} does not accept a {suffix or 'suffixless'} file",
992
+ category="configuration",
993
+ code="invalid_reference_type",
994
+ )
995
+ try:
996
+ content = path.read_bytes()
997
+ except OSError as exc:
998
+ raise AdapterFailure(
999
+ f"{provider} could not read a project reference",
1000
+ category="configuration",
1001
+ code="unreadable_reference",
1002
+ ) from exc
1003
+ if not content:
1004
+ raise AdapterFailure(
1005
+ f"{provider} project reference is empty",
1006
+ category="configuration",
1007
+ code="empty_reference",
1008
+ )
1009
+ # The bytes have to be the media type the extension claims, or the
1010
+ # provider rejects a request this adapter said was well formed.
1011
+ _validate_media_content(path.name, content)
1012
+ limit = INLINE_REFERENCE_LIMITS[field]
1013
+ if len(content) > limit:
1014
+ raise AdapterFailure(
1015
+ f"{provider} reference exceeds the {limit // (1024 * 1024)}MB inline "
1016
+ "limit; host it and bind an HTTPS URL instead",
1017
+ category="configuration",
1018
+ code="reference_too_large",
1019
+ )
1020
+ encoded = base64.b64encode(content).decode("ascii")
1021
+ total += len(encoded)
1022
+ if total > INLINE_REFERENCE_BODY_LIMIT:
1023
+ raise AdapterFailure(
1024
+ f"{provider} inline references exceed the request body limit; "
1025
+ "host the largest ones and bind HTTPS URLs instead",
1026
+ category="configuration",
1027
+ code="reference_body_too_large",
1028
+ )
1029
+ urls.append(f"data:{mime};base64,{encoded}")
1030
+ return urls
1031
+
1032
+
1033
+ def _is_inline_reference(url: str) -> bool:
1034
+ return bool(re.fullmatch(r"data:[\w.+-]+/[\w.+-]+;base64,[A-Za-z0-9+/]+=*", url))
1035
+
1036
+
576
1037
  def _validate_media_content(target: str, content: bytes) -> None:
577
1038
  suffix = Path(target).suffix.casefold()
578
1039
  signatures = {
@@ -677,14 +1138,20 @@ def _run_seedance(job: Mapping[str, Any]) -> tuple[Path, str]:
677
1138
  token = _credential("ARK_API_KEY")
678
1139
  model = os.environ.get("SEEDANCE_MODEL", "")
679
1140
  references = _reference_paths(job)
680
- if references:
681
- raise AdapterFailure(
682
- "Seedance local references require an external trusted HTTPS upload adapter"
683
- )
1141
+ reference_roles = (
1142
+ _binding_roles(job, allowed=SEEDANCE_REFERENCE_ROLES, provider="Seedance")
1143
+ if references
1144
+ else []
1145
+ )
1146
+ reference_urls = _inline_reference_urls(
1147
+ references, reference_roles, allowed=SEEDANCE_REFERENCE_ROLES, provider="Seedance"
1148
+ )
684
1149
  allowed_ratios, duration_range = _seedance_runtime_profile()
685
1150
  body = compile_seedance_payload(
686
1151
  job,
687
1152
  model=model,
1153
+ reference_urls=reference_urls,
1154
+ reference_roles=reference_roles,
688
1155
  allowed_ratios=allowed_ratios,
689
1156
  duration_range=duration_range,
690
1157
  )
@@ -861,6 +1328,94 @@ def _run_minimax(job: Mapping[str, Any]) -> tuple[Path, str | None]:
861
1328
  )
862
1329
 
863
1330
 
1331
+ def _run_minimax_video(job: Mapping[str, Any]) -> tuple[Path, str]:
1332
+ token = _credential("MINIMAX_API_KEY")
1333
+ model = os.environ.get("MINIMAX_VIDEO_MODEL", "")
1334
+ references = _reference_paths(job)
1335
+ reference_roles = (
1336
+ _binding_roles(job, allowed=MINIMAX_VIDEO_ROLES, provider="MiniMax")
1337
+ if references
1338
+ else []
1339
+ )
1340
+ reference_urls = _inline_reference_urls(
1341
+ references, reference_roles, allowed=MINIMAX_VIDEO_ROLES, provider="MiniMax"
1342
+ )
1343
+ ratios, resolutions, duration_range = _minimax_video_runtime_profile()
1344
+ body = compile_minimax_h3_payload(
1345
+ job,
1346
+ model=model,
1347
+ reference_urls=reference_urls,
1348
+ reference_roles=reference_roles,
1349
+ allowed_ratios=ratios,
1350
+ allowed_resolutions=resolutions,
1351
+ duration_range=duration_range,
1352
+ )
1353
+ base = _base_url("MINIMAX_VIDEO_BASE_URL", MINIMAX_VIDEO_BASE_URL)
1354
+ created, _ = _request_json(
1355
+ f"{base}/video_generation",
1356
+ provider="minimax-h3",
1357
+ body=body,
1358
+ token=token,
1359
+ )
1360
+ task_id = created.get("task_id")
1361
+ if not isinstance(task_id, str) or not task_id:
1362
+ raise AdapterFailure(
1363
+ "MiniMax did not return a task id",
1364
+ code=_provider_code(created) or "missing_task_id",
1365
+ )
1366
+ try:
1367
+ interval = float(os.environ.get("MINIMAX_VIDEO_POLL_INTERVAL", "5"))
1368
+ deadline = time.monotonic() + float(
1369
+ os.environ.get("MINIMAX_VIDEO_TIMEOUT_SECONDS", "1800")
1370
+ )
1371
+ except ValueError as exc:
1372
+ raise AdapterFailure("MiniMax polling configuration is invalid") from exc
1373
+ if interval <= 0 or deadline <= time.monotonic():
1374
+ raise AdapterFailure("MiniMax polling configuration is invalid")
1375
+ while time.monotonic() < deadline:
1376
+ document, _ = _request_json(
1377
+ f"{base}/query/video_generation/"
1378
+ + urllib.parse.quote(task_id, safe=""),
1379
+ provider="minimax-h3",
1380
+ method="GET",
1381
+ token=token,
1382
+ )
1383
+ task = document.get("task")
1384
+ status = task.get("status") if isinstance(task, Mapping) else None
1385
+ if status == "succeeded":
1386
+ content = task.get("content") if isinstance(task, Mapping) else None
1387
+ url = content.get("url") if isinstance(content, Mapping) else None
1388
+ if not isinstance(url, str):
1389
+ raise AdapterFailure(
1390
+ "MiniMax succeeded without a video URL",
1391
+ code="missing_video_url",
1392
+ request_id=task_id,
1393
+ )
1394
+ return _download(
1395
+ job, url, job["outputs"][0], provider="minimax-h3"
1396
+ ), task_id
1397
+ if isinstance(status, str) and status.casefold() in TERMINAL_FAILURES:
1398
+ raise AdapterFailure(
1399
+ "MiniMax video task failed",
1400
+ code="task_" + status.casefold(),
1401
+ request_id=task_id,
1402
+ )
1403
+ if status not in {"queued", "running", "processing", "pending", "in_progress"}:
1404
+ raise AdapterFailure(
1405
+ "MiniMax returned an unknown task status",
1406
+ code="unknown_task_status",
1407
+ request_id=task_id,
1408
+ )
1409
+ time.sleep(interval)
1410
+ raise AdapterFailure(
1411
+ "MiniMax video task polling timed out",
1412
+ category="timeout",
1413
+ code="task_poll_timeout",
1414
+ request_id=task_id,
1415
+ retryable=True,
1416
+ )
1417
+
1418
+
864
1419
  def _selftest() -> None:
865
1420
  image = {
866
1421
  "modality": "image", "prompt": "portrait", "references": [],
@@ -884,8 +1439,30 @@ def _selftest() -> None:
884
1439
  )
885
1440
  if seedance["model"] != "configured-model":
886
1441
  raise RuntimeError("Seedance model self-test failed")
887
- if not seedance["content"][0]["text"].endswith("--ratio 9:16 --dur 5"):
888
- raise RuntimeError("Seedance prompt switch self-test failed")
1442
+ if seedance.get("ratio") != "9:16" or seedance.get("duration") != 5:
1443
+ raise RuntimeError("Seedance parameter self-test failed")
1444
+ minimax_video = compile_minimax_h3_payload(
1445
+ {**video, "parameters": {"duration": 6, "ratio": "9:16", "resolution": "768P"}},
1446
+ model="configured-video-model",
1447
+ allowed_ratios={"9:16"},
1448
+ allowed_resolutions={"768P"},
1449
+ duration_range=(4, 15),
1450
+ )
1451
+ if minimax_video["model"] != "configured-video-model":
1452
+ raise RuntimeError("MiniMax video model self-test failed")
1453
+ if minimax_video["duration"] != 6 or minimax_video["resolution"] != "768P":
1454
+ raise RuntimeError("MiniMax video parameter self-test failed")
1455
+ try:
1456
+ compile_minimax_h3_payload(
1457
+ {**video, "parameters": {"duration": 6, "resolution": "768P"}},
1458
+ model="configured-video-model",
1459
+ allowed_resolutions={"768P"},
1460
+ duration_range=(4, 15),
1461
+ )
1462
+ except ValueError:
1463
+ pass
1464
+ else:
1465
+ raise AssertionError("MiniMax text-to-video without a ratio was accepted")
889
1466
  compiled_music = compile_minimax_music_payload(music)
890
1467
  if compiled_music["model"] != MINIMAX_MUSIC_MODEL:
891
1468
  raise RuntimeError("MiniMax model self-test failed")
@@ -908,16 +1485,21 @@ def _selftest() -> None:
908
1485
  invalid_reference,
909
1486
  model="configured-model",
910
1487
  reference_urls=["asset://asset-example-clip"],
1488
+ reference_roles=["reference_image"],
911
1489
  )
912
1490
  except ValueError:
913
1491
  pass
914
1492
  else:
915
- raise AssertionError("non-image Seedance reference was accepted")
1493
+ raise AssertionError("mismatched Seedance reference role was accepted")
916
1494
 
917
1495
 
918
1496
  def main() -> int:
919
1497
  parser = argparse.ArgumentParser(description=__doc__)
920
- parser.add_argument("provider", nargs="?", choices=("seedance", "gpt-image-2", "minimax-music"))
1498
+ parser.add_argument(
1499
+ "provider",
1500
+ nargs="?",
1501
+ choices=("seedance", "gpt-image-2", "minimax-music", "minimax-h3"),
1502
+ )
921
1503
  parser.add_argument("--selftest", action="store_true")
922
1504
  args = parser.parse_args()
923
1505
  if args.selftest:
@@ -929,7 +1511,12 @@ def main() -> int:
929
1511
  job = json.load(sys.stdin.buffer)
930
1512
  if not isinstance(job, Mapping):
931
1513
  raise ValueError("adapter input must be an object")
932
- runners = {"seedance": _run_seedance, "gpt-image-2": _run_openai, "minimax-music": _run_minimax}
1514
+ runners = {
1515
+ "seedance": _run_seedance,
1516
+ "gpt-image-2": _run_openai,
1517
+ "minimax-music": _run_minimax,
1518
+ "minimax-h3": _run_minimax_video,
1519
+ }
933
1520
  path, provider_job_id = runners[args.provider](job)
934
1521
  response: dict[str, Any] = {
935
1522
  "outputs": [{"target": job["outputs"][0], "source": str(path)}]