@mokuyoaxis/dsh-iris 0.1.3 → 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 (91) hide show
  1. package/.dsh/skills/iris-compose-media/SKILL.md +23 -49
  2. package/.dsh/skills/iris-compose-media/references/task-v2-recovery.md +30 -0
  3. package/.dsh/skills/iris-compose-media/references/workflows.md +38 -0
  4. package/.dsh/skills/iris-verify-ui/SKILL.md +3 -25
  5. package/.dsh/skills/iris-verify-ui/agents/openai.yaml +4 -0
  6. package/.dsh/skills/iris-verify-ui/references/verdict-and-degradation.md +31 -0
  7. package/CHANGELOG.md +77 -2
  8. package/README.en.md +98 -19
  9. package/README.md +98 -19
  10. package/bin/dsh-iris.js +392 -13
  11. package/docs/AGENT_QUICKSTART.md +35 -0
  12. package/docs/ARCHITECTURE.md +82 -52
  13. package/docs/ARTIFACT_LIBRARY.md +24 -0
  14. package/docs/ARTIFACT_MANIFEST.md +60 -0
  15. package/docs/CORE_RUNTIME_CONTRACT.md +83 -0
  16. package/docs/DSH_CORE_MIGRATION.md +106 -0
  17. package/docs/DSH_RC2_ADAPTATION.md +36 -0
  18. package/docs/HEADLESS_CLI.md +217 -0
  19. package/docs/HOST_ADAPTER_CONTRACT.md +105 -0
  20. package/docs/HOST_DOCTOR.md +49 -0
  21. package/docs/MODEL_PORT_CONTRACT.md +300 -0
  22. package/docs/PROVIDER_ADAPTER_CONTRACT.md +138 -0
  23. package/docs/PROVIDER_HEALTH.md +39 -0
  24. package/docs/PROVIDER_RUNTIME_HARNESS.md +33 -0
  25. package/docs/PROVIDER_SUBMISSION_CONTRACT.md +2 -2
  26. package/docs/ROADMAP.md +42 -76
  27. package/docs/SECURITY.md +10 -5
  28. package/docs/TASK_SEMANTICS.md +3 -2
  29. package/docs/assets/diagrams/iris-architecture.png +0 -0
  30. package/docs/assets/diagrams/iris-architecture.svg +5 -5
  31. package/docs/assets/logo/README.md +55 -0
  32. package/docs/assets/logo/iris-bubble-concept.svg +67 -0
  33. package/docs/assets/logo/iris-bubble.svg +14 -0
  34. package/docs/assets/logo/iris-interim-flower.png +0 -0
  35. package/docs/assets/logo/iris-logo-mono-inverse.svg +78 -0
  36. package/docs/assets/logo/iris-logo-mono.svg +78 -0
  37. package/docs/assets/logo/iris-logo.svg +78 -0
  38. package/docs/assets/logo/iris-mark-mono-inverse.svg +42 -0
  39. package/docs/assets/logo/iris-mark-mono.svg +42 -0
  40. package/docs/assets/logo/iris-mark.svg +42 -0
  41. package/docs/releases/0.1.4.md +59 -0
  42. package/docs/releases/0.2.0.md +48 -0
  43. package/lib/actions.js +673 -525
  44. package/lib/adapters.js +58 -21
  45. package/lib/api.js +182 -16
  46. package/lib/artifacts.js +239 -0
  47. package/lib/bundled-skills.js +5 -5
  48. package/lib/client.js +1116 -86
  49. package/lib/command-service.js +420 -0
  50. package/lib/config.js +245 -49
  51. package/lib/core-artifact-store.js +598 -0
  52. package/lib/core-artifacts.js +4 -0
  53. package/lib/core-attention.js +103 -0
  54. package/lib/core-contract.js +105 -0
  55. package/lib/core-doctor.js +41 -0
  56. package/lib/core-lease-recovery.js +170 -0
  57. package/lib/core-runtime.js +232 -0
  58. package/lib/core-tasks.js +560 -0
  59. package/lib/core-user-projection.js +192 -0
  60. package/lib/doctor.js +260 -30
  61. package/lib/dsh-core-adapter.js +599 -0
  62. package/lib/dsh-host-adapter.js +240 -0
  63. package/lib/generation-input.js +95 -0
  64. package/lib/guard.js +3 -3
  65. package/lib/host-contract.js +131 -0
  66. package/lib/host-runtime.js +156 -0
  67. package/lib/index.js +255 -166
  68. package/lib/locate.js +1 -1
  69. package/lib/media.js +16 -6
  70. package/lib/model-invoker.js +218 -0
  71. package/lib/model-port-contract.js +333 -0
  72. package/lib/models.js +97 -11
  73. package/lib/prompt-optimizer.js +10 -20
  74. package/lib/provider-adapter.js +397 -0
  75. package/lib/provider-adapters.js +342 -0
  76. package/lib/provider-catalog.js +171 -0
  77. package/lib/provider-contract.js +72 -3
  78. package/lib/provider-health.js +59 -0
  79. package/lib/provider-protocol.js +51 -2
  80. package/lib/provider-task-runner.js +328 -0
  81. package/lib/render.js +43 -12
  82. package/lib/tasks.js +45 -9
  83. package/lib/vision.js +51 -56
  84. package/package.json +20 -14
  85. package/user_guide.md +67 -12
  86. package/docs/assets/diagrams/iris-architecture.drawio +0 -1
  87. package/docs/assets/diagrams/iris-task-lifecycle.drawio +0 -1
  88. package/docs/assets/diagrams/iris-workflow-compose.drawio +0 -1
  89. package/docs/releases/0.1.1.md +0 -51
  90. package/docs/releases/0.1.2.md +0 -26
  91. package/docs/releases/0.1.3.md +0 -30
