@mokuyoaxis/dsh-iris 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +54 -1
  2. package/README.en.md +72 -15
  3. package/README.md +72 -15
  4. package/bin/dsh-iris.js +392 -13
  5. package/docs/AGENT_QUICKSTART.md +35 -0
  6. package/docs/ARCHITECTURE.md +80 -64
  7. package/docs/ARTIFACT_MANIFEST.md +60 -0
  8. package/docs/CORE_RUNTIME_CONTRACT.md +83 -0
  9. package/docs/DSH_CORE_MIGRATION.md +106 -0
  10. package/docs/DSH_RC2_ADAPTATION.md +36 -0
  11. package/docs/HEADLESS_CLI.md +217 -0
  12. package/docs/HOST_ADAPTER_CONTRACT.md +31 -12
  13. package/docs/HOST_DOCTOR.md +2 -0
  14. package/docs/MODEL_PORT_CONTRACT.md +300 -0
  15. package/docs/PROVIDER_ADAPTER_CONTRACT.md +51 -10
  16. package/docs/PROVIDER_RUNTIME_HARNESS.md +33 -0
  17. package/docs/PROVIDER_SUBMISSION_CONTRACT.md +1 -1
  18. package/docs/ROADMAP.md +42 -86
  19. package/docs/SECURITY.md +4 -3
  20. package/docs/assets/logo/README.md +55 -0
  21. package/docs/assets/logo/iris-bubble-concept.svg +67 -0
  22. package/docs/assets/logo/iris-bubble.svg +14 -0
  23. package/docs/assets/logo/iris-interim-flower.png +0 -0
  24. package/docs/assets/logo/iris-logo-mono-inverse.svg +78 -0
  25. package/docs/assets/logo/iris-logo-mono.svg +78 -0
  26. package/docs/assets/logo/iris-logo.svg +78 -0
  27. package/docs/assets/logo/iris-mark-mono-inverse.svg +42 -0
  28. package/docs/assets/logo/iris-mark-mono.svg +42 -0
  29. package/docs/assets/logo/iris-mark.svg +42 -0
  30. package/docs/releases/0.2.0.md +48 -0
  31. package/lib/actions.js +374 -362
  32. package/lib/adapters.js +52 -15
  33. package/lib/api.js +152 -5
  34. package/lib/client.js +763 -48
  35. package/lib/command-service.js +420 -0
  36. package/lib/config.js +56 -43
  37. package/lib/core-artifact-store.js +598 -0
  38. package/lib/core-artifacts.js +4 -0
  39. package/lib/core-attention.js +103 -0
  40. package/lib/core-contract.js +105 -0
  41. package/lib/core-doctor.js +41 -0
  42. package/lib/core-lease-recovery.js +170 -0
  43. package/lib/core-runtime.js +232 -0
  44. package/lib/core-tasks.js +560 -0
  45. package/lib/core-user-projection.js +192 -0
  46. package/lib/doctor.js +92 -33
  47. package/lib/dsh-core-adapter.js +599 -0
  48. package/lib/dsh-host-adapter.js +58 -7
  49. package/lib/generation-input.js +95 -0
  50. package/lib/guard.js +3 -3
  51. package/lib/index.js +159 -54
  52. package/lib/model-invoker.js +218 -0
  53. package/lib/model-port-contract.js +333 -0
  54. package/lib/models.js +97 -11
  55. package/lib/provider-adapter.js +49 -23
  56. package/lib/provider-adapters.js +84 -21
  57. package/lib/provider-catalog.js +171 -0
  58. package/lib/provider-contract.js +72 -3
  59. package/lib/provider-protocol.js +51 -2
  60. package/lib/provider-task-runner.js +328 -0
  61. package/lib/tasks.js +16 -4
  62. package/lib/vision.js +28 -7
  63. package/package.json +8 -8
  64. package/user_guide.md +27 -12
