@huanlin/dsh-plugin-aigc-canvas 0.1.0 → 0.1.2

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/README.md CHANGED
@@ -1,8 +1,8 @@
1
+ 本项目已被dshfind.com收录
2
+
1
3
  # dsh-aigc-canvas
2
4
 
3
- > DSH 插件:provider-agnostic AIGC HTTP + 自由画布 + ffmpeg 后处理。Agent 通过
4
- > `aigc_http_request` 调用任意 HTTP AIGC API(endpoint + apiKey 自动附加),生成的文件用
5
- > `aigc_canvas_place` 摆到无限画布上,可用 `aigc_media_edit` (ffmpeg) 后处理。
5
+ > DSH 插件:一个节点-连线式的 AIGC 画布。向模型暴露文生图 / 文生视频 / 首尾帧生视频 / 多参考生视频 / 音频生成五类工具,生成的图片 / 视频 / 音频以及提示词都作为画布元素(uuid 寻址)存在,生成完成后自动按多对一(promise + 所有参考元素 输出)在画布上连线。
6
6
 
7
7
  ## 安装
8
8
 
@@ -11,7 +11,7 @@
11
11
  dsh plugin --profile <profile> add link:D:\Projects\deepseek-harness\dsh-aigc-canvas
12
12
 
13
13
  # 从 git 安装(发布到 dsh-external 组织后):