@@ -0,0 +1,105 @@
1
+ # Host Adapter v0 契约
2
+
3
+ 状态:**v0.1.4 内部契约与渐进迁移已完成,并通过真实 DSH 隔离 canary。** 本文固定真实消费者所需的最小边界;不宣称已经提供独立 Core、公开 Host SDK 或完整 Local Host。
4
+
5
+ ## 目标
6
+
7
+ Host Adapter 只负责把一个宿主的工具、路由、客户端、附件、会话、浏览器和模型能力映射为 Iris 端口。Command 接收 Iris 输入、`AbortSignal` 和所声明的 Host Port,**不得接收原始 `ctx`**。Task、Provider、文件持久化和成本语义不属于宿主。
8
+
9
+ 契约版本固定为 `0`。它在 0.1.x 内仍是内部接口;只有经过 DSH 与 Local Host 两个真实消费者验证后,才考虑成为公开 SDK。
10
+
11
+ ## 端口清单
12
+
13
+ | Host Port | 当前 DSH 来源 | 最小职责 | 缺失时 |
14
+ |---|---|---|---|
15
+ | `tools` | `ctx.tools` / `ctx.effect` | 注册 Agent 工具并随插件生命周期释放 | Agent 工具不可用;核心动作不应因此失效 |
16
+ | `routes` | `webServer` / `httpServer` | 挂载 API、媒体与渲染路由并可逆卸载 | 工作台 HTTP 与媒体链接不可用 |
17
+ | `clientSlots` | Web Client `ctx.slots` | 注册设置、输入区、dock 与 overlay 座位 | 服务端任务和 CLI 不受影响 |
18
+ | `skills` | `ctx.skills` | 注册随包 Skill,声明用户/模型调用策略 | 工具仍可直接调用;Doctor 给出降级原因 |
19
+ | `attachments` | `ctx.get('attachments')` | 保存/读取图片字节,返回 Iris 自有引用 DTO | DSH 图片投影或会话图片读取不可用 |
20
+ | `sessions` | `ctx.get('sessionQuery')` / Client sessions | 列出当前会话的受支持附件和最小模型选择信息 | 只使用 Iris 自有产物或显式上传 |
21
+ | `browser` | `ctx.get('browser')` | 将受限 HTML 渲染为图片;不向 Command 暴露 DSH page/session 对象 | HTML 截图不可用 |
22
+ | `textModel` | DSH LLM/default-model 服务 | 执行无工具的文本生成并解析当前/默认模型 | 对话框提示词优化不可用 |
23
+ | `visionModel` | DSH 全局视觉模型 | 作为 Iris 自持视觉 Provider 之外的可选后端 | 有自持视觉配置时降级;否则视觉动作失败 |
24
+
25
+ `ports` 只包含真实可调用的方法对象。缺失或版本不兼容的能力进入 `unavailable`,并提供 `kind` 与人类可读 `reason`;Doctor 与运行错误必须消费同一份事实。
26
+
27
+ ## Command × Host Port 矩阵
28
+
29
+ | Command/入口 | 必需端口 | 可选端口 | 说明 |
30
+ |---|---|---|---|
31
+ | `image` | 无 | `visionModel` | 生成与 Task 不依赖宿主;成功后的简短视觉描述可以降级 |
32
+ | `video`、`tts`、`transcribe` | 无 | 无 | Provider、Task 与产物落盘属于 Iris Core 候选 |
33
+ | `look`、`locate`、`ocr` | 无 | `visionModel` | 优先使用 Iris Provider;没有任何视觉后端才失败 |
34
+ | `crop`、`diff`、`video_frames` | 无 | 无 | 确定性本地动作 |
35
+ | `media_summarize` | 无 | `visionModel` | 帧提取是本地动作,理解阶段允许宿主视觉降级 |
36
+ | `html` | `browser` | 无 | Command 只调用语义级 `renderHtml` |
37
+ | `relook` | 条件必需 `attachments` + `sessions` | 无 | Iris 自有附件可直接解析;会话附件必须走宿主端口 |
38
+ | `attachments_list`、`attachment_export` | 条件必需 `attachments` + `sessions` | 无 | Iris 自有产物不依赖宿主;会话来源不得读取宿主私有文件 |
39
+ | Task 查询、观察、交付、提醒和知情重试 | 无 | 无 | 事实、费用确认和新旧 Task 关系归 Iris 所有 |
40
+ | Provider/模型/能力配置 | 无 | `visionModel` | 普通发现与配置不依赖宿主;视觉实测可选宿主后端且必须显式触发 |
41
+ | Prompt Optimize | `textModel` | `sessions` | 客户端可显式传模型;否则从宿主获取当前/默认选择 |
42
+ | DSH 工具结果投影 | `tools` | `attachments` | 注册与结果转为 DSH attachment 属于 DSH Adapter,不进入 Command |
43
+ | Workbench/API/媒体路由 | `routes` | `clientSlots` | HTTP/UI 只是投影,不是真相来源 |
44
+ | 随包 Skill | `skills` | 无 | Skill 不得绕过 Command、Task 或费用边界 |
45
+
46
+ ## 不可违反规则
47
+
48
+ 1. Core 候选、Command、Task 和 Provider 模块不得导入 DSH/Cordis 运行时,也不得通过任意字段重新取得原始 `ctx`。
49
+ 2. Host Adapter 只能映射宿主能力;不得复制业务校验、Provider 路由、Task 状态机、failover 或产物持久化。
50
+ 3. 宿主会话 ID 只能作为来源上下文,不能成为 Iris Task、Attempt 或未来 Artifact 的主键。
51
+ 4. Command 输入和结果只能包含 JSON、字节、稳定引用与受控错误;不得持久化 page、session、service 等 live Host 对象。
52
+ 5. 能力探测必须显式、可序列化且零计费。缺能力返回稳定分类和可执行说明,不得用异常吞噬后继续猜测私有 API。
53
+ 6. 调用方的 `AbortSignal` 必须传播到 Host 操作;断开、取消和超时不得被 Adapter 转换为自动重试。
54
+ 7. 工具、路由、Skill 与 UI 注册必须可逆,并随对应宿主生命周期释放。
55
+ 8. 可选端口只能执行文档规定的降级;必需端口缺失必须立即失败,不得伪造成功结果。
56
+ 9. Host capability check、Doctor 和 Local Host fixture **不得自动产生新的远端媒体提交**,也不得把读取供应商密钥作为诊断输出。
57
+ 10. DSH 版本差异只能存在于 DSH Adapter/capability detection 和兼容 fixture;不得扩散到 Task、Provider 或通用 Command。
58
+
59
+ ## 错误与能力快照
60
+
61
+ `lib/host-contract.js` 提供:
62
+
63
+ - `defineHostAdapter()`:验证并冻结适配器描述,拒绝未知端口和原始 `ctx` 字段;
64
+ - `requireHostPort()`:缺失时抛出 `HostCapabilityError`;
65
+ - `IRIS_HOST_CAPABILITY_UNAVAILABLE`:宿主未提供能力;
66
+ - `IRIS_HOST_CAPABILITY_INCOMPATIBLE`:宿主存在相关服务但版本/形状不兼容;
67
+ - `hostCapabilitySnapshot()`:只输出宿主身份、版本和每项能力状态,不输出 live object。
68
+
69
+ `lib/host-runtime.js` 另记 Iris 自身成功完成的工具、Skill 和路由注册,以及客户端版本/Slot 握手;它不保存 `ctx`、service、会话、附件或密钥。
70
+
71
+ ## DSH 装载与版本变化
72
+
73
+ Iris 继续作为普通 DSH/Cordis 插件装载:`cordis.patch.yml` 只把 npm 包入口 `lib/index.js` 挂入宿主。该入口可以依赖 Core,但 Core Runtime 必须能在 DSH/Cordis 完全不可解析时独立装载。拆 Core 不会要求用户在 DSH 外另起守护进程,也不会取消工作台、Agent 工具或 🫧 入口。
74
+
75
+ 兼容规则:
76
+
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 实现。
83
+
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 不属于本契约。
@@ -0,0 +1,49 @@
1
+ # Host Doctor
2
+
3
+ Host Doctor 是 Iris 在运行中的 DSH 实例内提供的兼容性诊断。它与 `dsh-iris doctor` 共用 schema、检查状态和 0/1/2 严重度,但观察范围不同:
4
+
5
+ | 模式 | 入口 | 观察范围 |
6
+ |---|---|---|
7
+ | `offline` | `dsh-iris doctor [--json]` | Node、依赖、私有存储、配置、Task 与产物;不要求 DSH |
8
+ | `host` | 已认证 DSH 中的 `GET /iris/api/doctor` | DSH 版本和 Host Port、插件装载、14 个工具、2 项 Skill、4 组路由、Browser/附件/模型能力、客户端版本与 4 个 UI Slot |
9
+
10
+ 两种模式都默认零网络、零计费。Host Doctor 不调用任何 Host Port,不打开 Browser,不读取附件或会话,不调用文本/视觉模型,也不向 Provider 发送请求。它只读取:
11
+
12
+ 1. DSH Host Adapter 的可序列化能力快照;
13
+ 2. Iris 服务端本进程内记录的成功注册事实;
14
+ 3. DSH Web 客户端在 Slot 回调实际执行后发送的受限同源握手。
15
+
16
+ ## 使用
17
+
18
+ 在 Iris 工作台的“宿主诊断”卡片中可以直接查看、刷新并展开全部检查。需要原始 JSON 时,先通过 DSH 输出的认证 URL 打开 Web 页面,再在同一实例访问:
19
+
20
+ ```text
21
+ /iris/api/doctor
22
+ ```
23
+
24
+ 这是工作台路由下的 JSON 端点,继承 Iris 路由的 Host/认证边界。HTTP 200 表示诊断成功生成,不代表所有检查健康;应读取响应中的 `exitCode`:
25
+
26
+ - `0`:当前可观察项全部健康;
27
+ - `1`:存在可降级警告,例如页面尚未打开、可选 Browser 缺失;
28
+ - `2`:存在硬错误,例如工具/路由缺失、DSH 超出已验证范围、客户端和服务端版本不一致。
29
+
30
+ 如果刚安装、热更新或强制刷新,先等 Web 页面完成装载,再刷新诊断。未收到客户端握手时,Host Doctor 只报告警告,不会把“服务端已装载”冒充成“UI 已装载”。
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
+
34
+ ## 运行时证据
35
+
36
+ 服务端只登记成功完成的注册:
37
+
38
+ - 插件身份与 npm 版本;
39
+ - 14 个 Iris Agent 工具名;
40
+ - `iris-verify-ui`、`iris-compose-media`;
41
+ - `/iris/media`、`/iris/api`、`/iris/api/actions`、`/iris/render` 四组逻辑路由。
42
+
43
+ 客户端通过 `POST /iris/api/host-client` 回报插件 ID、静态客户端版本、协议版本和已执行的 Slot 名单。接口拒绝额外字段、未知 Slot、错误插件身份、空版本和无效协议号;同一前端版本的并发报告只做 Slot 单调并集,避免乱序请求把完整证据覆盖成部分证据。报告只保存在内存,不参与认证、任务执行或能力授权,也不接收 API Key、草稿、会话、附件或 Provider 数据。
44
+
45
+ ## 能力与降级
46
+
47
+ Host Adapter 将每项端口标记为 `available`、`unavailable` 或 `incompatible`,并给出同一份人类可读原因。工具注册与 Web 路由是 DSH 插件形态的硬要求;Skill、Browser、附件、会话以及宿主文本/视觉模型可按功能降级。
48
+
49
+ 客户端握手是当前浏览器实例的最后报告。关闭页面后报告不会立刻证明页面仍存活,因此它是装载兼容性证据,不是在线心跳。Host Doctor 也不替代真实 Provider 探针;任何可能计费的测试仍必须由用户显式触发。
@@ -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 通过不等于这些消费者门禁已通过。
@@ -0,0 +1,138 @@
1
+ # Provider Adapter v0 生命周期契约
2
+
3
+ 状态:**v0.1.4 的六操作内部契约已实现;0.2.0 增加可选输入准备能力。** 这是 Iris Core 模块与供应商协议实现之间的内部边界,尚未冻结为第三方公开 SDK。当前 DashScope 与 OpenAI Images 兼容实现已经通过同一套零网络 conformance runner。
4
+
5
+ ## 目标与依赖方向
6
+
7
+ ```text
8
+ Action / Task watcher / recovery / model discovery
9
+ │ canonical operation/result
10
+ ▼
11
+ provider-adapter.js
12
+ │
13
+ provider-adapters.js
14
+ │ injected transport
15
+ ▼
16
+ adapters.js
17
+ ```
18
+
19
+ - Action、Task 恢复、重新交付、模型发现和能力实测不得直接调用供应商生命周期 HTTP 方法。
20
+ - Provider Adapter 不创建或修改 Iris Task,不读取 DSH `ctx`,也不承担工作台展示。
21
+ - `adapters.js` 保留为协议级 HTTP transport;凭据和实际媒体端点仅由具体 Adapter 的操作闭包捕获。配置可用 `mediaBaseUrl` 分离媒体与普通/视觉端点,空值沿用 `baseUrl`。
22
+ - Adapter 快照只包含稳定身份、协议、能力与 supported/unsupported 事实,不包含 API Key、Authorization、私有 URL、配置对象或 live transport。
23
+
24
+ ## 描述结构
25
+
26
+ 0.2.0 的协议工厂由内部注册表选择,当前仍只有 `dashscope` 和 `openai-images` 两项,不提供公开注册 API。未知的显式 `mediaProtocol` 保留原值,调用时以 `IRIS_PROVIDER_PROTOCOL_UNSUPPORTED` 在联网前拒绝;错误只包含合法协议标识,不回显端点或凭据。
27
+
28
+ 未配置协议或选择 `auto` 时,官方端点可识别为对应协议;其他端点继续使用 OpenAI Images 兼容路径,并标记 `protocolInferred:true`。该标记表示尚待用户确认兼容性,不代表已经验证成功。显式选择协议会清除标记;旧记录中已保存的明确协议不追溯猜测其来源。CLI 与 DSH 共用选择逻辑,Task binding 绑定最终实际协议与端点。
29
+
30
+ 视觉理解的 `type` 使用独立注册表,目前只有 `openai` 实现。导入保留显式类型;未支持类型在该后端调用前返回 `IRIS_VISION_PROTOCOL_UNSUPPORTED`,不会被静默改写为另一种协议。错误保留在后端链的 `errors` 中,既有宿主视觉降级仍可继续。`auth:'none'` 不发送认证头,缺省认证方式仍为 `bearer`。
31
+
32
+ `defineProviderAdapter()` 只接受以下顶层字段:
33
+
34
+ ```js
35
+ {
36
+ contractVersion: 0,
37
+ id: 'stable-provider-id',
38
+ protocol: 'dashscope',
39
+ capabilities: ['image', 'video', 'tts', 'transcribe'],
40
+ operations: { discover, submit, poll, download, mapError },
41
+ unsupported: { cancel: '经过验证的原因' },
42
+ // 可选扩展:prepareInput 或 unsupportedPreparation,二者不能同时声明
43
+ prepareInput: async ({ model, filePath, signal, timeoutMs }) => ({ url: '临时地址' })
44
+ }
45
+ ```
46
+
47
+ 六个操作必须**恰好**出现在 `operations` 或 `unsupported` 之一。方法缺失不能被解释为临时故障;调用显式不支持的方法会得到稳定的 `IRIS_PROVIDER_OPERATION_UNSUPPORTED`。v0 要求所有 Adapter 实现 `submit` 与 `mapError`,其余操作可根据协议事实显式声明不支持。
48
+
49
+ `prepareInput` 不属于这六个生命周期操作。未提供该扩展的旧 Adapter 仍有效,能力快照中的 `inputPreparation` 会标为 unsupported;具体协议也可以用 `unsupportedPreparation` 给出受控原因。快照不包含凭据、端点或临时地址。
50
+
51
+ ## 生命周期操作
52
+
53
+ | 操作 | 输入职责 | canonical 结果 |
54
+ |---|---|---|
55
+ | `discover` | 可选 signal/timeout | `{ models: [{ id, capabilities? }] }`,去空并按 ID 去重 |
56
+ | `submit` | capability、model、规范化 input、signal | `completed / accepted / not_accepted / acceptance_unknown` |
57
+ | `poll` | capability、remoteTaskId、signal | `pending / succeeded / failed / canceled / unknown` |
58
+ | `cancel` | remoteTaskId、signal | `canceled / not_supported / unknown` |
59
+ | `download` | 可物化 artifact、目标路径、signal | `{ bytes }` |
60
+ | `mapError` | 原始错误与 stage/acceptance 上下文 | 可持久化的脱敏错误记录 |
61
+
62
+ Core Attempt 持久化 `providerId::modelId` 复合身份,具体 Provider Adapter 的 `submit.model` 只接收供应商原始模型 ID;复合引用不得穿透到 HTTP。
63
+
64
+ `accepted` 不是独立网络操作,而是异步 `submit` 的受理结果。它必须携带非空远端任务 ID;同步成功使用 `completed`,并提供 canonical `artifacts`。迁移期仍保留可直接读取但不随 JSON 序列化的旧 `value`,供 0.1.4 调用方使用。四态提交和自动 failover 规则继续以 [Provider 提交契约 v0](PROVIDER_SUBMISSION_CONTRACT.md) 为准。
65
+
66
+ ## poll、cancel 与交付事实
67
+
68
+ `poll` 的终态不能互相折叠:
69
+
70
+ - `failed` 需要供应商明确的失败证据;
71
+ - `canceled` 需要远端确认,Task 才能写入 `cancelState=remote_confirmed`;
72
+ - `unknown` 只停止当前盯守并保留 `acceptance=accepted`,不得伪造失败或重提;
73
+ - `succeeded` 和图片 `completed` 使用同一可物化描述:`remote-url` 或 `inline-base64`;文本任务仍可使用 text value。生成成功先于本地物化事实落盘。
74
+
75
+ `download` 物化已经确认成功的产物:`remote-url` 执行下载,OpenAI Images 的 `inline-base64` 直接写入私有 staging。inline 正文只作为 non-enumerable 的短生命周期字段传给 Runner,不进入 JSON、Task 或 Manifest。物化失败只能改变交付状态,不能触发生成重提。OpenAI Images 当前同步返回图片,所以 `poll` 与 `cancel` 显式 unsupported;DashScope 当前没有经过验证的远端取消接口,因此 `cancel` 同样显式 unsupported,而不是伪装取消成功。
76
+
77
+ ## 可选输入准备与上传边界
78
+
79
+ Host 和 CLI 通过 `prepareProviderInput(adapter, { model, filePath, signal?, timeoutMs? })` 调用可选准备能力,不能自行绕过 Adapter 上传。DashScope 的 Adapter 闭包持有实际凭据与媒体端点并实现临时上传;OpenAI Images 明确不支持。未实现时在网络调用前抛 `IRIS_PROVIDER_INPUT_PREPARATION_UNSUPPORTED`。返回的 `{url}` 只供紧随其后的提交读取,属性不参与 JSON 序列化,不能进入 Task、日志或能力快照。
80
+
81
+ 上传错误经 Adapter 映射与脱敏,固定为 `stage=upload / acceptance=not_accepted`;这组字段属于返回调用方的安全错误分类,**不等于已持久化的 Core Attempt stage**。上传属于 Host/CLI 输入准备,只有随后真正调用 `submit` 才跨越可能计费的受理边界。当前 s2v 仍走 legacy 链,其 Attempt 可保存上传错误;Core 转写的 `audio_path` 在创建 Task 前上传,失败会直接返回且不创建 Core Task,也不会调用 submit。这是 D-12 当前采用的路径 B;若未来要求把准备失败纳入 Core 事实,须另行评审路径 A,不能由错误字段反推已经落盘。
82
+
83
+ ## 错误与取消传播
84
+
85
+ 所有操作接收可选 `AbortSignal`。底层发现、轮询、下载和提交 transport 必须把外部 signal 与自己的超时合并。错误映射只保留受控字段:
86
+
87
+ - stage、category、acceptance、retryable;
88
+ - 可选安全的 HTTP status/provider code;
89
+ - 已移除密钥、认证头、绝对路径和签名查询的 `safeMessage`。
90
+
91
+ `submit` 抛错由统一调用器转换成保守四态;`cancel` 抛错转换为 `unknown`;`discover/poll/download` 抛出脱敏的 `ProviderContractError`,由各自调用方应用重试和事实规则。
92
+
93
+ ## 当前内置实现
94
+
95
+ | 协议 | discover | submit | poll | cancel | download | mapError |
96
+ |---|---:|---:|---:|---:|---:|---:|
97
+ | DashScope | 支持(`/api/v1/models` 分页与能力元数据) | 图片/视频/TTS/转写 | 图片/视频/转写 | 显式不支持 | 支持 | 支持 |
98
+ | OpenAI Images 兼容 | 支持 | 同步图片 | 显式不支持 | 显式不支持 | 支持 | 支持 |
99
+
100
+ 本表只描述协议生命周期,不扩大 0.1.4 的供应商范围。Gemini、Fal 与 Replicate 仍在 0.2.x 路线中。
101
+
102
+ ## conformance runner
103
+
104
+ ### 协议 Transport 接口
105
+
106
+ `provider-adapters.js` 的构造器接受可选 `transport`,默认使用 `adapters.js`。它是内部协议实现与测试 fixture 的注入边界,不是 Core API。除独立下载方法外,网络方法接收 `key`、`baseUrl`、可选 `signal`/`timeoutMs`;只有适用方法接收 `model` 或 `remoteTaskId`。
107
+
108
+ | 方法 | 额外输入 | 返回值 |
109
+ |---|---|---|
110
+ | `listModels` | 无 | 模型条目数组,保留能力元数据 |
111
+ | `dashscopeImageMode` | 原始模型名(位置参数) | `legacy-async` 或 `multimodal-sync`,本地判断 |
112
+ | `submitImage` | `prompt/size/n` | 远端任务 ID |
113
+ | `generateImageMultimodal` | `prompt/size/n` | 图片 URL 数组 |
114
+ | `openAiGenerateImage` | `prompt/size/n` | URL 或 base64 图片条目数组 |
115
+ | `submitVideo` | `prompt/imgDataUrl/size/duration/audioUrl/resolution` | 远端任务 ID |
116
+ | `submitTranscription` | `audioUrl` | 远端任务 ID |
117
+ | `synthesizeTts` | `text/voice` | `{audioUrl}` 或 `{audioB64}` |
118
+ | `pollTask/pollTranscriptionTask` | `remoteTaskId` | 协议结果,由 Adapter 归一化为 canonical poll 结果 |
119
+ | `uploadTempFile` | `model/filePath` | 临时输入 URL;仅由支持该扩展的协议 Adapter 调用 |
120
+ | `downloadTo` | URL、目标路径、选项(位置参数) | 写入字节数 |
121
+
122
+ transport 抛出的原始错误必须经 Adapter 的错误映射;提交异常不能直接授权重提。`uploadTempFile` 经过 `prepareProviderInput` 归一化错误,但仍不属于六个 canonical 操作。
123
+
124
+ ### 验证方式
125
+
126
+ 零网络 runner 位于 `tests/fixtures/provider-conformance.mjs`,并由 `tests/provider-adapters-conformance.mjs` 对内置实现执行。它验证:
127
+
128
+ 1. 快照可序列化且不含凭据;
129
+ 2. 六个操作全部被 supported/unsupported 记账;
130
+ 3. 每个 supported 操作都有 fixture 场景;
131
+ 4. unsupported 调用返回稳定错误;
132
+ 5. mapError 会清除 synthetic secret 与本地路径;
133
+ 6. 提交四态、轮询五态、URL/inline 物化字节数和协议特定能力符合契约;
134
+ 7. 同步多图片进入同一 Core Task,坏同步响应保持受理未知并停止 failover。
135
+
136
+ runner 本身不访问网络;具体 Adapter 构造时注入 fake transport。新增 Provider 前,必须先通过同一 runner,再进行用户明确授权的真实供应商短验。0.1.4 不发布 Provider SDK,也不把测试 runner 当成第三方兼容性承诺。
137
+
138
+ 可选准备能力另由 `tests/provider-input-preparation.mjs` 和调用方的转写/s2v 测试覆盖:包括未支持协议零网络拒绝、临时 URL 不序列化、上传错误脱敏及上传前后受理边界。