@@ -0,0 +1,217 @@
1
+ # Headless CLI(0.2.0)
2
+
3
+ Iris 0.2.0 提供无 DSH 的本地裁剪、图片 diff、视频抽帧,以及 Provider 媒体任务闭环。结果保存为 Core Artifact,并可在后续进程中检查或导出。Core 内部接口与 Artifact v0 尚未冻结为跨版本公共 SDK。
4
+
5
+ 需要 Node.js ≥ 22.0.0。在自己的项目安装 `npm install @mokuyoaxis/dsh-iris@0.2.0` 后,通过 `npx dsh-iris ...` 使用命令;也可临时运行 `npx --package @mokuyoaxis/dsh-iris@0.2.0 dsh-iris --help`。下文的 `dsh-iris` 指该版本提供的可执行入口,不需要安装 DSH。
6
+
7
+ ## 配置查询与离线诊断
8
+
9
+ ```bash
10
+ dsh-iris providers list --provider-config /absolute/path/to/providers.json
11
+ dsh-iris capabilities list --provider-config /absolute/path/to/providers.json
12
+ dsh-iris doctor --data-root /absolute/path/to/iris-data --json
13
+ ```
14
+
15
+ 前两项只读取显式提供的私有配置文件,不创建 Core Runtime 或数据根,也不请求 Provider。`providers list` 输出供应商身份、启用/配置状态、认证方式、媒体协议及其推断标记、配置能力与模型数;不输出 API Key 或端点。`capabilities list` 按五种配置能力列出 `providerId::modelId` 候选链,沿用 DSH 的“手工分配优先、模型池顺序补齐”规则;空链标为 `gap: no_configured_model`。这些只是配置事实,不证明远端模型或协议可调用。
16
+
17
+ 显式 `doctor --data-root` 是只读 Core 盘点:检查 Task/Artifact 结构、Index 一致性、孤立/未解析条目和写者租约;不创建缺失的根、不执行写入探针、不重建索引,也不释放租约。活跃写者可能使盘点在读取期间变化。省略 `--data-root` 时仍检查 DSH profile,保留原有写入探针,并纳入该 profile 的 `core-v0`。两种模式都返回 legacy/Core 计数对照,显式模式只统计指定根同层的 legacy 文件。退出码为 `0` 正常、`1` 警告、`2` 硬错误;诊断不会调用 Provider。
18
+
19
+ ## 异常退出后的写者租约
20
+
21
+ SIGINT/SIGTERM 可被 CLI 捕获并释放运行中的租约;SIGKILL、OOM 或断电可能留下租约。后续写操作返回 `IRIS_CORE_DATA_ROOT_BUSY` 时,先用 `doctor --data-root <absolute-path> --json` 只读查看 `core-writer-lease`。仅当报告中的 owner PID 为 `missing`,并且你已确认没有跨主机或其他仍在写入的进程,才可显式回显该 PID:
22
+
23
+ ```bash
24
+ dsh-iris runtime recover --data-root /absolute/path/to/iris-data --confirm-stale-pid 12345
25
+ ```
26
+
27
+ 此命令会再次核对租约证据和 PID,写入私有恢复审计记录后释放陈旧租约;活跃、无法确认、损坏或 PID 不匹配时拒绝。它不是 Doctor 的自动副作用,也不会修复 Task/Artifact。不要手动删除 `.iris-runtime-writer-v0` 或把“PID 不存在”单独当作安全证明。
28
+
29
+ ## 裁剪并保存 Artifact
30
+
31
+ ```bash
32
+ dsh-iris run crop \
33
+ --data-root /absolute/path/to/iris-data \
34
+ --input '{"image_path":"/absolute/path/input.png","left":10,"top":20,"width":640,"height":480}'
35
+ ```
36
+
37
+ 输出为 JSON,其中包含 `artifact.id`、媒体类型、字节数、创建时间与裁剪尺寸。结果不会返回输入文件或数据根的绝对路径。
38
+
39
+ ## 本地图片差异与视频抽帧
40
+
41
+ 两项命令只使用本机文件、Core Artifact、Sharp 与可选系统 ffmpeg;不读取 Provider 配置,不发起网络请求:
42
+
43
+ ```bash
44
+ dsh-iris media diff \
45
+ --data-root /absolute/path/to/iris-data \
46
+ --input '{"image_a_path":"/absolute/path/a.png","image_b_path":"/absolute/path/b.png","grid":8,"top_regions":3}'
47
+
48
+ dsh-iris media frames \
49
+ --data-root /absolute/path/to/iris-data \
50
+ --input '{"video_path":"/absolute/path/clip.mp4","max_frames":8,"target_width":640,"format":"jpeg","quality":85}'
51
+ ```
52
+
53
+ `media.diff` 返回差异比例、像素计数、最差区域和 `pixel-diff` 热力图 Artifact。A/B 两侧也可分别用 `image_a_artifact_id` / `image_b_artifact_id` 读取已有图片 Artifact;此时热力图写入 `derived-from` 关系。
54
+
55
+ `media.frames` 返回视频元数据和一组 `video-frame` Artifact;输入也可用 `artifact_id` 指向已有视频 Artifact,此时每帧写入 `frame-of` 关系。帧数最多 20,格式仅支持 `jpeg` 或 `png`;系统没有 ffmpeg/ffprobe 或视频无效时命令稳定失败,不影响其他 CLI 能力。Manifest 和命令结果都不保存宿主输入路径。
56
+
57
+ ## Provider 图片生成
58
+
59
+ CLI 必须同时收到显式 Core 数据根和私有供应商配置文件,不推断 DSH、HOME 或 cwd:
60
+
61
+ ```bash
62
+ dsh-iris run image \
63
+ --data-root /absolute/path/to/iris-core-data \
64
+ --provider-config /absolute/path/to/providers.json \
65
+ --input '{"prompt":"一朵雨后的鸢尾花","model_ref":"provider-id::model-id","size":"1024*1024","n":1}'
66
+ ```
67
+
68
+ `model_ref` 可省略;省略时采用“手工 assignment 顺序优先、模型池顺序补齐”的安全 failover。每个请求从首项开始,只有明确 `not_accepted` 才进入下一项。配置文件须为普通文件,POSIX 权限不得开放给 group/other;API Key、端点、Prompt 和下载 URL都不会写入 Task 输出。
69
+
70
+ 模型必须存在于配置的 `models` 或旧模型字段中,且具备所请求的能力;缺失时返回配置错误,不注入厂商默认模型。显式 `models:[]` 表示没有可用模型。`model_ref` 也接受裸模型名,同名跨供应商时按配置中的供应商顺序取首项;需要精确绑定账号时使用 `providerId::modelId`。显式指定只选择该模型,不扩展 failover 候选。
71
+
72
+ 旧裸 DashScope 账号的已知目录仅在 DSH 配置加载或保存时一次性写入 `models`,随后可见、可编辑;CLI 只读配置,不执行该迁移。独立 CLI 配置须自行声明模型。
73
+
74
+ 同步图片会在一次命令内完成 Task → Attempt → Provider Adapter → download → Artifact。异步图片模型先持久化受理事实与远端 Task ID;之后由用户显式执行单步观察,不建立后台 timer。
75
+
76
+ 视频(t2v/i2v)同样走 Core:
77
+
78
+ ```bash
79
+ dsh-iris run video \
80
+ --data-root /absolute/path/to/iris-data \
81
+ --provider-config /absolute/path/to/providers.json \
82
+ --input '{"prompt":"海浪拍岸","size":"1280*720","duration":5}'
83
+ ```
84
+
85
+ 输入冻结为 `{prompt, size?, duration?, img_data_url?, model_ref?}`:`img_data_url` 是首帧的 `data:image/` data URL(i2v);s2v 数字人上传流程不在 headless 面开放。视频交付走独立 Profile:远端成功后下载为 `video/mp4`,Artifact kind 为 `generated-video`。受理后由显式 `task observe` 逐拍推进(DashScope 视频通常需要数拍)。
86
+
87
+ 语音合成是同步完成型,一次命令内完成 Task → Attempt → 交付:
88
+
89
+ ```bash
90
+ dsh-iris run tts \
91
+ --data-root /absolute/path/to/iris-data \
92
+ --provider-config /absolute/path/to/providers.json \
93
+ --input '{"text":"要合成的文本","voice":"Cherry"}'
94
+ ```
95
+
96
+ 输入冻结为 `{text, voice?, model_ref?}`;产物走语音 Profile:`audio/mpeg`/`audio/wav` 白名单,Artifact kind 为 `generated-audio`(远端 URL 下载物化,inline base64 直接落盘)。同步完成即终态,`task observe/redeliver/cancel` 对终态语音任务一律拒绝,终态失败可用 `task retry` 显式重试为新任务。
97
+
98
+ 转写是上传型异步:
99
+
100
+ ```bash
101
+ dsh-iris run transcribe \
102
+ --data-root /absolute/path/to/iris-data \
103
+ --provider-config /absolute/path/to/providers.json \
104
+ --input '{"audio_url":"https://example.com/public.wav"}'
105
+ # 或本地文件(经首选候选 Provider 的临时存储上传):
106
+ dsh-iris run transcribe \
107
+ --data-root /absolute/path/to/iris-data \
108
+ --provider-config /absolute/path/to/providers.json \
109
+ --input '{"audio_path":"/absolute/path/to/voice.wav"}'
110
+ ```
111
+
112
+ 输入冻结为 `{audio_url | audio_path, model_ref?}`(二选一):`audio_url` 必须是 `https://` 或 `oss://`;`audio_path` 在命令内先上传为临时 `oss://` 地址再提交——**签名/临时 URL 绝不写入 Core 记录**。受理后由显式 `task observe` 逐拍推进;远端成功后正文物化为 `text/plain` 的 `transcript` Artifact(`artifact inspect/export` 可直接读取/导出文本)。终态失败可用 `task retry --input '{"audio_url":"..."}' --confirm-billing` 显式重试为新任务。
113
+
114
+ ## 检查 Task 与 Artifact
115
+
116
+ Task 查询是纯 reader,不需要 Provider 配置,也不会触发 submit、poll、恢复或下载:
117
+
118
+ ```bash
119
+ dsh-iris task list --data-root /absolute/path/to/iris-data
120
+
121
+ dsh-iris task inspect task_0123456789abcdef01234567 \
122
+ --data-root /absolute/path/to/iris-data
123
+ ```
124
+
125
+ 输出只包含已经持久化的安全 Task/Attempt 事实;不返回 Prompt、API Key、下载 URL 或数据根路径。
126
+
127
+ 新建 Attempt 可选保存 `stageTimestamps`:排队、开始提交、提交响应、远端终态、开始下载、下载校验完成和本地 Artifact 处理完成七个边界。字段按发生顺序单调不减;旧记录缺失该字段仍可读取。重新交付会从新的下载开始边界重新计时,不伪造尚未跨过的阶段。
128
+
129
+ 新建 Attempt 还可选保存 `selectionReason`:`explicit` 表示调用方显式提供 `model_ref`,`assignment` 表示候选来自提交当时的能力分配,`pool` 表示由配置模型池按顺序补齐。它与 Attempt 一起在 Provider submit 前落盘;`task inspect` 只展示持久事实,不会用当前可能已变化的 assignment 事后猜测。旧记录没有该字段时保持缺失,不伪造默认值;第几次 failover 仍看 `ordinal`。
130
+
131
+ 已受理的异步图片 Task 可以显式推进一次:
132
+
133
+ ```bash
134
+ dsh-iris task observe task_0123456789abcdef01234567 \
135
+ --data-root /absolute/path/to/iris-data \
136
+ --provider-config /absolute/path/to/providers.json
137
+ ```
138
+
139
+ 每次命令最多执行一次 Provider `poll`;若远端已成功,则在同一次命令中下载并写入 Core Artifact。它不会调用 `submit`、不会采用当前 assignment/failover,也不会自动循环。CLI 只按 Task 中保存的 `providerId::modelId` 恢复原 Adapter:API Key 可以轮换;Provider 被停用、模型被移除或失去图片能力时会在网络请求前失败。
140
+
141
+ 新 Task 还保存提交协议与实际媒体端点的 SHA-256 binding,不保存端点本身。端点或协议改变时,`observe` 会拒绝把旧远端 Task ID 发送给新端点。此前没有 binding 的异步 Task 保持可读,但不会被猜测性恢复;可继续使用创建它的旧入口处理。
142
+
143
+ 同一份"显式单步观察、绝不重新提交"也已在 DSH 侧开放:工作台 API 提供 `POST /iris/api/core/task/:id/reobserve`,用户任务区的 Core 投影行和高级诊断卡提供「重新观察」按钮。三者经同一个 Command Service 命令(对外命令名 `task.reobserve`,实现与 `task.observe` 相同),共用原 Provider/binding 校验;每次调用至多一次 poll,对终态、未受理、无远端 ID 或 binding 漂移的任务在网络前拒绝。
144
+
145
+ 已成功但本地产物取回失败(deliveryState=failed)的 Task 可以显式重新取回,绝不重新生成:
146
+
147
+ ```bash
148
+ dsh-iris task redeliver task_0123456789abcdef01234567 \
149
+ --data-root /absolute/path/to/iris-data \
150
+ --provider-config /absolute/path/to/providers.json
151
+ ```
152
+
153
+ 每次命令至多一次带 redelivery 语义标志的 `poll`(拿远端产物清单)接一轮下载,写入新的 Core Artifact;不调用 `submit`、不新建 Attempt、不产生第二次生成费用。非 failed、无远端 ID 或 binding 漂移时在调用前拒绝;成功后再次调用被同样拒绝,失败回落 failed 后可再次显式执行。DSH 侧同构开放:`POST /iris/api/core/task/:id/redeliver` 与工作台「重新取回作品」按钮(delivery_failed 投影行的唯一动作)。
154
+
155
+ 已受理且尚未终态的 Task 可以显式请求远端取消:
156
+
157
+ ```bash
158
+ dsh-iris task cancel task_0123456789abcdef01234567 \
159
+ --data-root /absolute/path/to/iris-data \
160
+ --provider-config /absolute/path/to/providers.json
161
+ ```
162
+
163
+ **只有供应商明确确认才把 outcome 写成 `canceled`**;供应商不支持远端取消(当前 DashScope 与 OpenAI Images 媒体协议均不提供经过验证的取消实现)时,取消未发生,任务保持真实状态并可继续显式观察;超时或网络失败记 `cancelState=unknown` 且保持非终态,可用 `task observe` 显式收敛。任何分支都绝不伪造"已取消"。已请求过取消但远端未确认的任务拒绝再次取消;竞态下远端先沉淀的成功事实不得被取消覆盖。DSH 侧同构开放:`POST /iris/api/core/task/:id/cancel` 与工作台需二次确认的「取消任务」按钮。
164
+
165
+ 终态且未成功交付的 Task 可以重试为**新任务**。这是控制面唯一产生新的真实计费的动作,必须显式确认:
166
+
167
+ ```bash
168
+ dsh-iris task retry task_0123456789abcdef01234567 \
169
+ --data-root /absolute/path/to/iris-data \
170
+ --provider-config /absolute/path/to/providers.json \
171
+ --input '{"prompt":"重新输入的生成指令"}' \
172
+ --confirm-billing \
173
+ [--model-ref providerId::modelId]
174
+ ```
175
+
176
+ 缺 `--confirm-billing` 时在网络与创建之前拒绝。候选链按当前 assignments/池实况重新解析(不绑定旧 Provider/binding——旧配置可能正是失败原因),`--model-ref` 可显式指定。新 Task 有全新 id、attempts 与提交时实况 binding,并记录单向 `retriedFrom` 指向旧 Task;旧 Task 零变化。**Core 记录不持久化 Prompt**,所以生成指令必须由 `--input` 重新提供,绝不从旧任务恢复。DSH 侧同构开放:`POST /iris/api/core/task/:id/retry`(请求体必须 `confirmBilling:true`)与工作台「重试为新任务」按钮(重新输入指令 + 重复计费确认弹窗)。
177
+
178
+ 重试输入按旧 Task 的能力校验:图片为 `{prompt, size?, n?}`,视频为 `{prompt, size?, duration?, img_data_url?}`,TTS 为 `{text, voice?}`,转写为 `{audio_url}`。视频首帧和转写地址会转换为 Provider 所需字段;原任务中的媒体输入不会自动恢复。转写 retry 要求重新提供 HTTPS/OSS 地址,本地 `audio_path` 上传仍通过正常提交入口处理;模型覆盖使用外层 `--model-ref`,不放在 retry 的 `--input` 中。
179
+
180
+ CLI、DSH 正常生成与 retry 共用输入规范:Prompt/文本修剪后为 1–20000 字符,图片 `n` 为 1–4 的整数(缺省 1),视频时长为 1–60 的数字,规范化后的显式 size/voice 为 1–64 字符。转写地址修剪后最多 20000 字符。数字字符串会转为数字;非法生成参数在提交前拒绝,不创建新的计费 Task。CLI 和 retry 还会拒绝未知输入字段;DSH 保留自己的宿主参数,由入口提取生成参数,工作台清空可选文本框仍使用默认值。
181
+
182
+ ```bash
183
+ dsh-iris artifact inspect artifact_0123456789abcdef01234567 \
184
+ --data-root /absolute/path/to/iris-data
185
+
186
+ dsh-iris artifact list --data-root /absolute/path/to/iris-data
187
+
188
+ dsh-iris artifact export artifact_0123456789abcdef01234567 \
189
+ --data-root /absolute/path/to/iris-data \
190
+ --output /absolute/path/cropped.png
191
+
192
+ dsh-iris artifact rebuild --data-root /absolute/path/to/iris-data
193
+ ```
194
+
195
+ `task` 与 `artifact` 的 `inspect/list` 以 reader 打开数据根,不创建或修复文件。`run`、`export` 使用 writer;`rebuild` 是显式 recover 操作。同一数据根已有写者时返回 `IRIS_CORE_DATA_ROOT_BUSY`。导出默认不覆盖已有文件。
196
+
197
+ ## 当前边界
198
+
199
+ - Core 的 `run`、`media`、`task`、`artifact` 和 `runtime recover` 操作必须显式提供绝对 `--data-root`,避免误写日常 profile;配置查询只要求 `--provider-config`,Doctor 省略数据根时按上述默认范围检查。
200
+ - 该路径只使用 Node.js、`sharp` 和 Iris Core,不加载 DSH/Cordis,也不启动服务;只有显式 `run image`、`run video`、`run tts`、`run transcribe`、`task observe`、`task redeliver`、`task cancel` 和 `task retry` 会访问供应商。
201
+ - Artifact Manifest v0 已包含 SHA-256、关系边、可重建 Index 和进程崩溃窗口恢复;格式与限制见 [Artifact Manifest v0](ARTIFACT_MANIFEST.md)。
202
+ - 当前开放 `crop`、同步/异步图片提交、视频 t2v/i2v 提交、语音同步合成(`run tts`)、音频转写(`run transcribe`)、图片/视频/转写单步观察(CLI `task observe` 与工作台「重新观察」共用)、失败产物重新取回(CLI `task redeliver` 与工作台「重新取回作品」共用)、人工取消(CLI `task cancel` 与工作台「取消任务」共用,只有供应商明确确认才记为已取消)、重试为新任务(CLI `task retry` 与工作台「重试为新任务」共用,必须显式确认计费且重新提供 prompt/文本/音频地址)、Task 只读查询与 Artifact 管理;自动观察、其他媒体能力和其余 Command 会按相同事实语义逐项迁入。
203
+ - CLI 暂不提供 Core Task/Artifact 删除能力(`task`/`artifact` 的 `inspect/list` 均为纯 reader);DSH 工作台的删除、清空与孤儿清理也只处理 legacy `outputs/`,Core 作品同样只读。不要手动删除 `core-v0` 中的记录、Manifest 或对象;Core 删除与清理能力计划在 0.2.x 开放。
204
+
205
+ ## 源码仓库的包外验收
206
+
207
+ 在源码仓库运行以下开发验收命令,需要 Node/npm 与本机 ffmpeg/ffprobe:
208
+
209
+ ```bash
210
+ node scripts/verify-headless-package.mjs
211
+ # npm 缓存完整时也可让依赖安装离线执行:
212
+ node scripts/verify-headless-package.mjs --offline
213
+ ```
214
+
215
+ 脚本在仓库外的独立临时目录打包、安装 tarball,通过 npm 安装的 `dsh-iris` 入口验证 help、crop、diff、抽帧以及图片、视频、TTS、转写的 Task/Artifact 闭环。安装树不含 DSH/Cordis,不链接仓库依赖;Provider 请求全部由复制到包外的 fixture 接管,本地 Sharp/ffmpeg 则实际处理媒体。报告检查每次 observe 只有一次 poll、无重提,Artifact 导出字节与 Manifest SHA-256 一致,reader 和导出保持 Core 零写入且不请求 Provider。
216
+
217
+ 每次运行保留独立目录内的 tarball、安装日志、fixture 数据、导出产物和 `report.json`,终端输出验收目录和报告位置。该脚本属于源码开发工具,不随 npm 包发布;没有真实 Provider、DSH 重启或其他平台的验收结果时,不据此声明那些路径已通过。
@@ -68,19 +68,38 @@ Host Adapter 只负责把一个宿主的工具、路由、客户端、附件、
68
68
 