14
- dsh plugin --profile <profile> add github:huanlinoto/dsh-plugin-aigc-canvas
14
+ dsh plugin --profile <profile> add github:dsh-external/dsh-aigc-canvas
15
15
  ```
16
16
 
17
17
  预构建 `lib/` 入库策略(含 `@deepseek-ai/*` private peer deps,必须预构建),`github:` 安装开箱即用,无需 `allowBuilds`。
@@ -22,128 +22,60 @@ dsh plugin --profile <profile> add github:huanlinoto/dsh-plugin-aigc-canvas
22
22
 
23
23
  | 字段 | 类型 | 默认值 | 说明 |
24
24
  |------|------|--------|------|
25
- | `providers` | `AigcProvider[]` | 见下 | 一个或多个 AIGC provider;第一个为默认。运行时可在设置页 CRUD |
26
- | `requestTimeoutMs` | number | `300000` (5 min) | 单次 provider 请求超时(ms) |
27
- | `mediaSizeLimit` | number | `104857600` (100 MiB) | 单个媒体文件大小上限(落盘校验) |
28
-
29
- ### `AigcProvider` 字段
30
-
31
- | 字段 | 类型 | 默认值 | 说明 |
32
- |------|------|--------|------|
33
- | `id` | string | 必填 | provider 标识符(小写字母+数字+连字符,字母开头)。作为 `provider_id` 传给工具 |
34
- | `name` | string | `''` | 显示名(如 "Volcano Engine") |
35
- | `endpoint` | string | `stub://aigc-backend` | API endpoint URL。`stub://aigc-backend` = 内置 stub 后端 |
36
- | `apiKey` | string | `''` | API key(仅内存;通过 GUI 或 cordis.yml 设置) |
37
- | `instructions` | string | `''` | Agent 通过 `aigc_get_provider_info` 读取的调用说明(预览)+ `aigc_provider_get_instructions` 读全量 |
38
- | `auth.scheme` | `'bearer'` \| `'header'` \| `'query'` | `'bearer'` | apiKey 附加方式 |
39
- | `auth.name` | string | `''` | `header` 方式的 header 名(默认 `x-api-key`)或 `query` 方式的参数名(默认 `api_key`) |
40
- | `builtin` | boolean | `false` | 是否是 seed 层内置 provider(仅 cordis.yml 标记) |
25
+ | `baseURL` | string | `stub://aigc-backend` | AIGC 后端 API 地址。`stub://aigc-backend` = 内置 stub 后端(无网络,合成 PNG/MP4/MP3 字节)。其他值会触发 `backend-error` 等待未来实现 |
26
+ | `apiKeyEnv` | credential-ref | `AIGC_API_KEY` | API key 的环境变量名 / 凭据引用。stub 后端不读 |
27
+ | `requestTimeoutMs` | number | `60000` | 单次后端请求超时(ms) |
28
+ | `mediaSizeLimit` | number | `104857600` (100 MiB) | 单个媒体文件大小上限(媒体路由校验) |
41
29
 
42
30
  ## 工具
43
31
 
44
- 模型可见的十三个工具,均在调用代理的会话作用域内执行(模型不需要传 `sessionId`):
45
-
46
- | 工具 | 用途 |
47
- |------|------|
48
- | `aigc_get_provider_info` | 列出所有 provider(id/name/endpoint/instructions 预览/capabilities/capabilityMap/stub 标志)。**最先调用** |
49
- | `aigc_http_request` | 向 provider API 发 HTTP 请求(endpoint + apiKey 自动附加)。二进制响应落盘返回 `file_path`,JSON/文本内联返回。有 EndpointSpec 时按 spec 处理响应 |
50
- | `aigc_provider_set_instructions` | 探测 API 后记录 provider 的调用说明(每 provider 限 1000 字,旧式自由文本) |
51
- | `aigc_provider_get_instructions` | 拉取一个 provider 的完整 instructions(`aigc_get_provider_info` 只返回前 200 字预览) |
52
- | `aigc_provider_set_endpoints` | 探测 API 后记录结构化 EndpointSpec[] catalog(自动派生 instructions,支持 spec 驱动响应处理) |
53
- | `aigc_get_endpoint_details` | 拉取一个 provider+capability 的完整 EndpointSpec[](path/method/params/response shape) |
54
- | `aigc_probe_endpoint` | 向一个 endpoint 发最小测试请求,自动嗅探响应格式(ResponseKind + path) |
55
- | `aigc_reroll` | 基于已有元素的 `meta.originalRequest` 重新生成(支持 seed/prompt_delta/prompt_replace/size patch,count>1 时生成变体簇) |
56
- | `aigc_canvas_place` | 把文件摆到画布上(可选 x/y,可自动布局;可附 references + relation 自动连边) |
57
- | `aigc_canvas_link` / `aigc_canvas_unlink` | 创建/删除两个元素之间的边(filePath 寻址,relation 必填:input/first_frame/style/variation_of/...) |
58
- | `aigc_canvas_list_elements` | 只读:返回当前会话画布的完整快照(elements + edges with relation) |
59
- | `aigc_media_edit` | ffmpeg 编辑(concat/clip/extract_audio/extract_frame/speed/resize/reverse/add_audio/images_to_video) |
60
-
61
- ### `aigc_get_provider_info`
62
-
63
- 列出所有已配置的 provider。返回每个 provider 的 `id`、`name`、`endpoint`、`instructions`(前 200 字预览 + `instructions_total_chars`)、`isStub`、`isDefault`。
64
-
65
- 参数:无。
66
-
67
- ### `aigc_http_request`
68
-
69
- 向 provider API 发送一个 HTTP 请求。provider 的 `endpoint` 和 `apiKey` 自动附加(Agent 永远看不到 apiKey)。`path` 相对 provider endpoint,如 `/v1/images/generations`;同源的绝对 URL 也接受(用于下载 provider 返回的下载链接)。
32
+ 模型可见的六个工具,均在调用代理的会话作用域内执行(模型不需要传 `sessionId`):
70
33
 
71
- 二进制响应(image/video/audio)落盘到会话 canvas 目录并返回 `file_path`;JSON/文本响应内联返回(过大时落盘并返回预览 + `file_path`)。非 2xx 响应返回 `{ ok: false, status, error, sent_body_preview }`,便于 Agent 自诊断字段丢失/编码 bug。
34
+ ### `aigc_text_to_image`
72
35
 
73
- **`$base64` / `$data_uri` 占位符**:在 `json_body` `body` 内使用 `{"$base64": "<file_path>"}` 或 `{"$data_uri": "<file_path>"}`,host 会读取画布元素文件并替换占位符。`file_path` 必须在会话 canvas 目录内。
36
+ 文生图。把 prompt 也作为 prompt 元素存入画布,生成图片作为 image 元素,自动连线 prompt image。
74
37
 
75
- 参数:`provider_id?`、`method?`、`path`(必填)、`headers?`、`query?`、`json_body?` / `body?`(二选一)
38
+ 参数:`prompt`(必填)、`negative_prompt`、`width`(默认 1024)、`height`(默认 1024)、`seed`。
76
39
 
77
- ### `aigc_provider_set_instructions`
40
+ ### `aigc_text_to_video`
78
41
 
79
- 记录一个 provider 的调用说明(endpoints、请求格式、参数、响应形状)。每 provider 限 1000 字。Agent 探测 API 后调用此工具持久化,后续会话可直接使用。
42
+ 文生视频。同上,prompt 元素 video 元素。
80
43
 
81
- 参数:`provider_id`(必填)、`instructions`(必填,≤ 1000 )
44
+ 参数:`prompt`(必填)、`negative_prompt`、`width`(默认 1280)、`height`(默认 720)、`duration_seconds`(默认 5)、`seed`。
82
45
 
83
- ### `aigc_provider_get_instructions`
46
+ ### `aigc_first_last_frame_to_video`
84
47
 
85
- 拉取一个 provider 的**完整** instructions。`aigc_get_provider_info` 只返回前 200 字预览;当需要精确的 endpoint 路径/参数名/响应形状时调用此工具。
48
+ 首尾帧 + 文生视频。`first_frame_uuid` / `last_frame_uuid` 必须是已存在的 image 元素 uuid(由 `aigc_text_to_image` 返回)。prompt 元素 + 两个 image 元素都 → video 元素(三条边)。
86
49
 
87
- 参数:`provider_id`(必填)。返回:`{ provider_id, instructions, total_chars }`。
50
+ 参数:`prompt`(必填)、`first_frame_uuid`(必填)、`last_frame_uuid`(必填)、`width`、`height`、`duration_seconds`、`seed`。
88
51
 
89
- ### `aigc_canvas_place`
52
+ ### `aigc_multi_reference_to_video`
90
53
 
91
- 把文件摆到画布上(`aigc_http_request` 返回的 `file_path`)。文件必须已存在于会话 canvas 目录内。
54
+ 多参考生视频。`reference_uuids` image / video / audio 元素的 uuid 数组(至少一个)。prompt 元素 + 所有参考元素都 → video 元素。
92
55
 
93
- - `x` / `y` 可省略,**优先省略让 host 自动布局**:有 `references` 时新元素落到最右参考的右侧(垂直居中),否则落到现有最低元素下方的左对齐垂直列
94
- - `references` 是已有元素的 filePath 数组,自动从每个参考向新元素连边
95
- - `description` 是 ≤ 40 字的极简描述(名词/形容词/短语,如 "orange cat"),显示在卡片上
56
+ 参数:`prompt`(必填)、`reference_uuids`(必填,非空数组)、`width`、`height`、`duration_seconds`、`seed`。
96
57
 
97
- 参数:`file_path`(必填)、`description`(必填)、`x?`、`y?`、`title?`、`kind?`、`prompt?`、`meta?`、`references?`。
58
+ ### `aigc_generate_audio`
98
59
 
99
- ### `aigc_canvas_link` / `aigc_canvas_unlink`
60
+ 文本生成音频。prompt 元素 audio 元素。
100
61
 
101
- 创建/删除两个元素之间的边(filePath 寻址,source → target)。幂等。
102
-
103
- 参数:`source`(必填)、`target`(必填)。
62
+ 参数:`prompt`(必填)、`duration_seconds`(默认 10)、`seed`。
104
63
 
105
64
  ### `aigc_canvas_list_elements`
106
65
 
107
- 只读:返回当前会话画布的完整快照。每个元素返回 `filePath`(主标识符)、`kind`、`title`、`x`、`y`、`createdAt`、`producedBy`、可选 `promptText`/`mediaSize`/`meta`。每条边返回 `source` filePath → `target` filePath
66
+ 只读:返回当前会话画布的完整快照(elements + edges)。用于长工具序列后恢复状态、查找要传给后续调用的 uuid
108
67
 
109
68
  参数:无。
110
69
 
111
- ### `aigc_media_edit`
112
-
113
- 通过 ffmpeg 编辑媒体文件。`operation` 选定操作,所有输入文件必须已存在于会话 canvas 目录内,输出落盘并返回 `file_path`。
114
-
115
- | 操作 | inputs | output_ext | 关键参数 |
116
- |------|--------|------------|---------|
117
- | `concat` | ≥ 2 视频 | mp4 | — |
118
- | `clip` | 1 视频 | mp4 | `start`/`end` 或 `start`/`duration`(秒) |
119
- | `extract_audio` | 1 视频 | mp3 | — |
120
- | `extract_frame` | 1 视频 | png | `timestamp`(秒) |
121
- | `speed` | 1 视频 | mp4 | `speed`(2 = 2x,0.5 = 半速) |
122
- | `resize` | 1 视频 | mp4 | `width` 和/或 `height`(像素) |
123
- | `reverse` | 1 视频 | mp4 | — |
124
- | `add_audio` | 1 视频 + 1 音频 | mp4 | — |
125
- | `images_to_video` | ≥ 1 图片 | mp4 | `fps`(默认 2) |
126
-
127
- **ffmpeg 查找顺序**:
128
- 1. `AIGC_FFMPEG_PATH` 环境变量(显式覆盖,适用于非标准安装路径)
129
- 2. `ffmpeg` on PATH(macOS/Linux 和大多数 Windows 的正常情况)
130
- 3. 平台特定常见安装位置:
131
- - **Windows**:`C:\ffmpeg\bin\ffmpeg.exe`、`C:\Program Files\ffmpeg\bin\ffmpeg.exe`、`C:\Program Files (x86)\ffmpeg\bin\ffmpeg.exe`、`${CONDA_PREFIX}\Scripts\ffmpeg.exe`
132
- - **macOS/Linux**:`/usr/bin/ffmpeg`、`/usr/local/bin/ffmpeg`、`/opt/homebrew/bin/ffmpeg`
133
-
134
- 找不到时抛 `backend-error`,错误信息指引安装方式。
135
-
136
70
  ## 画布视图
137
71
 
138
72
  通过 better-sidebar 的服务消费(`ctx.betterSidebar.registerTab`)注册一个 `aigc-canvas:main` tab。tab 是单实例(每个会话一个),打开后:
139
73
 
140
74
  - 通过 WebSocket `/aigc-canvas/ws/canvas?sessionId=...` 订阅 host 端的画布变更推送
141
75
  - 首次加载会先 HTTP `POST /aigc-canvas/api/canvas.list` priming 一次快照
142
- - 节点按 vertical flow 排列,每个节点的入边在节点上方以 chip 形式显示
76
+ - 节点按 vertical flow 排列,每个节点的入边在节点上方以 chip 形式显示(短 uuid)
143
77
  - 不同 kind 用左侧色条区分:prompt 蓝 / image 绿 / video 橙 / audio 紫
144
- - 边按 relation 分线型:实线=直接输入(input/first_frame/last_frame/audio_track),虚线=参考(reference/style/mask),点线=变体(variation_of/remix_of/alternative_of),粗实线=编辑链(edited_from)。曲线中点显示关系标签
145
- - WS 断开后自动重连
146
- - **请求日志面板**:header 上的 📊 按钮打开浮层,显示本会话所有 `aigc_http_request` + `aigc_media_edit` 调用(时间/provider/path/状态/耗时/大小),点条目展开详情(请求头 + 请求体 + 响应预览,apiKey 已脱敏),可"在画布上定位"产物元素
78
+ - WS 断开后 2 秒自动重连
147
79
 
148
80
  > better-sidebar 未安装时,host 半的工具 + 元素表仍然正常工作,只是没有 UI 可视化(未来的 host-side 消费者可以通过 `ctx.aigcCanvas` 服务读取状态)。
149
81
 
@@ -161,25 +93,25 @@ dsh plugin --profile <profile> add github:huanlinoto/dsh-plugin-aigc-canvas
161
93
  <cwd>/.dsh-aigc-canvas/<sessionId>/<uuid>.<ext>
162
94
  ```
163
95
 
164
- provider 列表持久化到 `~/.dsh/aigc-canvas/providers.json`(用户运行时 CRUD 后保留)
96
+ 扩展名按 kind 决定:`prompt` → `.txt`(实际上 prompt 元素不写文件,只在 JSON 中存 `promptText`)、`image` → `.png`、`video` → `.mp4`、`audio` → `.mp3`。
165
97
 
166
- 刷新浏览器 / 重启 DSH 后,会话画布从 `canvas.json` 重新水合,媒体文件保留在原位;provider 列表从 `providers.json` 重新加载。
98
+ 刷新浏览器 / 重启 DSH 后,会话画布从 `canvas.json` 重新水合,媒体文件保留在原位。
167
99
 
168
100
  ## 开发
169
101
 
170
102
  ```sh
171
103
  pnpm install # 安装开发依赖(schemastery、typescript、vitest、tsdown)
172
104
  pnpm run typecheck # tsc --noEmit 类型检查
173
- pnpm test # vitest run 单元测试(canvas-registry / tools / wire / provider-store)
174
- pnpm run build # tsdown 构建 → lib/index.js + lib/invariant.js + lib/client.js + tsc -p tsconfig.build.json
105
+ pnpm test # vitest run 单元测试(canvas-registry / tools / wire)
106
+ pnpm run build # tsdown 构建 → lib/index.js + lib/invariant.js + lib/client.js
175
107
  pnpm watch # tsdown --watch(client bundle 热重建)
176
108
  ```
177
109
 
178
110
  构建产物:
179
111
  - `lib/index.js` — host 入口(cordis 插件,提供 `ctx.aigcCanvas` 服务 + 路由 + 工具)
180
112
  - `lib/invariant.js` — 包级 invariant 伴生
181
- - `lib/client.js` — 浏览器 bundle(`window.__ModuleLoader__.load` 闭包工厂,id = `@huanlin/dsh-plugin-aigc-canvas`)
182
- - `lib/index.d.ts` 等 — TypeScript 声明(由 `tsc -p tsconfig.build.json` 产出)
113
+ - `lib/client.js` — 浏览器 bundle(`window.__ModuleLoader__.load` 闭包工厂,id = `@dsh-external/dsh-aigc-canvas`)
114
+ - `lib/index.d.ts` 等 — TypeScript 声明(由 `tsc -p tsconfig.json` 产出,不在 tsdown 流程内)
183
115
 
184
116
  ## 目录结构
185
117
 
@@ -193,23 +125,19 @@ dsh-aigc-canvas/
193
125
  │ ├── wire.ts # HTTP helpers + AigcError
194
126
  │ ├── trust-fence.ts # Host 头信任围栏(从 better-sidebar 拷贝)
195
127
  │ ├── canvas-registry.ts # 元素表 + 边 + 持久化(host-owned state)
196
- │ ├── provider-http.ts # 抽象 provider HTTP 客户端(stub + 真实 fetch)
197
- │ ├── provider-store.ts # ProviderStore(CRUD + 持久化到 ~/.dsh/aigc-canvas/providers.json)
198
- │ ├── media-edit.ts # ffmpeg 编辑引擎
199
- │ ├── tools.ts # 9 个 defineTool
128
+ │ ├── aigc-backend.ts # 抽象 AIGC 后端客户端(stub 实现)
129
+ │ ├── tools.ts # 6 defineTool
200
130
  │ ├── types.d.ts # @deepseek-ai/dsh-tools + cordis 环境类型声明
201
131
  │ └── client/
202
132
  │ ├── index.tsx # client 入口:注册 better-sidebar tab
203
133
  │ ├── CanvasView.tsx # 画布主视图
204
134
  │ ├── CanvasNode.tsx # 节点组件
205
- │ ├── SettingsPage.tsx # provider 设置页
206
135
  │ ├── store.ts # CanvasStore(WS 订阅 + useSyncExternalStore)
207
136
  │ ├── api.ts # HTTP/WS 客户端
208
137
  │ ├── locales.ts # i18n(zh/en)
209
138
  │ └── canvas.module.css # 画布样式
210
139
  ├── tests/
211
140
  │ ├── canvas-registry.spec.ts
212
- │ ├── provider-store.spec.ts
213
141
  │ ├── tools.spec.ts
214
142
  │ └── wire.spec.ts
215
143
  ├── cordis.patch.yml # bundle 层:插入插件行
@@ -222,15 +150,30 @@ dsh-aigc-canvas/
222
150
  └── README.md
223
151
  ```
224
152
 
153
+ ## 后端实现路线(stub → 真实)
154
+
155
+ 当前 `aigc-backend.ts` 的 stub 在 `baseURL === 'stub://aigc-backend'`(或为空)时返回合成字节;其他 `baseURL` 立即抛 `backend-error`(HTTP 501)。
156
+
157
+ 要接入真实后端:
158
+
159
+ 1. 在 `aigc-backend.ts` 的每个 `generate*` 方法的 `!this.isStub` 分支里替换为真实 `fetch` 调用(参考 dsh-mineru 的 `MinerUClient`)。
160
+ 2. 后端 API 形状建议保持 `{ prompt, ..., signal } → { mediaBytes, meta }` 的统一契约,这样 `tools.ts` 不需要改动。
161
+ 3. 工具的参数 schema 已包含 `width / height / duration_seconds / seed` 等通用字段,后端可以选择性消费。
162
+ 4. 媒体格式: stub 用最小合法 PNG / ftyp-only MP4;真实后端返回的字节由 host 直接落盘 + 浏览器渲染,不需要转码。
163
+
225
164
  ## 安全边界
226
165
 
227
166
  - 路由受 Host 头信任围栏保护(与 `/api` 一致;`0.0.0.0` 部署时由 `dsh web` 启动器动态派生的 LAN IP 列表生效)
228
167
  - `/aigc-canvas/file` 仅限会话 canvas 目录内的媒体文件
229
- - `/aigc-canvas/api/*` JSON API 受同一 Host 头信任围栏保护
230
168
  - 工具执行绑定到调用代理的会话 id(`exec.agent.session.id`),模型不能跨会话读取 / 引用其他会话的元素
231
- - `aigc_http_request` `path` 相对 provider endpoint;绝对 URL 仅限同源(防 SSRF)
232
- - `$base64` / `$data_uri` 占位符的 `file_path` 必须在会话 canvas 目录内
233
- - provider apiKey 永不出现在工具输出中;`aigc_http_request` 内部附加 auth header/param
169
+ - `apiKeyEnv` 通过 `ctx.get('credentials')` 懒解析,先于 `process.env`
170
+
171
+ ## 已知限制(v0.1 阶段)
172
+
173
+ - 后端 stub,无真实 AIGC 调用
174
+ - 画布视图是垂直 flow,不是真正的图布局(force-directed / DAG 布局待后续)
175
+ - 边以 chip 形式标注在节点上方,没有 SVG 连线(避免引入 react-flow 等运行时依赖)
176
+ - 元素不支持删除 / 编辑(只能通过删除 `canvas.json` + 重启会话来重置画布)
234
177
 
235
178
  ## 规范符合性
236
179
 
@@ -0,0 +1,142 @@
1
+ /** Discriminated union of element kinds the canvas stores. */
2
+ export type AigcElementKind = 'prompt' | 'image' | 'video' | 'audio';
3
+ /** File extension for each media kind (no leading dot). */
4
+ export declare function extensionFor(kind: AigcElementKind): string;
5
+ /** MIME type for each media kind (for the file route). */
6
+ export declare function mimeTypeFor(kind: AigcElementKind): string;
7
+ /** One node on the canvas. */
8
+ export interface AigcElement {
9
+ /** Stable opaque handle (internal; used for edges). */
10
+ uuid: string;
11
+ /** Owning conversation id. */
12
+ sessionId: string;
13
+ /** Discriminator. */
14
+ kind: AigcElementKind;
15
+ /** Display title (short human-readable label). */
16
+ title: string;
17
+ /** Canvas position, world coordinates (infinite free canvas). */
18
+ x: number;
19
+ /** Canvas position, world coordinates (infinite free canvas). */
20
+ y: number;
21
+ /** Creation time (ms since epoch). */
22
+ createdAt: number;
23
+ /** Tool that produced this element. */
24
+ producedBy: string;
25
+ /**
26
+ * Absolute path to the element file on disk. For prompt elements: the
27
+ * `.txt` file containing the prompt text. For media elements: the media
28
+ * file. This is the **primary external identifier** — tools return and
29
+ * accept this path.
30
+ */
31
+ filePath: string;
32
+ /** For prompt elements: the prompt text (mirrored in the .txt file). */
33
+ promptText?: string;
34
+ /** For media elements: byte size of the media file. */
35
+ mediaSize?: number;
36
+ /** Freeform metadata bag (dimensions, duration, model, seed, ...). */
37
+ meta?: Record<string, unknown>;
38
+ /**
39
+ * Ultra-short model-supplied description of the element (a noun, an
40
+ * adjective, or a short phrase — e.g. "orange cat", "sunset beach",
41
+ * "fast cut"). Bounded to ~40 chars; shown on the node card under the
42
+ * title and injected into context when the element is referenced.
43
+ */
44
+ description?: string;
45
+ }
46
+ /** One edge: source element → target element (multi-to-one fan-in). */
47
+ export interface AigcEdge {
48
+ /** Source element uuid (an input — prompt or reference). */
49
+ source: string;
50
+ /** Target element uuid (the produced output). */
51
+ target: string;
52
+ }
53
+ /** The serializable canvas state for one session. */
54
+ export interface AigcCanvasState {
55
+ sessionId: string;
56
+ elements: AigcElement[];
57
+ edges: AigcEdge[];
58
+ }
59
+ /** Listener callback receives the session id that changed. */
60
+ export type AigcCanvasListener = (sessionId: string) => void;
61
+ /** The registry service published as `ctx.aigcCanvas`. */
62
+ export interface AigcCanvasService {
63
+ /** Add a prompt element (writes a .txt file). Returns the new element. */
64
+ addPrompt(sessionId: string, params: {
65
+ title: string;
66
+ promptText: string;
67
+ producedBy: string;
68
+ x?: number;
69
+ y?: number;
70
+ meta?: Record<string, unknown>;
71
+ description?: string;
72
+ }, cwd: string): Promise<AigcElement>;
73
+ /** Add a media element (image/video/audio) with the given bytes on disk. */
74
+ addMedia(sessionId: string, params: {
75
+ kind: 'image' | 'video' | 'audio';
76
+ title: string;
77
+ producedBy: string;
78
+ mediaBytes: Buffer;
79
+ x?: number;
80
+ y?: number;
81
+ meta?: Record<string, unknown>;
82
+ description?: string;
83
+ }, cwd: string): Promise<AigcElement>;
84
+ /**
85
+ * Register an element for a file that already exists on disk inside the
86
+ * session canvas directory (written by the model's http tool). The file
87
+ * is not copied; the element references it in place.
88
+ *
89
+ * x/y are optional: when omitted the host picks a position automatically
90
+ * (a left-aligned vertical column below the lowest existing element) so
91
+ * newly placed elements land somewhere reasonable instead of all piling
92
+ * at (0, 0).
93
+ */
94
+ placeFile(sessionId: string, params: {
95
+ kind: 'image' | 'video' | 'audio' | 'prompt';
96
+ filePath: string;
97
+ title: string;
98
+ producedBy: string;
99
+ x?: number;
100
+ y?: number;
101
+ promptText?: string;
102
+ meta?: Record<string, unknown>;
103
+ description?: string;
104
+ /** Uuids of reference elements — when x/y are omitted, the new element is placed to the right of them. */
105
+ referenceUuids?: readonly string[];
106
+ }, cwd: string): Promise<AigcElement>;
107
+ /** Move an element to a new canvas position (persisted + pushed). */
108
+ updatePosition(sessionId: string, uuid: string, x: number, y: number): Promise<AigcElement>;
109
+ /**
110
+ * Delete one element and any edges referencing it. The media file on
111
+ * disk is NOT removed (the model may still reference its filePath);
112
+ * only the canvas registration is dropped.
113
+ */
114
+ deleteElement(sessionId: string, uuid: string): Promise<void>;
115
+ /** Wire edges from each input uuid to the target uuid (multi-to-one). */
116
+ wireEdges(sessionId: string, inputUuids: readonly string[], targetUuid: string): Promise<void>;
117
+ /** Remove one edge (source → target). Idempotent. */
118
+ unlink(sessionId: string, sourceUuid: string, targetUuid: string): Promise<void>;
119
+ /** Load the persisted state for one session (idempotent; used before sync reads). */
120
+ ensureHydrated(sessionId: string): Promise<void>;
121
+ /** Look up one element by uuid (throws if not found or wrong session). */
122
+ getElement(sessionId: string, uuid: string): AigcElement;
123
+ /** Look up one element by its filePath (throws if not found). */
124
+ getElementByPath(sessionId: string, filePath: string): AigcElement;
125
+ /** Snapshot of one session's full canvas state (elements + edges). */
126
+ snapshot(sessionId: string): AigcCanvasState;
127
+ /** Subscribe to canvas mutations for any session. Returns disposer. */
128
+ subscribe(listener: AigcCanvasListener): () => void;
129
+ /** Subscribe to canvas mutations for one specific session. */
130
+ subscribeSession(sessionId: string, listener: AigcCanvasListener): () => void;
131
+ }
132
+ /** Resolve the per-session canvas directory under the session cwd. */
133
+ export declare function canvasDirFor(cwd: string, sessionId: string): string;
134
+ /** Resolve the per-session canvas JSON path. */
135
+ export declare function canvasJsonPath(cwd: string, sessionId: string): string;
136
+ /** Resolve the per-session file path for one element (by uuid + kind). */
137
+ export declare function elementFilePath(cwd: string, sessionId: string, uuid: string, kind: AigcElementKind): string;
138
+ /**
139
+ * Build the service. The `resolveCwd` callback threads the live session cwd;
140
+ * `mediaSizeLimit` bounds how large a placed file may be.
141
+ */
142
+ export declare function createAigcCanvasService(resolveCwd: (sessionId: string) => string, mediaSizeLimit?: () => number): AigcCanvasService;