dsh-image-video 0.3.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ihero3
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,344 @@
1
+ # 🎨 dsh-image-video — AI 文生图 / 文生视频插件 for DeepSeek Harness
2
+
3
+ **为 DeepSeek Harness 对话模型注册 `generate_image` / `generate_video` 两个工具。模型在对话中自主决策调用,生成结果自动下载到本地 `outputs/`,图片经 attachment 服务内嵌渲染——对话即创作,无需离开终端。**
4
+
5
+ > DSH 生态有丰富的工具插件,但缺少一个轻量、即装即用的文生图/文生视频入口。dsh-image-video 填补这个空白:两个工具、四家服务商、零外部依赖,热插拔即用。
6
+
7
+ ## 核心特性
8
+
9
+ - **图片单次提交与幂等找回** — `imageTransport: auto` 优先使用异步任务;提交超时/5xx 时只按 `request_id` 反查原任务,默认不盲目重提、不换模型、不生成候选,异步端点未启用时安全降级同步单次提交
10
+ - **统一品牌水印后处理** — 图片在写入 `outputsDir` 和附件前以内存方式叠加可配置水印,桌面交互流直接展示最终水印图,并透出传输方式、提交次数与后处理元数据
11
+ - **两个工具,自然语言触发** — 模型在对话中自主决策何时调用 `generate_image` / `generate_video`,无需手动指令
12
+ - **四家服务商 + 按模型家族自动路由** — Threerouter(聚合器,默认)、万象 wanx(阿里云百炼)、MiniMax 官方平台、Seedance2.5(火山引擎);显式指定模型时按家族关键词自动选择直连商,Threerouter 永远兜底,回退链透明写入 notes
13
+ - **异步任务不阻塞对话** — `TaskManager` 基于 `ctx.effect()` 托管轮询生命周期,插件卸载时自动取消排队任务、清理定时器,杜绝内存泄漏
14
+ - **图片内嵌渲染** — 图片字节经 attachment 服务持久化,附件引用走 `presentationMeta` UI-only 通道,模型只见文本摘要,纯文本模型照常工作
15
+ - **统一异常分类** — `GenerationError` 五类错误(auth / quota / task / timeout / network),可重试错误指数退避,不可重试错误即时中止
16
+ - **声明式服务依赖** — `inject = ['tools']` 顶部声明 + `ctx.inject(['attachments'])` 显式注入,不假设其他插件内部实现
17
+
18
+ ## 能力概览
19
+
20
+ | 工具 | 能力 | 服务商 | 异步轮询 | 对话渲染 |
21
+ |---|---|---|---|---|
22
+ | `generate_image` | 文生图 + **图生图**(可选 `image` 参考图入参) | **Threerouter**(默认)/ 万象 wanx / MiniMax 官方 / Seedance2.5 | 同步或异步(自动适配) | 图片经 `presentationMeta` 内嵌渲染;模型只见文本摘要 |
23
+ | `generate_video` | 文生短视频 + 图生视频(首帧驱动,上限 10s) | **Threerouter**(默认)/ 万象 wanx / MiniMax 官方 / Seedance2.5 | 始终异步轮询,不阻塞对话 | 本地文件路径 + 源地址 |
24
+
25
+ ## Provider 矩阵
26
+
27
+ | 服务商 | 渠道 | 文生图 | 图生图 | 文生视频 | 备注 |
28
+ |---|---|---|---|---|---|
29
+ | **Threerouter**(默认,聚合器) | threerouter.com Bearer Key | ✅ `/images/generations` 同步(默认 `qwen-image-3.0`) | ✅ 同端点 `image` 参考图字段(默认 `qwen-image-3.0-pro`;2026-09 文档:/images/edits 已移除) | ✅ 已验证(MiniMax-H3 / wan 系) | 生图走 OpenAI Images 标准结构(url/b64_json 双形态 + 顶层 url 兼容);视频走 `/v1/media/generations` + 轮询 |
30
+ | **万象 wanx**(阿里云百炼) | DashScope `sk-` Key | ✅ 已验证(wanx2.1-t2i-turbo) | ✅ **已实测**(wanx2.1-imageedit,`base_image_url` 字段,参考图宽度须 512–4096px) | ✅ 已验证(wan2.2-t2v-plus) | i2i 为 description_edit 异步任务;图片同步/异步自适应;视频始终异步 |
31
+ | **MiniMax 官方平台** | platform.minimaxi.com Key | ✅ `image-01`(待 key 实测) | ✅ `subject_reference` 主体一致性(待 key 实测) | ✅ 适配器已实现(video-generation v2,待 key 实测) | 图片同步返回 **base64**(无 URL),插件直接落盘;i2i 语义为「保留主体换场景」,每次仅 1 张参考图;Hailuo 系视频;缺省注入 `768P` |
32
+ | **Seedance2.5**(火山引擎 Ark) | ARK API Key | ✅ 适配器已实现(即梦 3.0) | ✅ Seedream 4.0 `image` 入参(待 key 实测) | ✅ 适配器已实现 | 图片同步返回 URL(24h 有效立即下载);i2i 默认模型自动切换 Seedream 4.0(3.0 不支持参考图);显式关组图与水印;视频走异步任务 |
33
+
34
+ ## 模型家族路由
35
+
36
+ 显式指定 `model` 参数时,插件按厂商关键词(小写包含匹配,一条规则覆盖一个家族)构建候选服务商序列,按序尝试提交:
37
+
38
+ | model 含关键词 | 家族 | 候选服务商(有序) |
39
+ |---|---|---|
40
+ | `minimax` / `hailuo` | MiniMax | `minimax`(官方直连)→ `threerouter`(图片+视频) |
41
+ | `doubao` / `seedance` / `seedream` | 火山方舟 | `seedance`(直连)→ `threerouter` |
42
+ | `wan`(覆盖 `wan*` 与 `wanx*`) | 通义万相 | `wanx`(百炼直连)→ `threerouter` |
43
+ | 无命中(自定义模型) | 未知 | 仅配置链 + `threerouter` |
44
+
45
+ 候选序列 = **配置链服务商**(composer 会话选定 > 配置默认 > 激活服务商,配置优先)→ 家族直连商 → **threerouter 聚合器兜底**(其目录覆盖所有家族的模型)。回退规则:
46
+
47
+ - 仅在提交阶段的「模型不被该服务商接受」类错误(模型不存在 / 能力缺失,判定见 `isModelNotAcceptedError`)时回退下一候选;
48
+ - 403 **分组/模型未开通该能力**(threerouter 实测:`permission_error`「Image generation is not enabled for this group」,文档语义 403=无该分组/模型权限或未开通生图 `allow_image_generation`)视为「该候选无法服务此请求」,同样回退下一候选;
49
+ - 鉴权 Key 无效(401)、无权限语义的 403、配额(429)、网络/超时错误**响亮失败不回退**,不掩盖配置错误;
50
+ - 拿到任务 ID 之后的任何失败**不回退**,绝不重复生成、双重扣费;
51
+ - 回退链写入结果 `notes` 透明告知。
52
+
53
+ 新模型(wan3.0、qwen-video、minimax 新版本等)无需逐个登记即自动命中家族规则,维护点只按厂商关键词。
54
+
55
+ ## 结果透明与配置自证
56
+
57
+ - **每次调用自报身份(固定标准输出)**:`generate_video` / `generate_image` 的可见文本固定包含**提示词、服务商、实际发给上游的模型名**(未指定模型时显示服务商内置默认的实际值)、生成模式、时长参数、分辨率与 `notes`(时长被丢弃、候选回退链)。「这次用了哪个服务商、哪个模型、有没有降级」直接看结果即可,不必去服务商后台对账,也不必从配置推断。配合桌面客户端 keyed toolview(消费 `presentationMeta` 的 `localPath`/`prompt` 渲染内嵌播放器),构成「**提示词 + 播放器 + 结果块**」的固定标准输出:任何人安装本插件即自动获得,无需任何配置。
58
+ - **启动即打印生效配置**:插件 `apply()` 向宿主日志写一行生效配置摘要(`provider` / `defaultVideoProvider` / `defaultImageProvider` / `defaultVideoModel` / `defaultImageModel` / `defaultVideoDuration` / `outputsDir` / 各服务商 key 是否已配置),**绝不包含 key 明文**。多 profile(web / desktop)场景可直接从日志确认哪个 patch 层生效。
59
+
60
+ ## 快速开始
61
+
62
+ ### 1. 安装
63
+
64
+ ```sh
65
+ # GitHub 远程安装
66
+ dsh plugin --profile <profile> add github:ihero3/dsh-image-video
67
+ ```
68
+
69
+ > 构建产物 `lib/` 已随仓库提交,安装过程不执行任何构建脚本,无需 pnpm 的 `allowBuilds` 授权。
70
+
71
+ ### 2. 配置 API Key
72
+
73
+ 在 profile 的 `cordis.patch.yml` 中覆盖默认配置(`~/.dsh/profiles/<profile>/cordis.patch.yml`):
74
+
75
+ ```yaml
76
+ - id: image-video
77
+ config:
78
+ provider: threerouter # threerouter(默认,聚合器) | wanx | minimax | seedance
79
+ threerouter:
80
+ apiKey: !!js process.env.THREEROUTER_API_KEY
81
+ baseURL: ''
82
+ wanx:
83
+ apiKey: !!js process.env.DASHSCOPE_API_KEY
84
+ baseURL: ''
85
+ minimax:
86
+ apiKey: !!js process.env.MINIMAX_API_KEY # 官方平台 key,可选(无则自动跳过该候选)
87
+ baseURL: ''
88
+ seedance:
89
+ apiKey: ''
90
+ baseURL: ''
91
+ defaultImageSize: '1024*1024' # 百炼接口要求 * 分隔
92
+ defaultVideoDuration: 5
93
+ timeoutMs: 60000
94
+ pollIntervalMs: 5000
95
+ pollTimeoutMs: 300000
96
+ retryTimes: 3
97
+ outputsDir: './outputs'
98
+ imageTransport: auto # auto | async(强制) | sync(强制同步)
99
+ imageUnknownStatePolicy: fail # fail(默认)| resubmit-same-key
100
+ watermark:
101
+ enabled: true
102
+ text: 'Threerouter'
103
+ position: bottom-right
104
+ opacity: 0.68
105
+ fontSizeRatio: 0.032
106
+ marginXRatio: 0.028
107
+ marginYRatio: 0.012
108
+ glowEnabled: true
109
+ glowColor: '#ffffff'
110
+ glowBlurRatio: 0.18
111
+ ```
112
+
113
+ 凭证建议通过环境变量注入(`!!js process.env.XXX`),不要明文写入配置文件。DSH 凭证系统支持 `~/.dsh/.credentials.yaml` 和 `~/.dsh/.env` 两种来源。
114
+
115
+ ### 3. 运行
116
+
117
+ ```sh
118
+ # headless 模式(一次性任务)
119
+ dsh --profile headless "帮我画一只赛博朋克风格的猫,1080*1080"
120
+
121
+ # 交互式模式(持续对话)
122
+ dsh --profile <profile>
123
+ ```
124
+
125
+ 模型会自主决策调用 `generate_image` 工具:
126
+
127
+ ```
128
+ 用户:帮我画一只赛博朋克风格的猫,1080*1080
129
+
130
+ 模型(调用 generate_image):
131
+ prompt: "赛博朋克风格的猫"
132
+ size: "1080*1080"
133
+
134
+ → 图片保存到 outputs/<时间戳>-<随机>.png
135
+ → 附件引用经 presentationMeta 持久化,客户端可内嵌渲染
136
+ → 模型收到文本摘要(路径 / 服务商 / 尺寸 / 大小)
137
+ ```
138
+
139
+ **文生视频:**
140
+
141
+ ```
142
+ 用户:生成一段 5 秒的海浪拍打沙滩视频
143
+
144
+ 模型(调用 generate_video):
145
+ prompt: "海浪拍打沙滩"
146
+ duration: 5
147
+
148
+ → 后台轮询任务状态(不阻塞对话)
149
+ → 完成后视频保存到 outputs/<时间戳>-<随机>.mp4
150
+ → 对话返回文件路径
151
+ ```
152
+
153
+ **图生视频(让图片动起来):**
154
+
155
+ ```
156
+ 用户:参考这张图 /path/photo.png 生成一段视频
157
+
158
+ 模型(调用 generate_video):
159
+ prompt: "让画面自然地动起来"
160
+ image: "/path/photo.png"
161
+ duration: 5
162
+
163
+ → 本地路径自动编码为 data URL(http(s)/data URL 原样透传)
164
+ → 存在 image 时构图由首帧决定:不传 aspectRatio,模型用服务商内置 i2v 默认模型(图生视频时该参数被忽略)
165
+ → 完成后视频首帧即该图片
166
+ ```
167
+
168
+ `resolution` 为可选分辨率档位,取值由服务商与模型决定(如 MiniMax-H3:`480P/768P/2K`;wan 图生视频:`480P/1080P`),留空使用服务商默认(MiniMax 系要求显式携带,未指定时插件自动注入 `768P`),不支持的值由上游响亮报错。图生视频的字段映射:threerouter `image`、wanx `input.img_url`、Seedance content 数组 `image_url` 块、MiniMax 官方 `first_frame_image`(MiniMax/Seedance 分支按官方协议实现,未在真实账号验证)。
169
+
170
+ **图生图(参考图)语义差异**:`image` 入参各家模型行为不同——threerouter `/images/edits`(gpt-image-2 等)按提示词自由编辑;方舟 Seedream 4.0+ 按参考图编辑/组图(默认模型自动切换为 Seedream 4.0,3.0 不支持参考图);MiniMax `subject_reference` 是**主体一致性**(保留人物/主体特征换场景换动作),且每次仅支持 1 张参考图、官方示例仅网络 URL(本地图转 data URL 传入待实测)。尺寸默认 **3:4**(`config.defaultImageSize`,接受比例写法 `3:4`/`16:9`/`1:1`):qwen/wan 系自动换算为上游要求的 `宽*高`(长边 1536、32 对齐,如 3:4→1152*1536,实测 `3:4` 原样透传会被千问 400);threerouter(OpenAI 系)/ 方舟系换算为 `宽x高`;MiniMax 比例写法原样透传。**实测约束**:百炼 `wanx2.1-imageedit` 参考图宽度须 512–4096px(过小先放大再调用);其文字渲染弱(拼写易错),需要精准文字时建议生成后本地合成(如 ffmpeg drawtext);千问图像默认开启 prompt_extend+思考模式,出图约 1–5 分钟,超时已按官方建议抬到 600s。
171
+
172
+ **时长与模型能力**:wan 系模型(`wan2.2-t2v-plus`、`wan2.7-t2v`)不支持自定义时长——调用时传入 `duration` 会被插件丢弃(不发给上游,避免 `duration customization is not supported` 报错),并在结果 `notes` 中透明注明,实际时长由上游模型默认决定;MiniMax 系(`minimax-h3`)支持 4–15 秒。能力表见 `src/runtime-defaults.ts` 的 `VIDEO_DURATION_UNSUPPORTED`,按上游实测维护。
173
+
174
+ ## 目录结构
175
+
176
+ ```
177
+ dsh-image-video/
178
+ ├── package.json # 依赖定义 + dsh.bundle manifest + build/prepublishOnly 脚本
179
+ ├── cordis.patch.yml # bundle 层插件清单(image-video 行 + 默认 config)
180
+ ├── lib/ # 构建产物,随仓库提交(安装方无需构建)
181
+ ├── tsconfig.json # TypeScript 配置
182
+ ├── tsdown.config.ts # 构建配置(build 脚本调用)
183
+ ├── README.md
184
+ └── src/
185
+ ├── index.ts # 主入口:name / inject / Config / apply
186
+ ├── config.ts # 配置 Schema(Schemastery)
187
+ ├── http-client.ts # 统一 HTTP 客户端 + 异常分类 + 可配置重试
188
+ ├── task-manager.ts # 异步任务管理器(ctx.effect 托管轮询生命周期)
189
+ ├── media.ts # 媒体下载 / 落地 / 摘要文本 / presentationMeta
190
+ ├── providers/
191
+ │ ├── types.ts # ProviderAdapter 接口 + 通用类型
192
+ │ ├── threerouter.ts # Threerouter API 适配器(默认服务商,聚合器:图片+视频)
193
+ │ ├── wanx.ts # 万象(wanx)API 适配器
194
+ │ ├── minimax.ts # MiniMax 官方平台适配器(video-generation v2 + image_generation)
195
+ │ └── seedance.ts # Seedance2.5 API 适配器
196
+ └── tools/
197
+ ├── generate-image.ts # generate_image 工具注册
198
+ └── generate-video.ts # generate_video 工具注册
199
+ ```
200
+
201
+ ## 配置
202
+
203
+ 插件配置通过 Schemastery 暴露,可在 profile 的 `cordis.patch.yml` 或 `--patch` 覆盖层中按 `id: image-video` 覆盖。完整字段:
204
+
205
+ | 字段 | 类型 | 默认值 | 说明 |
206
+ |---|---|---|---|
207
+ | `provider` | `'threerouter' \| 'wanx' \| 'minimax' \| 'seedance'` | `threerouter` | 激活的服务商,切换后立即生效(HMR) |
208
+ | `threerouter.apiKey` | `string` | `''` | Threerouter API Key;`provider=threerouter` 时必填 |
209
+ | `threerouter.baseURL` | `string` | `''` | Threerouter 自定义接口地址,留空用默认端点 |
210
+ | `wanx.apiKey` | `string` | `''` | 万象 API Key;`provider=wanx` 时必填 |
211
+ | `minimax.apiKey` | `string` | `''` | MiniMax 官方平台 API Key;`provider=minimax` 或模型家族路由命中时使用,留空自动跳过该候选 |
212
+ | `wanx.baseURL` | `string` | `''` | 万象自定义接口地址,留空用默认端点 |
213
+ | `seedance.apiKey` | `string` | `''` | Seedance2.5 API Key;`provider=seedance` 时必填 |
214
+ | `seedance.baseURL` | `string` | `''` | Seedance2.5 自定义接口地址,留空用默认端点 |
215
+ | `defaultImageProvider` | `'' \| 'threerouter' \| 'wanx' \| 'seedance'` | `''` | 默认图片服务商,留空跟随激活服务商,adapter 用其内置默认模型 |
216
+ | `defaultVideoProvider` | `'' \| 'threerouter' \| 'wanx' \| 'seedance'` | `''` | 默认视频服务商,留空跟随激活服务商,adapter 用其内置默认模型 |
217
+ | `defaultImageModel` | `string` | `''` | 默认图片模型,留空用服务商内置默认(模型取值链:调用参数 > 此配置 > 内置默认) |
218
+ | `defaultVideoModel` | `string` | `''` | 默认视频模型,留空用服务商内置默认(threerouter 内置 `minimax-h3`) |
219
+ | `defaultImageSize` | `string` | `'1024*1024'` | 默认图片尺寸,形如 `宽*高`(百炼接口要求 `*` 分隔) |
220
+ | `defaultVideoDuration` | `number` | `5` | 默认视频时长(秒),范围 1-10 |
221
+ | `timeoutMs` | `number` | `60000` | 单次 HTTP 请求超时(毫秒) |
222
+ | `pollIntervalMs` | `number` | `5000` | 视频任务轮询间隔(毫秒) |
223
+ | `pollTimeoutMs` | `number` | `300000` | 视频任务整体超时(毫秒),超时中止轮询 |
224
+ | `retryTimes` | `number` | `3` | 可重试错误的最大重试次数(鉴权/配额错误不重试) |
225
+ | `outputsDir` | `string` | `'./outputs'` | 生成媒体落地目录(相对路径基于进程 cwd) |
226
+ | `imageTransport` | `'auto' \| 'async' \| 'sync'` | `'auto'` | 图片传输;auto 优先异步,异步端点未启用时降级同步 |
227
+ | `imageUnknownStatePolicy` | `'fail' \| 'resubmit-same-key'` | `'fail'` | 提交超时/5xx 状态未知时的策略;默认只反查、不重提 |
228
+ | `watermark.enabled` | `boolean` | `true` | 是否在落盘/附件前叠加品牌水印 |
229
+ | `watermark.text` | `string` | `'Threerouter'` | 水印文字;空文本跳过后处理并保留原图 |
230
+ | `watermark.position` | `'top-left' \| 'top-right' \| 'bottom-left' \| 'bottom-right'` | `'bottom-right'` | 水印锚点 |
231
+ | `watermark.opacity` | `number` | `0.68` | 水印透明度,0–1 |
232
+ | `watermark.fontSizeRatio` | `number` | `0.032` | 字号相对图片宽度比例 |
233
+ | `watermark.marginXRatio` / `marginYRatio` | `number` | `0.028` / `0.012` | 水平/垂直边距比例 |
234
+ | `watermark.glowEnabled` | `boolean` | `true` | 是否启用柔光描边 |
235
+ | `watermark.glowColor` / `glowBlurRatio` | `string` / `number` | `'#ffffff'` / `0.18` | 描边颜色与模糊半径比例 |
236
+
237
+ ## 图片渲染设计
238
+
239
+ `generate_image` 的工具结果**只向模型返回文本摘要**(本地路径、服务商、尺寸、大小),不把图片作为 `image` 内容块注入工具结果。原因:纯文本模型或网关不支持图片输入时,生成图片后的下一轮请求会直接 400。
240
+
241
+ 图片字节通过 attachment 服务持久化,附件引用经 `output.presentationMeta` 写入 `tool/result` 事件的 `meta` 字段——**持久化但对模型不可见**,客户端可消费它做内嵌渲染:
242
+
243
+ - **模型可见**:文本摘要(`render` 产物)
244
+ - **UI 可见**:`meta` 中的图片附件信息(`presentationMeta` 产物)
245
+ - 图片文件始终落地 `outputs/` 目录
246
+ - 水印后处理发生在内存中、唯一写盘入口之前;因此 UI 读取 `localPath` 时看到的就是最终水印图
247
+ - `presentationMeta` 同时透出 `transport`、`submitAttempts`、`postprocess`、`requestId`,桌面媒体卡片会展示传输方式、物理提交次数和后处理链路
248
+
249
+ ## 服务依赖
250
+
251
+ 插件通过官方服务插槽声明依赖,不假设其他插件内部实现:
252
+
253
+ | 依赖 | 声明方式 | 必需性 | 提供方 |
254
+ |---|---|---|---|
255
+ | `tools` | `inject = ['tools']` 顶部声明 | 插件必需,缺失则不加载 | dsh-base |
256
+ | `attachments` | `ctx.inject(['attachments'], cb)` | `generate_image` 必需,`generate_video` 不依赖 | dsh-base 的 `attachment-local` |
257
+
258
+ `generate_image` 通过 `ctx.inject` 显式声明 attachment 服务依赖:服务挂载时注册工具,服务撤销时 fiber dispose 自动注销。`generate_video` 始终注册。`cordis.patch.yml` 用 `- insert` 新增 `image-video` 行,不覆盖任何现有插件行,升级保持组合兼容。
259
+
260
+ ## 异常处理
261
+
262
+ 统一封装的 `GenerationError` 分类捕获,返回友好中文提示:
263
+
264
+ | 错误种类 | 触发条件 | 是否重试 |
265
+ |---|---|---|
266
+ | `auth` | HTTP 401/403,API Key 无效或无权限 | 否 |
267
+ | `quota` | HTTP 429,配额耗尽或限流 | 否 |
268
+ | `task` | 任务逻辑错误(参数非法、服务端任务失败) | 否 |
269
+ | `timeout` | 请求超时或任务被取消 | 取消不重试;超时重试 |
270
+ | `network` | 网络抖动、5xx 服务端错误 | 是 |
271
+
272
+ 可重试错误按指数退避重试,次数由 `retryTimes` 控制。
273
+
274
+ ## 开发 & 测试
275
+
276
+ ### 前置
277
+
278
+ ```sh
279
+ cd dsh-image-video
280
+ pnpm install --ignore-workspace --no-frozen-lockfile
281
+ ```
282
+
283
+ ### 单元测试
284
+
285
+ ```sh
286
+ pnpm run test
287
+ ```
288
+
289
+ 34 个测试覆盖:
290
+
291
+ | 文件 | 覆盖 |
292
+ |---|---|
293
+ | `tests/config.test.ts` | 配置 schema 默认值校验、provider 凭证解析、非法输入拒绝 |
294
+ | `tests/http-client.test.ts` | HTTP 状态码 → `GenerationError` 分类映射(401→auth / 429→quota / 500→network) |
295
+ | `tests/media.test.ts` | `Content-Type` → 扩展名推断、JPEG/PNG/WEBP/MP4 映射 |
296
+ | `tests/providers.test.ts` | 万象wanx/Seedance 适配器集成测试:mock `fetch`,覆盖 submit→query 成功路径 + 状态流转 + 异常分类 |
297
+
298
+ ### 类型检查 + 构建
299
+
300
+ ```sh
301
+ pnpm run typecheck # tsc --noEmit
302
+ pnpm run build # tsdown 输出 lib/index.js + 类型
303
+ ```
304
+
305
+ ### DSH 安装验证
306
+
307
+ ```sh
308
+ dsh plugin --profile <profile> add /absolute/path/to/dsh-image-video
309
+ dsh --profile <profile> --dump-config | grep -A3 "dsh-image-video"
310
+ ```
311
+
312
+ 预期输出出现 `# == dsh-image-video` 分层和 `image-video` 插件行。
313
+
314
+ ### 常见排错
315
+
316
+ | 症状 | 解决 |
317
+ |---|---|
318
+ | `pnpm run test` 报 lockfile out of date | 重跑 `pnpm install --ignore-workspace --no-frozen-lockfile` |
319
+ | vitest 里 fetch 真实发网请求 | 用 `vi.stubGlobal('fetch', ...)`,`afterEach` 调 `vi.unstubAllGlobals()` |
320
+ | DSH 启动报 `cannot find module dsh-image-video` | 若用 `link:` 本地路径安装,改过 `src/` 后需跑 `pnpm run build` 让 `lib/` 与源码同步;从 git/npm 安装无需构建,产物已随包提交 |
321
+ | 生成图片但对话无内嵌渲染 | 确认 profile 包含 `attachment-local`;图片文件始终可在 `outputs/` 查看 |
322
+ | `size is not in the correct format` | 百炼接口要求 `*` 分隔(如 `1024*1024`),不是 `x`;插件已自动转换 |
323
+
324
+ ## 服务商 API 参考
325
+
326
+ - **Threerouter**:[统一媒体生成 API](https://threerouter.com/)(`/v1/media/generations` 创建 → `/v1/media/{id}` 轮询 → `/v1/media/{id}/content` 下载)
327
+ - **万象 wanx(阿里云百炼 DashScope)**:[文生图](https://help.aliyun.com/zh/model-studio/text-to-image-guide) / [文生视频](https://help.aliyun.com/zh/model-studio/video-generation)
328
+ - **Seedance2.5(火山引擎 Ark)**:[文生图](https://docs.volcengine.com/docs/85621/1616429) / [文生视频](https://docs.volcengine.com/docs/82379/1520757)
329
+
330
+ ## 依赖
331
+
332
+ - `@deepseek-ai/cordis`(peer):插件框架
333
+ - `@deepseek-ai/dsh-tools`(peer):`defineTool` 工具注册
334
+ - `@deepseek-ai/dsh-attachment`(peer,可选):图片附件持久化服务
335
+ - `@deepseek-ai/dsh-llm`(peer):ContentBlock 类型
336
+ - `@deepseek-ai/schemastery`:配置 Schema
337
+
338
+ ## 诚实声明
339
+
340
+ 模型输出质量由服务商模型决定,插件负责正确传参、可靠轮询和异常分类。API Key 仅在本地使用,不上传任何远程服务。本插件不附属于阿里云或火山引擎。使用各服务商 API 前请确认其服务条款。
341
+
342
+ ## License
343
+
344
+ [MIT](LICENSE)
@@ -0,0 +1,70 @@
1
+ # dsh-image-video bundle patch: 在 profile 根之上插入一行插件条目。
2
+ # 后续 bundle 层或用户 profile 的 cordis.patch.yml 可按 id 引用本行,
3
+ # 覆盖 config(如切换 provider、注入 API Key、调整默认尺寸/时长)。
4
+ #
5
+ # patch 替换目标行的整个 config 而非合并;如需多模式差异化,请在各模式
6
+ # bundle 中重述完整 config。本文件仅声明插件入口与默认配置,不内联凭证。
7
+ #
8
+ # 服务依赖:
9
+ # - tools(必需):工具注册表,由 dsh-base 提供。
10
+ # - attachments(generate_image 必需):attachment 服务,由 dsh-base 的
11
+ # attachment-local 行提供。generate_image 通过 ctx.inject 声明依赖,
12
+ # 服务未挂载时 generate_image 不注册;generate_video 不依赖此服务。
13
+ # 本插件用 - insert 新增 image-video 行,不覆盖任何现有插件行。
14
+
15
+ - insert:
16
+ - id: image-video
17
+ name: 'dsh-image-video'
18
+ config:
19
+ # 激活的生成服务商,threerouter(默认,图片+视频) | wanx | seedance
20
+ provider: threerouter
21
+ # Threerouter 凭证;provider=threerouter 时使用(图片+视频统一入口)
22
+ threerouter:
23
+ apiKey: ''
24
+ baseURL: ''
25
+ # 万象(阿里云百炼)凭证;provider=wanx 时使用
26
+ wanx:
27
+ apiKey: ''
28
+ baseURL: ''
29
+ # MiniMax 官方平台凭证;provider=minimax 时使用
30
+ minimax:
31
+ apiKey: ''
32
+ baseURL: ''
33
+ # Seedance2.5(火山引擎)凭证;provider=seedance 时使用
34
+ seedance:
35
+ apiKey: ''
36
+ baseURL: ''
37
+ # 默认图片服务商;留空跟随激活服务商(threerouter | wanx | seedance)
38
+ defaultImageProvider: ''
39
+ # 默认视频服务商;留空跟随激活服务商(threerouter | wanx | seedance)
40
+ defaultVideoProvider: ''
41
+ # 默认图片尺寸(百炼接口要求 * 分隔,如 1024*1024)
42
+ defaultImageSize: '1024*1024'
43
+ # 默认视频时长(秒),上限 30,由上游模型校验具体能力
44
+ defaultVideoDuration: 5
45
+ # 单次 HTTP 请求超时(毫秒)
46
+ timeoutMs: 60000
47
+ # 视频任务轮询间隔(毫秒)
48
+ pollIntervalMs: 5000
49
+ # 视频任务整体超时(毫秒),超时后中止轮询
50
+ pollTimeoutMs: 600000
51
+ # 可重试错误的最大重试次数
52
+ retryTimes: 3
53
+ # 图片提交传输:auto 优先异步;异步端点未启用时安全降级同步
54
+ imageTransport: auto
55
+ # 提交状态未知时默认失败,不盲目再次生成;可选 resubmit-same-key
56
+ imageUnknownStatePolicy: fail
57
+ # 生成媒体落地目录(相对路径基于进程 cwd 解析)
58
+ outputsDir: './outputs'
59
+ # 最终图片水印:与模型结果使用同一 outputsDir 和 attachment 流程
60
+ watermark:
61
+ enabled: true
62
+ text: 'Threerouter'
63
+ opacity: 0.68
64
+ position: bottom-right
65
+ fontSizeRatio: 0.032
66
+ marginXRatio: 0.028
67
+ marginYRatio: 0.012
68
+ glowEnabled: true
69
+ glowColor: '#ffffff'
70
+ glowBlurRatio: 0.18