69
69
  `lib/host-runtime.js` 另记 Iris 自身成功完成的工具、Skill 和路由注册,以及客户端版本/Slot 握手;它不保存 `ctx`、service、会话、附件或密钥。
70
70
 
71
- ## 0.1.4 实现顺序
71
+ ## DSH 装载与版本变化
72
72
 
73
- 1. 以本契约和测试冻结名称、依赖方向与错误分类。
74
- 2. 提供 test-only Local Host fixture,先运行本地确定性动作、任务查询与确认流程。(已完成;不等同于 standalone Host。)
75
- 3. 从 `actions.js` 的真实消费者逐项替换 `ctx.get()`,不一次性搬迁全部目录。(已完成服务端消费者迁移;lint 禁止重新扩散。)
76
- 4. 将 DSH 工具、路由、附件、会话、Browser、模型和 Skill 映射集中进 DSH Adapter。(已完成;客户端 Slot 使用 client bundle 内的最小 adapter。)
77
- 5. Host Doctor 消费同一能力快照、成功注册账本与受限客户端握手,验证真实 DSH profile 的服务端与客户端装载。(已完成;见 [Host Doctor](HOST_DOCTOR.md)。)
73
+ Iris 继续作为普通 DSH/Cordis 插件装载:`cordis.patch.yml` 只把 npm 包入口 `lib/index.js` 挂入宿主。该入口可以依赖 Core,但 Core Runtime 必须能在 DSH/Cordis 完全不可解析时独立装载。拆 Core 不会要求用户在 DSH 外另起守护进程,也不会取消工作台、Agent 工具或 🫧 入口。
78
74
 
79
- Local Host fixture 不能假装拥有 Browser、会话或附件能力;需要什么就显式注入什么,缺失路径也必须纳入测试。
75
+ 兼容规则:
80
76
 
81
- ## 非目标
77
+ 1. DSH 原始 `ctx` 只允许存在于 `lib/index.js`、`lib/dsh-host-adapter.js` 和客户端 Slot 边界;任务、Provider、Artifact 与 Command 不感知其形状。
78
+ 2. 工具、Skill、路由、附件、Browser、模型和客户端 Slot 分别探测、分别注册、分别诊断。单个端口失败只能降级对应体验,不能改变 Core Task/Artifact 事实。
79
+ 3. 不把所有功能押在单个便利 RPC 或私有服务上。当前 Iris Web 数据通路使用宿主公开的 prefix route + 普通 `fetch`,不调用 `connection.rpc.handle()`;这只是当前适配实现,不是永久绑定承诺。
80
+ 4. DSH Doctor 必须区分 loader、服务端端口、路由、工具/Skill 账本、客户端 bundle 与 Slot 握手;“Core 可运行但 DSH 投影失败”必须成为可识别结果。
81
+ 5. 兼容范围只按真实版本/commit canary 声明,不使用宽泛 semver 猜测未来预览版。上游破坏时先修改 DSH Adapter 与 fixture,不迁移或重写 Core 数据。
82
+ 6. 若 DSH 原生吸收某项同类功能,不自动删除 Iris 能力。只有公开契约在语义、安全和生命周期上等价并有版本化测试时,Adapter 才可选择委托;否则继续使用 Iris Core 实现。
82
83
 
83
- - 0.1.4 不拆分 npm 多包,不发布第三方 Host SDK。
84
- - 不启动 standalone 服务,不复制 DSH 聊天界面。
85
- - 不实现 Artifact Manifest、Recipe/Flow 或完整 headless 命令集。
86
- - 不新增 Gemini、Fal、Replicate,也不为尚无消费者的能力预建端口。
84
+ ## 当前覆盖
85
+
86
+ - `lib/index.js` 负责 DSH 插件入口、生命周期和工具注册;
87
+ - `lib/dsh-host-adapter.js` 映射九类命名端口;
88
+ - Local Host fixture 覆盖 crop/diff、Task 查询与提醒操作,并验证缺失能力的错误;
89
+ - Host Doctor 对服务端注册账本、客户端版本和 Slot 握手分别给出结果;
90
+ - 已发布 Iris `0.1.4` 在 DSH `0.1.2-rc.1` 与 `0.1.5-rc.1` 上有历史宿主验证记录,不继承为当前候选的支持声明。
91
+ - Iris `0.2.0` 只声明 DSH `0.2.0-rc.2`,已通过真实安装服务隔离验收,保留附件完整引用、绑定明确图片模型路由并适配客户端主视图选择;边界与尚未完成的浏览器实机检查见 [rc.2 适配说明](DSH_RC2_ADAPTATION.md)。
92
+
93
+ Local Host 不模拟 Browser、会话或附件。测试需要这些能力时必须显式注入,缺失行为也属于契约。
94
+
95
+ ## Text/Vision 后续共享契约
96
+
97
+ [Text/Vision Model Port v0](MODEL_PORT_CONTRACT.md) 已实现独立纯契约、调用控制与 test-only Fake Port,不替换本契约已有的 `textModel.stream()` / `visionModel.analyze()` 方法。后续由 DSH 模型适配器把 Host 方法映射为共享 `complete()`:解析原始流和正常终态、传递取消、桥接同一张图片,并隔离 Host 引用与会话上下文。
98
+
99
+ 普通能力快照仍为零模型调用。显式视觉实测属于可能计费的模型操作,不能用于 `describe()` 或自动 Doctor;缺图片桥接时必须在模型调用前失败。新共享版本不代表 Host Adapter v0 已升级、公开 SDK 已冻结或现有消费者已迁移。
100
+
101
+ ## 范围
102
+
103
+ - Host Adapter v0 仍是内部接口,不是第三方 SDK;
104
+ - DSH 插件不启动 standalone 服务,也不复制聊天界面;
105
+ - Artifact Manifest、Recipe/Flow 和新 Provider 不属于本契约。
@@ -29,6 +29,8 @@ Host Doctor 是 Iris 在运行中的 DSH 实例内提供的兼容性诊断。它
29
29
 
30
30
  如果刚安装、热更新或强制刷新,先等 Web 页面完成装载,再刷新诊断。未收到客户端握手时,Host Doctor 只报告警告,不会把“服务端已装载”冒充成“UI 已装载”。
31
31
 
32
+ Iris `0.2.0` 仅支持 DSH `0.2.0-rc.2`,版本检查与 package 的 DSH engines 一致。旧 DSH `0.1.2` 线或 `0.1.5-rc.1` 返回版本错误,并提示使用历史稳定包 `@mokuyoaxis/dsh-iris@0.1.4`;旧版 Doctor 的支持范围不继承到当前版本。无 DSH 的 CLI/离线诊断不受此宿主限制。
33
+
32
34
  ## 运行时证据
33
35
 
34
36
  服务端只登记成功完成的注册:
@@ -0,0 +1,300 @@
1
+ # Text/Vision Model Port v0 契约设计
2
+
3
+ 状态:**2026-09-30,M1 纯契约、调用控制与 Fake Text/Vision Port 已实现;真实适配器及消费者尚未迁移。** 本文约定共享层与模型适配器的边界;现有 `textModel.stream()`、`visionModel.analyze()`、视觉后端与提示词优化器仍使用原实现。M1 不增加公开 export、CLI 命令、配置字段或生产依赖,也不改变 [Host Adapter v0](HOST_ADAPTER_CONTRACT.md) 的现有方法。
4
+
5
+ ## 目标与范围
6
+
7
+ 共享层负责一次文本或视觉调用的输入校验、预算、取消、完整结果与安全错误。DSH 与自持 Provider 只适配协议;提示词优化、看图、定位、OCR 和媒体摘要消费同一调用语义。
8
+
9
+ v0 只支持单轮、无工具的完整文本结果。视觉请求携带一张图片;多帧摘要先由业务层制作一张 contact sheet。会话历史、工具调用、音频、视频、实时流式 UI 与结构化输出协议留到后续单独设计。定位的 bbox JSON 和 OCR 的分块结果仍由对应业务模块解析,不成为通用模型端口的数据结构。
10
+
11
+ 这些操作不属于媒体生成 Task:端口不创建 Task/Attempt/Artifact,不占 Core writer 租约,不接入 Provider Task Runner。后续若需要保存文字结果,应另行通过显式 Command 设计产物交付。
12
+
13
+ ```mermaid
14
+ flowchart TD
15
+ DSH[DSH 工具 / 工作台] --> DB[DSH 边界:模型选择与附件解析]
16
+ LOCAL[后续独立入口 / 离线 fixture] --> LB[显式配置与图片字节]
17
+ DB --> OPS[共享业务:优化 / 看图 / 定位 / OCR / 摘要]
18
+ LB --> OPS
19
+ OPS --> INV[调用控制:预算 / 取消 / 完整结果 / 错误]
20
+ INV --> TEXT[TextModelPort v0]
21
+ INV --> VISION[VisionModelPort v0]
22
+ TEXT --> DA[DSH 模型适配器]
23
+ VISION --> DA
24
+ VISION --> PA[自持 Provider 模型适配器]
25
+ TEXT --> FA[Fake Model Port]
26
+ VISION --> FA
27
+ ```
28
+
29
+ 图中是目标结构。独立文本 Provider 与新 Headless 命令不在本轮范围;当前自持 Provider 目录尚无 `text` 能力。
30
+
31
+ ## 职责归属
32
+
33
+ | 层 | 拥有的职责 | 交给下一层的输入 |
34
+ |---|---|---|
35
+ | 入口 / Host 边界 | 读取配置、固定或会话/默认模型选择、解析附件与文件、预览和写回草稿 | 已绑定端口、文字、图片字节、预算、取消信号 |
36
+ | 共享业务 | 模板、目标类型、bbox 解析、OCR 分块、contact sheet、操作整体调用预算 | 单轮请求与单次调用预算 |
37
+ | 共享调用控制 | 通用校验、deadline、结果验证、安全错误、显式候选策略 | 已验证请求与合成信号 |
38
+ | 模型适配器 | 凭据和端点闭包、请求映射、底层流聚合、终态识别、资源清理 | 完整文本或稳定错误 |
39
+ | Host 投影 | 附件、工具输出、健康反馈、UI 文案 | 对用户有用的结果 |
40
+
41
+ 共享模块不得导入 DSH/Cordis、`config-store.js`、Host Runtime 或 Store,不读取环境配置,不取得原始 `ctx`。DSH 模型适配器只接收已探测的 Host Port;原始 DSH 对象仍封闭在已有 Host Adapter 与插件装配边界。
42
+
43
+ ## 端口与版本
44
+
45
+ 两种端口均提供同步、纯读取的 `describe()` 与异步 `complete(request, options)`。v0 不向消费者暴露原始事件流;适配器可以在内部使用流,并在收到正常终态后返回完整结果。
46
+
47
+ 以下 TypeScript 描述内部契约形状,不是已发布类型声明:
48
+
49
+ ```ts
50
+ type Support = 'supported' | 'unsupported' | 'unknown';
51
+
52
+ type ModelIdentity =
53
+ | { origin: 'provider'; backendId: string; providerId: string; modelId: string }
54
+ | { origin: 'host'; backendId: string; providerId?: string; modelId?: string };
55
+
56
+ type ModelDescriptor = {
57
+ contractVersion: 0;
58
+ kind: 'text' | 'vision';
59
+ identity: ModelIdentity;
60
+ availability: 'available' | 'unavailable' | 'incompatible';
61
+ reasonCode?: string;
62
+ features: {
63
+ system: Support;
64
+ temperature: Support;
65
+ maxOutputTokens: Support;
66
+ reasoning: Support;
67
+ };
68
+ reasoning?: { off: Support; effortIds?: string[] };
69
+ image?: { mediaTypes?: string[]; maxBytes?: number };
70
+ };
71
+
72
+ type Reasoning =
73
+ | { mode: 'provider-default' }
74
+ | { mode: 'off' }
75
+ | { mode: 'effort'; effortId: string };
76
+
77
+ type TextRequest = {
78
+ prompt: string;
79
+ system?: string;
80
+ generation?: {
81
+ temperature?: number;
82
+ maxOutputTokens?: number;
83
+ reasoning?: Reasoning;
84
+ };
85
+ };
86
+
87
+ type VisionRequest = TextRequest & {
88
+ image: { bytes: Uint8Array; mediaType: string };
89
+ };
90
+
91
+ type TextCallOptions = {
92
+ signal?: AbortSignal;
93
+ budget: {
94
+ timeoutMs: number;
95
+ maxInputTextBytes: number;
96
+ maxOutputChars: number;
97
+ };
98
+ };
99
+
100
+ type VisionCallOptions = {
101
+ signal?: AbortSignal;
102
+ budget: TextCallOptions['budget'] & { maxImageBytes: number };
103
+ };
104
+
105
+ type ModelCompletion = {
106
+ contractVersion: 0;
107
+ text: string;
108
+ finishReason: 'stop';
109
+ identity: ModelIdentity;
110
+ usage?: {
111
+ inputTokens?: number;
112
+ outputTokens?: number;
113
+ totalTokens?: number;
114
+ reasoningTokens?: number;
115
+ };
116
+ };
117
+
118
+ interface TextModelPort {
119
+ describe(): ModelDescriptor & { kind: 'text' };
120
+ complete(request: TextRequest, options: TextCallOptions): Promise<ModelCompletion>;
121
+ }
122
+
123
+ interface VisionModelPort {
124
+ describe(): ModelDescriptor & { kind: 'vision' };
125
+ complete(request: VisionRequest, options: VisionCallOptions): Promise<ModelCompletion>;
126
+ }
127
+ ```
128
+
129
+ 契约版本独立于 Host Adapter 版本。`lib/model-port-contract.js` 验证端口、请求、描述与结果;未知字段、错误类型或不兼容版本应明确拒绝。端口是只包含 `describe` / `complete` 的方法对象;DTO 拒绝额外字段、accessor 和非普通对象原型。能力列表只接受密集的自有数据元素,不读取元素 getter、不调用自定义 iterator。标识长度最多 256 code units,命名空间化 `backendId` 最多 512,组件需使用安全字符或百分号编码。描述与结果中的对象只包含安全 DTO;端口方法与 `AbortSignal` 不可序列化,也不进入任何记录。
130
+
131
+ `describe()` 只读取已绑定或缓存的事实,返回独立、可序列化的快照;不得联网、调用模型、保存附件、探测账号或写健康状态。未知能力保留 `unknown`,不能猜测为 supported 或 unsupported。`reasonCode` 是受控诊断码,不透传供应商异常原文。
132
+
133
+ ## 模型绑定与能力
134
+
135
+ 端口在一次业务操作开始时绑定后端。`backendId` 使用命名空间,例如 `iris-provider:<id>`、`dsh-text:<id>`、`dsh-vision:default`;DSH 的 provider/model ID 与 Iris 的 `providerId::modelId` 是不同命名空间,不能互相猜测转换。
136
+
137
+ 自持 Provider 必须有明确的 Provider 与模型身份。DSH 固定选择或可解析的当前选择应记录已知身份;全局视觉默认模型若无法解析,只声明 Host 后端,省略未知 `providerId`/`modelId`。不得用文本默认模型冒充视觉模型,也不得承诺宿主内部动态路由每次选到相同物理模型。
138
+
139
+ `availability=available` 表示本地依赖与协议形状可调用,不表示凭据有效、模型具备看图能力或远端一定成功。真实视觉实测仍是显式模型调用,可能产生费用;它使用同一预算与取消机制。普通 describe/Doctor 不做该实测。
140
+
141
+ `kind='text'|'vision'` 是共享端口的调用类型,不是新增持久 Provider 能力标签。当前目录只声明媒体生成、转写和视觉能力;不能从模型名或 `vision` 标签推导 `text`。首个文本消费者使用 DSH 或显式注入的 Fake Text Port。增加自持文本目录、配置或 CLI 选择能力应另行审查,不在迁移中顺带改写现有配置。
142
+
143
+ ## 输入与生成控制
144
+
145
+ - `prompt` 必须是非空字符串,`system` 若提供也必须非空;用 trim 判断空白,但端口不改写原文。业务层负责当前功能已有的修剪、JSON 数据包装与模板选择。
146
+ - 文本大小按 `prompt` 与 `system` 的 UTF-8 字节总和检查。`maxOutputChars` 按 JS 字符串长度(UTF-16 code unit)计算,与当前提示词优化器一致;不能按 token 数替代。
147
+ - 视觉输入必须是非空字节和规范化 MIME,不接受路径、URL、data URL、Artifact ID 或 DSH attachment/session 对象。入口解引用并解码;业务层制作的 OCR 分块或 contact sheet 也以字节传入。
148
+ - v0 图片 MIME 范围为 `image/png`、`image/jpeg`、`image/webp`、`image/gif`;适配器可以声明更小范围。声明限制与调用预算取更严格者。未知远端限制不伪造数值;格式不支持要返回稳定错误,不能丢掉图片继续纯文本调用。
149
+ - 字节在调用期间只读,消费者不得复用后修改;适配器若异步保留或转码,需持有安全副本。图片来源和宿主引用留在入口,不进入通用错误或结果。
150
+ - 预算必须显式传入;时间、文本、图片和输出上限均为正的有限安全整数。超限在模型调用前拒绝,输出超限在读取过程中中止;不能截取前若干字再返回成功。
151
+ - `temperature` 为有限非负数,具体范围由绑定协议校验;`maxOutputTokens` 为正安全整数。不支持的显式参数在调用前拒绝;未知能力不能静默丢弃参数,必须由已验证协议映射,否则返回 unsupported。
152
+ - `reasoning` 缺省等于 `provider-default`;`off` 必须有明确的关闭能力,`effort` 必须在已声明 effort 列表中。适配器不推测厂商字段,也不把思考正文作为结果文本。
153
+
154
+ 提示词优化的 `off-if-supported` 与 `inherit` 是业务策略,不成为通用请求字段:前者在已知支持关闭时映射为 `off`,否则 `provider-default`;后者只继承入口明确选择的 effort。当前 `off/none/disabled/disable/no-thinking` 等供应商元数据只在 DSH 适配边界归一化。模型元数据解析使用操作信号与总 deadline,不在 `describe()` 中触发网络查询。
155
+
156
+ 迁移保留已有业务预算:提示词草稿 32 KiB、输出 16,000 code units、默认 45 秒与 1,200 输出 token,原配置范围不变。草稿上限仍在业务层校验;端口的总文本上限需按草稿上限、JSON 转义的最坏增长与已验证模板上限计算,不能把带模板的整个请求限制为 32 KiB,导致原本合法的草稿被拒绝。
157
+
158
+ 视觉单次调用沿用 120 秒默认档,Host 原 6,000 字限制改为超限失败。其他入口需明确其文字/图片上限,不能直接套用生成任务的图片参数校验。v0 不为这些预算增加持久配置格式。显式 effort 在元数据未知时从直接透传变为调用前拒绝,是待实施的契约收紧;M2 必须覆盖配置与 inherit 路径,不能声称行为完全未变。
159
+
160
+ ## 完整结果与底层流
161
+
162
+ 成功只返回 `finishReason='stop'` 且 trim 后非空的完整正文。端口保留正文原样,业务层决定展示或解析时是否 trim;结果身份来自本次真实绑定或明确的 Host 不透明后端。
163
+
164
+ 正文按本次请求返回调用方,可能复述用户输入,例如优化后的提示词或 OCR 文字;这是显式结果,不是诊断字段。共享层不自动记录输入/正文,也不把它们复制到能力快照、错误、健康元数据或 Core 记录。
165
+
166
+ 适配器必须处理底层完整性:
167
+
168
+ 1. 正常结束原因需有已验证语义。SSE `[DONE]`、EOF、退出迭代或已经读到文字均不能单独证明正常完成;非流式协议需确认完整响应与终态。
169
+ 2. DSH `text-delta` 聚合为正文;`block-end` 的完整文本替换同一块的累积值,不能再追加导致重复。旧 `delta` 只有在同一协议存在可验证终态时才兼容。
170
+ 3. `length/max-tokens`、本地输出上限、工具调用、内容阻断、未知终态、解析失败、流中断和流错误均拒绝成功,即使已读到部分正文。不得把 partial text 伪装成完成结果。
171
+ 4. 不返回原始块、事件、thinking、工具参数、HTTP headers/body 或 Host live object。底层 cleanup 后才能完成正常调用;取消路径按下一节处理。
172
+ 5. usage 只填供应商实际提供且语义已知的非负安全整数;未知字段省略,不能补零、推算 token 或把思考 token 重复加到总数。
173
+
174
+ 现有 `adapters.visionStream()` 与 Host `visionModel.analyze()` 只返回字符串,不足以证明正常结束;实现时必须增强协议读取,不能仅套一层 wrapper 就宣称符合 v0。缺少终态的既有 SSE fixture 也需要改为真实协议形状。
175
+
176
+ ## 取消、超时与调用次数
177
+
178
+ 一次 `complete()` 最多调用一次上游模型生成入口,不自动重试或切换后端。DSH 内部的调度/重试事实未知时应如实说明;“调用 Host 一次”不等于已证明远端绝对只执行一次。
179
+
180
+ 调用前先检查取消;已取消时零图片准备、零模型调用。调用期间的合成信号覆盖图片桥接、HTTP/Host 请求与流读取,deadline 从调用开始计时。成功返回前再次检查信号;已经取消或超时的晚到结果不能覆盖错误。
181
+
182
+ 取消时向底层传递 abort,结束流迭代、释放 reader,移除监听器并清理 timer。超时也必须触发 abort;不能只用 `Promise.race` 返回超时而放任底层继续。本地应在 deadline 后有界退出;若第三方不响应信号,则丢弃晚到结果、处理晚到拒绝并报告本地取消,不宣称远端执行或计费已经停止。没有可验证信号传递/流清理路径的适配器不能标为符合 v0。
183
+
184
+ 业务操作另有整体预算 `{ timeoutMs, maxInvocations }`,两者均为正的有限安全整数;该预算属于共享调用控制,不作为额外字段传给模型端口。整体 deadline 覆盖路由解析、图片处理、OCR 所有块及候选切换。每次调用只能消费剩余时间与次数,不能为每一块重置总预算。取消/超时立即停止整次操作;OCR 不得把它记录成普通失败块后继续请求后续图片。
185
+
186
+ 看图或提示词优化等单次业务默认 `maxInvocations=1`;OCR 应按已规划的块数设置有限基础预算,若允许候选切换,再显式给出总调用上限与整体 deadline,不沿用单次调用的默认次数。保留视觉有序候选时,由共享调用策略显式给出候选、总次数和允许的错误条件:
187
+
188
+ | 上次结果 | 可否进入下一候选 |
189
+ |---|---|
190
+ | 本地校验错误、取消、超时 | 停止;不能通过换模型掩盖请求问题或取消 |
191
+ | 能力缺失/不兼容/不支持且 `not_invoked` | 显式策略可跳过,无模型调用 |
192
+ | 明确认证、限额或速率拒绝且 `rejected` | 显式策略可继续;仍计入调用次数 |
193
+ | 正常结束但正文为空且 `responded` | 仅策略明确允许时继续;前次可能已计费 |
194
+ | 已读部分正文、截断、工具调用、内容阻断、协议错误或调用事实未知 | 停止,不自动重提 |
195
+
196
+ `maxInvocations` 统计实际进入上游生成入口的次数;跳过不可用候选不消耗次数,但候选表必须有限。适配器没有自己的候选列表。此处是文本/视觉调用策略,不借用媒体 Task 的 `accepted/not_accepted`,也不把错误分类当作免费调用证明。
197
+
198
+ 整体调用次数用尽后不得进入下一次生成,返回 `IRIS_MODEL_CALL_LIMIT`;OCR 可以在业务层将此前成功块投影为明确的 partial 结果,但不能宣称全部完成。取消或超时仍立即终止操作,不通过 partial 结果隐藏。
199
+
200
+ ## 错误契约
201
+
202
+ 错误使用 `ModelPortError`,只暴露受控字段:
203
+
204
+ ```ts
205
+ type ModelPortErrorRecord = {
206
+ code: string;
207
+ message: string; // 固定或经过白名单映射的安全说明
208
+ stage: 'validate' | 'prepare' | 'invoke' | 'read' | 'normalize';
209
+ invocation: 'not_invoked' | 'rejected' | 'responded' | 'unknown';
210
+ backendId?: string;
211
+ status?: number; // 已验证的 HTTP 状态码;未知时省略
212
+ };
213
+ ```
214
+
215
+ | code | 含义 |
216
+ |---|---|
217
+ | `IRIS_MODEL_INPUT_INVALID` | 字段、类型、预算或输入大小非法 |
218
+ | `IRIS_MODEL_UNAVAILABLE` | 未配置或缺失依赖/Host 图片桥接能力 |
219
+ | `IRIS_MODEL_INCOMPATIBLE` | 端口版本或底层协议形状不兼容 |
220
+ | `IRIS_MODEL_UNSUPPORTED` | 当前模型/协议不支持显式输入或生成参数 |
221
+ | `IRIS_MODEL_AUTH_FAILED` | 明确认证或权限拒绝 |
222
+ | `IRIS_MODEL_RATE_LIMITED` | 明确速率或额度限制 |
223
+ | `IRIS_MODEL_REQUEST_FAILED` | 其他请求失败;网络异常不推定未调用 |
224
+ | `IRIS_MODEL_PROTOCOL_INVALID` | 无法解释的响应/事件/终态 |
225
+ | `IRIS_MODEL_INCOMPLETE` | 流结束但缺少可验证的正常终态 |
226
+ | `IRIS_MODEL_OUTPUT_LIMIT` | 本地输出超限或远端 token 截断 |
227
+ | `IRIS_MODEL_CALL_LIMIT` | 操作整体生成调用次数已用尽 |
228
+ | `IRIS_MODEL_EMPTY_RESULT` | 正常结束但正文为空 |
229
+ | `IRIS_MODEL_UNEXPECTED_TOOL` | 收到工具调用或工具结束原因 |
230
+ | `IRIS_MODEL_CONTENT_BLOCKED` | 明确内容阻断/拒答终态 |
231
+ | `IRIS_MODEL_ABORTED` | 调用方或上层操作取消 |
232
+ | `IRIS_MODEL_TIMEOUT` | 单次或整体 deadline 到期 |
233
+
234
+ `invocation` 只描述本地可确认的调用事实:未进入生成入口为 `not_invoked`;上游明确拒绝为 `rejected`;收到有效生成内容/终态为 `responded`;进入入口但无法确认结果为 `unknown`。网络失败、在途取消和超时不能回写为 `not_invoked`。收到部分正文后中断仍为 `responded`,但调用失败。它不是账单事实或持久 Task 状态。
235
+
236
+ 不提供通用 `retryable` 布尔值。是否切换候选由上述策略同时检查 code 与 invocation;同一个错误码可发生于不同调用阶段。
237
+
238
+ 安全错误不包含 Prompt、图片、凭据、端点、签名 URL、会话/附件标识、绝对路径或原始异常 cause。上层可给出预算数值等安全上下文,但不能直接拼接供应商错误原文。`ModelPortError` 不复用依赖 Task 受理/持久化语义的 Provider Error Record。
239
+
240
+ ## DSH 图片与结果桥接
241
+
242
+ 当前 Host 视觉实现只消费 attachment 引用,忽略 `imageDataUrl`。新适配器必须保证模型实际收到共享请求里的同一张图:
243
+
244
+ 1. 已验证 DSH 支持内联图片时,在适配边界转换字节;
245
+ 2. 否则使用 Attachments Port 保存相同字节,取得 Host 引用,再调用 Host 模型;该步骤受同一信号、deadline 与图片预算约束;
246
+ 3. 若两者都不可用,调用模型前返回 `IRIS_MODEL_UNAVAILABLE`,禁止退化成只有问题的纯文本请求。
247
+
248
+ Host 引用、session ID 与 source metadata 可存在于 DSH 适配器闭包,但不能进入共享请求、结果或诊断快照。桥接不得创建 Core Artifact;宿主是否持久保存附件需在实现与 canary 中明确记录,不能把这条路径称为完全零副作用。
249
+
250
+ 提示词优化仍由 DSH 边界读取 v1 配置、解析固定/会话/默认模型,返回选择来源供 UI 展示。共享核心只接收选定端口与模板;保留原草稿、预览后写回和不携带会话历史的行为。Provider 健康反馈由入口消费安全结果/错误,模型端口不导入健康 Store。
251
+
252
+ ## 实现切片与退出条件
253
+
254
+ M1 已完成。以下其余切片仍为实施建议,不要求一次跨模块改造;提示词优化的功能定位与输入隔离继续讨论,M2 未获本轮实施授权。
255
+
256
+ | 切片 | 范围 | 退出条件 |
257
+ |---|---|---|
258
+ | M1:纯契约与调用控制(已实现) | `model-port-contract.js`、`model-invoker.js`、test-only Fake Text/Vision Port | 无 Host/配置/Store 依赖;请求、错误、预算与取消 conformance 通过 |
259
+ | M2:首个文本消费者 | DSH 模型适配器与纯 `prompt-optimizer-core.js`;原优化入口保留配置/选择/投影 | 真实 DSH 流 fixture 与 Fake Port 结果一致;reasoning、预览/写回、取消、零 Task 写入回归通过 |
260
+ | M3:视觉单次调用 | 自持协议适配器、DSH 图片桥接、look/relook | 真正检查终态;同图字节进入两后端;取消后零下一候选;Host 可选降级如实诊断 |
261
+ | M4:复合视觉业务 | locate、OCR、contact-sheet summarize | bbox 与分块语义保持;整体预算有效;取消停止后续块;摘要仍默认一张拼图一次调用 |
262
+ | M5:独立入口 | 再决定文本目录、显式 Headless 选择和产物交付 | 先审查配置/公开面;无 DSH tarball 消费者与真实 Provider 单独举证 |
263
+
264
+ `model-http-adapter.js` 和 `dsh-model-adapter.js` 可作为协议映射的实现位置,最终命名由实施切片决定。现有 `vision.js` 的有序候选可以渐进接入共享调用策略;共享代码不反向导入旧 Host 实现。媒体摘要的 Core 抽帧接线与 S2V/Browser 迁移另行处理。
265
+
266
+ ## M1 内部调用方式
267
+
268
+ - `modelPortSnapshot()` 取得严格描述;`normalizeModelCall()` / `normalizeModelCompletion()` 校验输入和完整结果;`modelErrorRecord()` 只输出受控字段,不信任普通 Error 的原始 code、message 或 status。
269
+ - `invokeModel(port, request, options)` 用于单次调用,自动释放 deadline 和监听器,没有候选切换。
270
+ - `createModelOperation({ signal?, budget: { timeoutMs, maxInvocations? } })` 用于有总预算的复合操作;次数缺省 1。`operation.signal` 可覆盖入口解析与图片准备;`invoke()` 共用总额度,取消或任一调用超时后整个操作停止。
271
+ - `operation.runCandidates(ports, request, options, policy)` 只消费有限的端口数组;`skipUnavailable`、`allowRejected`、`allowEmptyResult` 均缺省 false。策略只允许前文矩阵中的错误组合,不能通过放宽策略重试未知结果、截断、工具调用或取消。
272
+ - 复合操作在 `finally` 中调用 `operation.dispose()`;`snapshot()` 只返回次数、在途数和本地生命周期,不包含请求或结果。并发调用先预留次数,只有可信契约错误明确 `not_invoked` 才返还,普通 Error 不能伪造未调用。
273
+ - `createModelTextCollector()` 是协议无关的正文辅助工具,支持 append/replace、读取时输出上限和正常终态;`fail(error)` 在流错误后保留已收到正文的事实,错误进入终态后不再接受新的 stop。具体适配器先映射自身事件;该辅助工具不理解 DSH 或 SSE 协议,也不提供公共流接口。
274
+
275
+ `tests/fixtures/fake-model-port.mjs` 可复现正常正文、规范化流、准备失败、在途错误、等待取消与迟到结果;`model-conformance.mjs` 的同一组 17 项检查分别消费 Text/Vision。fixture 和输入记录只在测试进程存在,不进入 npm 包。
276
+
277
+ 所有共享调用通过调用控制执行;协议适配器负责将 abort 传递到真实源及清理资源。当前 M1 的完整性与取消证据来自 Fake Port,不代表现有 HTTP/DSH 协议已经符合新契约。
278
+
279
+ 现有配置版本、公开接口和全部消费者不应在一个提交中同时切换。M1 不提供对提示词内容的语义过滤或注入防护证明;优化系统的主动模板/约束加入和不可信输入隔离,需在后续业务设计中分别验证。
280
+
281
+ ## 必须补齐的 conformance
282
+
283
+ M1 已执行纯契约、调用控制与 Fake Port 路径。后续协议/业务测试仍需要执行真实适配/消费路径,不能仅比较 DTO 或检查源码字符串。
284
+
285
+ | 场景 | 必须证明的结果 |
286
+ |---|---|
287
+ | describe、能力快照与 Doctor | 零模型/网络调用、零配置/Task/Artifact 写入;未知能力不猜测 |
288
+ | 非法输入、格式、预算、reasoning | 在生成入口前拒绝;显式参数不静默丢弃 |
289
+ | 预先取消 | 零附件桥接、零生成调用 |
290
+ | 在途取消、超时与晚到结果 | 底层收到 abort,资源清理,本地有界退出,无下一候选/下一 OCR 块 |
291
+ | delta 与 block-end | 不重复文字;只有可验证正常终态才返回完整正文 |
292
+ | EOF、`[DONE]` 但无终态、坏 JSON、未知终态 | 稳定失败,部分正文不返回为成功 |
293
+ | 空结果、token 截断、输出超限、工具调用、内容阻断 | 独立错误;切换行为符合显式策略与总次数 |
294
+ | OCR 与候选组合 | 有限总调用数与整体 deadline;普通块失败可形成明确 partial 业务结果,取消/超时终止操作 |
295
+ | DSH 图片桥接 | 保存/内联的字节与输入相同;缺桥接时零模型调用,无引用泄漏 |
296
+ | 模型身份与 usage | DSH 与 Iris 命名空间不混用;未知默认身份和 usage 字段省略 |
297
+ | 错误、快照与自动记录 | 不包含输入、Key、端点、路径或 Host 对象;正文只作为显式结果返回,无隐式 Task/Artifact 写入 |
298
+ | 两类适配器与业务入口 | Fake Port、HTTP 协议 fixture、DSH 流 fixture 消费同一组结果/错误规则 |
299
+
300
+ 当前 `tests/vision-backend.mjs` 明确允许预先取消后继续 Host fallback,当前 OCR 捕获取消后继续分块,视觉实测超时也只是 race。它们是待修行为,未来切换时应更新测试并留下先失败后修复的证据。M1/Fake Port 通过不等于这些消费者门禁已通过。