@xiaohhhh1/canvas-agent 0.2.2 → 0.3.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 (42) hide show
  1. package/README.md +9 -1
  2. package/agent-instructions.md +21 -0
  3. package/dist/agent/claude.d.ts +3 -0
  4. package/dist/agent/claude.js +46 -0
  5. package/dist/agent/codex-client.d.ts +73 -0
  6. package/dist/agent/codex-client.js +438 -0
  7. package/dist/agent/codex-history.d.ts +25 -0
  8. package/dist/agent/codex-history.js +405 -0
  9. package/dist/agent/codex-protocol.d.ts +208 -0
  10. package/dist/{agents.d.ts → agent/codex.d.ts} +34 -31
  11. package/dist/agent/codex.js +210 -0
  12. package/dist/agent/types.d.ts +14 -0
  13. package/dist/agent/types.js +1 -0
  14. package/dist/canvas/operations.d.ts +13 -0
  15. package/dist/canvas/operations.js +161 -0
  16. package/dist/{schemas.d.ts → canvas/schemas.d.ts} +21 -20
  17. package/dist/{schemas.js → canvas/schemas.js} +1 -0
  18. package/dist/{canvas-session.d.ts → canvas/session.d.ts} +21 -1
  19. package/dist/canvas/session.js +256 -0
  20. package/dist/{tools.d.ts → canvas/tools.d.ts} +13 -8
  21. package/dist/{tools.js → canvas/tools.js} +5 -0
  22. package/dist/{types.d.ts → canvas/types.d.ts} +1 -10
  23. package/dist/canvas/types.js +1 -0
  24. package/dist/config.d.ts +5 -1
  25. package/dist/config.js +22 -4
  26. package/dist/index.js +2 -2
  27. package/dist/server/http.d.ts +2 -0
  28. package/dist/{http-server.js → server/http.js} +100 -16
  29. package/dist/server/mcp.d.ts +2 -0
  30. package/dist/{mcp-server.js → server/mcp.js} +5 -2
  31. package/dist/utils/date.d.ts +2 -0
  32. package/dist/utils/date.js +7 -0
  33. package/dist/utils/logger.d.ts +17 -0
  34. package/dist/utils/logger.js +83 -0
  35. package/dist/utils/value.d.ts +5 -0
  36. package/dist/utils/value.js +8 -0
  37. package/package.json +7 -4
  38. package/dist/agents.js +0 -557
  39. package/dist/canvas-session.js +0 -391
  40. package/dist/http-server.d.ts +0 -1
  41. package/dist/mcp-server.d.ts +0 -1
  42. /package/dist/{types.js → agent/codex-protocol.js} +0 -0
@@ -1,30 +1,36 @@
1
- import type { AgentAttachment, AgentEmit } from "./types.js";
2
- type Json = Record<string, unknown>;
1
+ import type { AgentAttachment, AgentEmit, AgentPermissionMode } from "./types.js";
3
2
  type CodexRunOptions = {
4
3
  threadId?: string;
5
4
  cwd?: string;
5
+ permissionMode?: AgentPermissionMode;
6
6
  appEmit?: AgentEmit;
7
7
  onStart?: () => void;
8
8
  onThread?: (threadId: string) => void;
9
9
  onTurn?: (turnId: string) => void;
10
10
  onFinish?: () => void;
11
11
  };
12
- type AgentHistoryMessage = {
13
- id: string;
14
- role: "user" | "assistant" | "tool" | "error";
15
- title?: string;
16
- text: string;
17
- detail?: unknown;
18
- streamId?: string;
19
- };
20
- export declare function withAgentPrompt(prompt: string): string;
12
+ export { summarizeCodexThread } from "./codex-history.js";
13
+ /** 将 Codex turn 加入串行队列并等待执行完成。 */
21
14
  export declare function runCodexTurn(prompt: string, emit: AgentEmit, attachments?: AgentAttachment[], options?: CodexRunOptions): Promise<void>;
22
- export declare function interruptCodexTurn(threadId?: string): boolean;
23
- export declare function startCodexThread(emit: AgentEmit, cwd?: string): Promise<Json>;
24
- export declare function resumeCodexThread(emit: AgentEmit, threadId: string, cwd?: string): Promise<{
25
- thread: Json;
26
- messages: AgentHistoryMessage[];
15
+ /** 中断当前线程正在执行的 Codex turn。 */
16
+ export declare function interruptCodexTurn(threadId?: string): Promise<boolean>;
17
+ /** 回复当前 app-server 的待处理权限请求。 */
18
+ export declare function resolveCodexApproval(requestId: string, decision: string): Promise<boolean>;
19
+ /** 创建新的 Codex 线程并记录当前线程 ID。 */
20
+ export declare function startCodexThread(emit: AgentEmit, cwd?: string, permissionMode?: AgentPermissionMode): Promise<import("./codex-protocol.js").CodexThread>;
21
+ /** 恢复指定 Codex 线程并返回聊天历史。 */
22
+ export declare function resumeCodexThread(emit: AgentEmit, threadId: string, cwd?: string, permissionMode?: AgentPermissionMode): Promise<{
23
+ thread: import("./codex-protocol.js").CodexThread;
24
+ messages: {
25
+ id: string;
26
+ role: "user" | "assistant" | "tool" | "error";
27
+ title?: string;
28
+ text: string;
29
+ detail?: unknown;
30
+ streamId?: string;
31
+ }[];
27
32
  }>;
33
+ /** 查询当前工作空间中的 Codex 线程。 */
28
34
  export declare function listCodexThreads(emit: AgentEmit, options: {
29
35
  cwd: string;
30
36
  searchTerm?: string;
@@ -45,6 +51,7 @@ export declare function listCodexThreads(emit: AgentEmit, options: {
45
51
  nextCursor: {} | null;
46
52
  backwardsCursor: {} | null;
47
53
  }>;
54
+ /** 读取指定 Codex 线程及其聊天历史。 */
48
55
  export declare function readCodexThread(emit: AgentEmit, threadId: string, cwd?: string): Promise<{
49
56
  thread: {
50
57
  id: string;
@@ -58,22 +65,18 @@ export declare function readCodexThread(emit: AgentEmit, threadId: string, cwd?:
58
65
  createdAt: number;
59
66
  updatedAt: number;
60
67
  };
61
- messages: AgentHistoryMessage[];
68
+ messages: {
69
+ id: string;
70
+ role: "user" | "assistant" | "tool" | "error";
71
+ title?: string;
72
+ text: string;
73
+ detail?: unknown;
74
+ streamId?: string;
75
+ }[];
62
76
  }>;
77
+ /** 确认指定 Codex 线程属于当前工作空间。 */
63
78
  export declare function verifyCodexThreadWorkspace(emit: AgentEmit, threadId: string, cwd: string): Promise<void>;
79
+ /** 归档指定 Codex 线程。 */
64
80
  export declare function archiveCodexThread(emit: AgentEmit, threadId: string, cwd?: string): Promise<void>;
65
- export declare function runClaudeTurn(prompt: string, emit: AgentEmit): void;
81
+ /** 判断线程异常是否允许自动新建线程后重试。 */
66
82
  export declare function isRecoverableThreadError(error: unknown): boolean;
67
- export declare function summarizeCodexThread(thread: unknown): {
68
- id: string;
69
- sessionId: string;
70
- preview: string;
71
- name: string | null;
72
- cwd: string;
73
- status: string;
74
- source: unknown;
75
- threadSource: unknown;
76
- createdAt: number;
77
- updatedAt: number;
78
- };
79
- export {};
@@ -0,0 +1,210 @@
1
+ import fs from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { logger } from "../utils/logger.js";
5
+ import { errorMessage, field } from "../utils/value.js";
6
+ import { CodexAppClient } from "./codex-client.js";
7
+ import { summarizeCodexThread, threadMessages } from "./codex-history.js";
8
+ let codexQueue = Promise.resolve();
9
+ let codexApp = null;
10
+ let codexAppStart = null;
11
+ let codexThreadId = "";
12
+ const unmaterializedThreadIds = new Set();
13
+ export { summarizeCodexThread } from "./codex-history.js";
14
+ /** 将 Codex turn 加入串行队列并等待执行完成。 */
15
+ export async function runCodexTurn(prompt, emit, attachments = [], options = {}) {
16
+ if (!prompt.trim())
17
+ return;
18
+ codexQueue = codexQueue.catch(() => undefined).then(() => runCodexTurnNow(prompt, emit, attachments, options));
19
+ await codexQueue;
20
+ }
21
+ /** 中断当前线程正在执行的 Codex turn。 */
22
+ export async function interruptCodexTurn(threadId) {
23
+ if (!codexApp || (threadId && threadId !== codexThreadId))
24
+ return false;
25
+ return await codexApp.interruptCurrentTurn();
26
+ }
27
+ /** 回复当前 app-server 的待处理权限请求。 */
28
+ export async function resolveCodexApproval(requestId, decision) {
29
+ return Boolean(codexApp?.resolveApproval(requestId, decision));
30
+ }
31
+ /** 创建新的 Codex 线程并记录当前线程 ID。 */
32
+ export async function startCodexThread(emit, cwd, permissionMode = "request") {
33
+ const app = await getCodexApp(emit);
34
+ const thread = await app.startThread(cwd, permissionMode);
35
+ codexThreadId = String(field(thread, "id") || "");
36
+ if (codexThreadId)
37
+ unmaterializedThreadIds.add(codexThreadId);
38
+ return thread;
39
+ }
40
+ /** 恢复指定 Codex 线程并返回聊天历史。 */
41
+ export async function resumeCodexThread(emit, threadId, cwd, permissionMode = "request") {
42
+ const app = await getCodexApp(emit);
43
+ await loadCodexThread(emit, threadId, cwd, false);
44
+ const thread = await app.resumeThread(threadId, cwd, permissionMode);
45
+ assertThreadWorkspace(thread, cwd);
46
+ codexThreadId = String(field(thread, "id") || threadId);
47
+ const historyThread = await loadCodexThread(emit, codexThreadId, cwd, true);
48
+ return { thread, messages: threadMessages(historyThread, app.planUpdates(threadId)) };
49
+ }
50
+ /** 查询当前工作空间中的 Codex 线程。 */
51
+ export async function listCodexThreads(emit, options) {
52
+ const app = await getCodexApp(emit);
53
+ const result = await app.listThreads({
54
+ limit: options.limit || 40,
55
+ sortKey: "updated_at",
56
+ sortDirection: "desc",
57
+ sourceKinds: ["cli", "vscode", "appServer", "exec"],
58
+ cwd: options.cwd,
59
+ ...(options.searchTerm ? { searchTerm: options.searchTerm } : {}),
60
+ });
61
+ const data = Array.isArray(field(result, "data")) ? field(result, "data").map(summarizeCodexThread).filter((thread) => threadInWorkspace(thread, options.cwd)) : [];
62
+ return { data, nextCursor: field(result, "nextCursor") || null, backwardsCursor: field(result, "backwardsCursor") || null };
63
+ }
64
+ /** 读取指定 Codex 线程及其聊天历史。 */
65
+ export async function readCodexThread(emit, threadId, cwd) {
66
+ const app = await getCodexApp(emit);
67
+ let thread;
68
+ try {
69
+ thread = await loadCodexThread(emit, threadId, cwd, !unmaterializedThreadIds.has(threadId));
70
+ }
71
+ catch (error) {
72
+ if (!/not materialized yet.*includeTurns/i.test(errorMessage(error)))
73
+ throw error;
74
+ unmaterializedThreadIds.add(threadId);
75
+ thread = await loadCodexThread(emit, threadId, cwd, false);
76
+ }
77
+ return { thread: summarizeCodexThread(thread), messages: threadMessages(thread, app.planUpdates(threadId)) };
78
+ }
79
+ /** 确认指定 Codex 线程属于当前工作空间。 */
80
+ export async function verifyCodexThreadWorkspace(emit, threadId, cwd) {
81
+ await loadCodexThread(emit, threadId, cwd, false);
82
+ }
83
+ /** 归档指定 Codex 线程。 */
84
+ export async function archiveCodexThread(emit, threadId, cwd) {
85
+ const app = await getCodexApp(emit);
86
+ await loadCodexThread(emit, threadId, cwd, false);
87
+ await app.archiveThread(threadId);
88
+ app.clearPlanUpdates(threadId);
89
+ unmaterializedThreadIds.delete(threadId);
90
+ }
91
+ /** 判断线程异常是否允许自动新建线程后重试。 */
92
+ export function isRecoverableThreadError(error) {
93
+ return /thread not loaded|no rollout found/i.test(errorMessage(error));
94
+ }
95
+ /** 执行一次 Codex turn,并负责附件临时文件和线程恢复。 */
96
+ async function runCodexTurnNow(prompt, emit, attachments, options) {
97
+ let files = [];
98
+ try {
99
+ options.onStart?.();
100
+ files = await writeAttachmentFiles(attachments);
101
+ const app = await getCodexApp(options.appEmit || emit);
102
+ let threadId = await ensureCodexThread(app, options, emit);
103
+ options.onThread?.(threadId);
104
+ unmaterializedThreadIds.delete(threadId);
105
+ try {
106
+ await app.startTurn(threadId, prompt, files, options.permissionMode || "request", options.onTurn);
107
+ }
108
+ catch (error) {
109
+ if (!isRecoverableThreadError(error))
110
+ throw error;
111
+ emit("agent_log", { text: `Codex thread unavailable, starting a new thread: ${errorMessage(error)}` });
112
+ codexThreadId = "";
113
+ threadId = await ensureCodexThread(app, { cwd: options.cwd }, emit);
114
+ options.onThread?.(threadId);
115
+ unmaterializedThreadIds.delete(threadId);
116
+ await app.startTurn(threadId, prompt, files, options.permissionMode || "request", options.onTurn);
117
+ }
118
+ }
119
+ catch (error) {
120
+ logger.error("Codex turn failed", error);
121
+ emit("agent_error", { message: errorMessage(error) });
122
+ }
123
+ finally {
124
+ options.onFinish?.();
125
+ await Promise.all(files.map((file) => fs.unlink(file).catch(() => undefined)));
126
+ }
127
+ }
128
+ /** 恢复请求线程或创建新的 Codex 线程。 */
129
+ async function ensureCodexThread(app, options, emit) {
130
+ if (options.threadId) {
131
+ if (options.threadId === codexThreadId)
132
+ return codexThreadId;
133
+ try {
134
+ const result = await app.readThread(options.threadId, false);
135
+ assertThreadWorkspace(field(result, "thread") || {}, options.cwd);
136
+ const thread = await app.resumeThread(options.threadId, options.cwd, options.permissionMode || "request");
137
+ assertThreadWorkspace(thread, options.cwd);
138
+ codexThreadId = String(field(thread, "id") || options.threadId);
139
+ return codexThreadId;
140
+ }
141
+ catch (error) {
142
+ if (!isRecoverableThreadError(error))
143
+ throw error;
144
+ emit("agent_log", { text: `Codex thread unavailable, starting a new thread: ${errorMessage(error)}` });
145
+ }
146
+ }
147
+ if (!codexThreadId) {
148
+ const thread = await app.startThread(options.cwd, options.permissionMode || "request");
149
+ codexThreadId = String(field(thread, "id") || "");
150
+ if (codexThreadId)
151
+ unmaterializedThreadIds.add(codexThreadId);
152
+ }
153
+ return codexThreadId;
154
+ }
155
+ /** 从 app-server 读取线程并校验工作空间。 */
156
+ async function loadCodexThread(emit, threadId, cwd, includeTurns) {
157
+ const app = await getCodexApp(emit);
158
+ const result = await app.readThread(threadId, includeTurns);
159
+ const thread = field(result, "thread") || {};
160
+ assertThreadWorkspace(thread, cwd);
161
+ return thread;
162
+ }
163
+ /** 获取已启动的 Codex app-server 客户端。 */
164
+ async function getCodexApp(emit) {
165
+ if (codexApp)
166
+ return codexApp;
167
+ codexAppStart ||= CodexAppClient.start(emit, () => {
168
+ codexApp = null;
169
+ codexThreadId = "";
170
+ });
171
+ try {
172
+ codexApp = await codexAppStart;
173
+ return codexApp;
174
+ }
175
+ finally {
176
+ codexAppStart = null;
177
+ }
178
+ }
179
+ /** 校验线程是否属于指定工作空间。 */
180
+ function assertThreadWorkspace(thread, cwd) {
181
+ if (!cwd || threadInWorkspace(thread, cwd))
182
+ return;
183
+ throw new Error("该 Codex 会话不属于当前画布工作空间");
184
+ }
185
+ /** 判断线程工作目录是否与当前工作空间一致。 */
186
+ function threadInWorkspace(thread, cwd) {
187
+ const threadCwd = String(field(thread, "cwd") || "");
188
+ return Boolean(threadCwd && path.resolve(threadCwd) === path.resolve(cwd));
189
+ }
190
+ /** 将图片附件写入临时文件供 Codex 读取。 */
191
+ async function writeAttachmentFiles(attachments) {
192
+ return await Promise.all(attachments.filter((item) => item.dataUrl?.startsWith("data:image/")).map(writeAttachmentFile));
193
+ }
194
+ /** 将单个 Data URL 图片附件写入临时文件。 */
195
+ async function writeAttachmentFile(item) {
196
+ const [, meta = "", data = ""] = item.dataUrl?.match(/^data:([^;]+);base64,(.+)$/) || [];
197
+ if (!data)
198
+ throw new Error(`图片附件无效:${item.name || "未命名图片"}`);
199
+ const file = path.join(os.tmpdir(), `infinite-canvas-${Date.now()}-${Math.random().toString(16).slice(2)}.${imageExt(meta || item.type)}`);
200
+ await fs.writeFile(file, Buffer.from(data, "base64"));
201
+ return file;
202
+ }
203
+ /** 根据图片 MIME 类型返回临时文件扩展名。 */
204
+ function imageExt(type = "") {
205
+ if (type.includes("png"))
206
+ return "png";
207
+ if (type.includes("webp"))
208
+ return "webp";
209
+ return "jpg";
210
+ }
@@ -0,0 +1,14 @@
1
+ /** Agent 向网页广播事件的函数类型。 */
2
+ export type AgentEmit = (type: string, payload: unknown) => void;
3
+ /** 用户随当前 Agent 消息上传的附件。 */
4
+ export type AgentAttachment = {
5
+ id?: string;
6
+ name?: string;
7
+ type?: string;
8
+ size?: number;
9
+ width?: number;
10
+ height?: number;
11
+ dataUrl?: string;
12
+ };
13
+ /** Codex 文件、命令和网络权限模式。 */
14
+ export type AgentPermissionMode = "request" | "automatic" | "full";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ import type { ToolName } from "./schemas.js";
2
+ import type { CanvasSnapshot } from "./types.js";
3
+ export type CanvasToolRequest = {
4
+ name: "canvas_apply_ops";
5
+ input: Record<string, unknown>;
6
+ };
7
+ /** 将上层画布工具调用转换为前端可执行的批量操作。 */
8
+ export declare function buildCanvasToolRequest(name: ToolName, input: Record<string, unknown>, state: CanvasSnapshot | null): CanvasToolRequest;
9
+ /** 按最大边限制计算附件图片节点尺寸,并保持原始比例。 */
10
+ export declare function fitAttachmentNodeSize(width: number, height: number): {
11
+ width: number;
12
+ height: number;
13
+ };
@@ -0,0 +1,161 @@
1
+ import crypto from "node:crypto";
2
+ import { nextCanvasX } from "./tools.js";
3
+ /** 将上层画布工具调用转换为前端可执行的批量操作。 */
4
+ export function buildCanvasToolRequest(name, input, state) {
5
+ if (name === "canvas_apply_ops")
6
+ return { name, input };
7
+ if (name === "canvas_create_node") {
8
+ const data = input;
9
+ return applyOps([{ type: "add_node", nodeType: data.nodeType, title: data.title, position: { x: data.x ?? nextCanvasX(state), y: data.y ?? 0 }, width: data.width, height: data.height, metadata: data.metadata }]);
10
+ }
11
+ if (name === "canvas_create_text_node") {
12
+ const data = input;
13
+ return applyOps([textNodeOp(data, data.x ?? nextCanvasX(state), data.y ?? 0)]);
14
+ }
15
+ if (name === "canvas_create_text_nodes") {
16
+ const data = input;
17
+ const x = Number(data.x ?? nextCanvasX(state));
18
+ const y = Number(data.y ?? 0);
19
+ const gap = Number(data.gap ?? 40);
20
+ return applyOps(data.items.map((item, index) => textNodeOp(item, item.x ?? (data.direction === "row" ? x + index * (340 + gap) : x), item.y ?? (data.direction === "row" ? y : y + index * (240 + gap)))));
21
+ }
22
+ if (name === "canvas_create_image_prompt_flow")
23
+ return applyOps(generationFlowOps({ ...input, mode: "image" }, state));
24
+ if (name === "canvas_create_config_node") {
25
+ const x = Number(input.x ?? nextCanvasX(state));
26
+ const y = Number(input.y ?? 0);
27
+ const configId = `config-${crypto.randomUUID()}`;
28
+ const mode = generationMode(input.mode);
29
+ const prompt = String(input.prompt || "");
30
+ return applyOps([configNodeOp(configId, input, x, y), ...(input.autoRun ? [runGenerationOp(configId, mode, prompt)] : [])]);
31
+ }
32
+ if (name === "canvas_create_generation_flow")
33
+ return applyOps(generationFlowOps(input, state));
34
+ if (name === "canvas_generate_text" || name === "canvas_generate_image" || name === "canvas_generate_video" || name === "canvas_generate_audio") {
35
+ return applyOps(generationFlowOps({ ...input, mode: name.replace("canvas_generate_", ""), autoRun: true }, state));
36
+ }
37
+ if (name === "canvas_update_node") {
38
+ const data = input;
39
+ return applyOps([{ type: "update_node", id: data.id, patch: data.patch, metadata: data.metadata }]);
40
+ }
41
+ if (name === "canvas_update_node_text") {
42
+ const data = input;
43
+ return applyOps([{ type: "update_node", id: data.id, patch: { ...(data.title ? { title: data.title } : {}) }, metadata: { content: data.text, status: "success" } }]);
44
+ }
45
+ if (name === "canvas_move_nodes") {
46
+ const data = input;
47
+ return applyOps(data.items.map((item) => {
48
+ const current = findNode(state, item.id);
49
+ return { type: "update_node", id: item.id, patch: { position: { x: item.x ?? ((current?.position.x || 0) + (item.dx || 0)), y: item.y ?? ((current?.position.y || 0) + (item.dy || 0)) } } };
50
+ }));
51
+ }
52
+ if (name === "canvas_resize_node") {
53
+ const data = input;
54
+ return applyOps([{ type: "update_node", id: data.id, patch: { width: data.width, height: data.height }, metadata: data.freeResize === undefined ? undefined : { freeResize: data.freeResize } }]);
55
+ }
56
+ if (name === "canvas_delete_nodes")
57
+ return applyOps([{ type: "delete_node", ids: input.ids }]);
58
+ if (name === "canvas_connect_nodes") {
59
+ const data = input;
60
+ return applyOps(data.connections.map((connection) => ({ type: "connect_nodes", ...connection })));
61
+ }
62
+ if (name === "canvas_select_nodes")
63
+ return applyOps([{ type: "select_nodes", ids: input.ids }]);
64
+ if (name === "canvas_set_viewport")
65
+ return applyOps([{ type: "set_viewport", viewport: input.viewport }]);
66
+ if (name === "canvas_run_generation") {
67
+ const data = input;
68
+ return applyOps([runGenerationOp(data.nodeId, generationMode(data.mode), data.prompt)]);
69
+ }
70
+ throw new Error(`未知工具:${name}`);
71
+ }
72
+ /** 按最大边限制计算附件图片节点尺寸,并保持原始比例。 */
73
+ export function fitAttachmentNodeSize(width, height) {
74
+ const scale = Math.min(1, 640 / width, 640 / height);
75
+ return { width: width * scale, height: height * scale };
76
+ }
77
+ /** 创建统一的批量画布操作请求。 */
78
+ function applyOps(ops) {
79
+ return { name: "canvas_apply_ops", input: { ops } };
80
+ }
81
+ /** 创建文本节点操作。 */
82
+ function textNodeOp(input, x, y) {
83
+ return { type: "add_node", id: input.id, nodeType: "text", title: input.title, position: { x, y }, width: input.width, height: input.height, metadata: { content: input.text || "", status: "success", fontSize: 14 } };
84
+ }
85
+ /** 创建生成配置节点操作。 */
86
+ function configNodeOp(id, input, x, y) {
87
+ const mode = generationMode(input.mode);
88
+ const prompt = String(input.prompt || "");
89
+ return {
90
+ type: "add_node",
91
+ id,
92
+ nodeType: "config",
93
+ title: String(input.title || generationTitle(mode)),
94
+ position: { x, y },
95
+ width: typeof input.width === "number" ? input.width : undefined,
96
+ height: typeof input.height === "number" ? input.height : undefined,
97
+ metadata: cleanRecord({
98
+ generationMode: mode,
99
+ composerContent: prompt,
100
+ prompt,
101
+ status: "idle",
102
+ model: input.model,
103
+ size: input.size,
104
+ quality: input.quality,
105
+ count: input.count,
106
+ seconds: input.seconds,
107
+ vquality: input.vquality,
108
+ generateAudio: input.generateAudio,
109
+ watermark: input.watermark,
110
+ audioVoice: input.audioVoice,
111
+ audioFormat: input.audioFormat,
112
+ audioSpeed: input.audioSpeed,
113
+ audioInstructions: input.audioInstructions,
114
+ }),
115
+ };
116
+ }
117
+ /** 创建包含提示词、配置节点和引用连线的生成流程。 */
118
+ function generationFlowOps(input, state) {
119
+ const mode = generationMode(input.mode);
120
+ const prompt = String(input.prompt || "");
121
+ const x = Number(input.x ?? nextCanvasX(state));
122
+ const y = Number(input.y ?? 0);
123
+ const textId = `text-${crypto.randomUUID()}`;
124
+ const configId = `config-${crypto.randomUUID()}`;
125
+ const referenceNodeIds = Array.isArray(input.referenceNodeIds) ? input.referenceNodeIds.filter((id) => typeof id === "string") : [];
126
+ const tokens = [`@[node:${textId}]`, ...referenceNodeIds.map((id) => `@[node:${id}]`)];
127
+ return [
128
+ textNodeOp({ id: textId, text: prompt, title: String(input.title || "提示词") }, x, y),
129
+ configNodeOp(configId, { ...input, prompt: tokens.join("\n") }, x + 420, y),
130
+ { type: "connect_nodes", fromNodeId: textId, toNodeId: configId },
131
+ ...referenceNodeIds.map((fromNodeId) => ({ type: "connect_nodes", fromNodeId, toNodeId: configId })),
132
+ { type: "select_nodes", ids: [configId] },
133
+ ...(input.autoRun ? [runGenerationOp(configId, mode, tokens.join("\n"))] : []),
134
+ ];
135
+ }
136
+ /** 创建触发节点生成的画布操作。 */
137
+ function runGenerationOp(nodeId, mode, prompt) {
138
+ return { type: "run_generation", nodeId, mode, prompt };
139
+ }
140
+ /** 将未知生成模式归一为画布支持的模式。 */
141
+ function generationMode(value) {
142
+ return value === "text" || value === "video" || value === "audio" ? value : "image";
143
+ }
144
+ /** 获取生成模式对应的默认节点标题。 */
145
+ function generationTitle(mode) {
146
+ if (mode === "text")
147
+ return "文本生成";
148
+ if (mode === "video")
149
+ return "视频生成";
150
+ if (mode === "audio")
151
+ return "音频生成";
152
+ return "图片生成";
153
+ }
154
+ /** 按节点 ID 查找当前画布节点。 */
155
+ function findNode(state, id) {
156
+ return (state?.nodes || []).find((node) => node.id === id);
157
+ }
158
+ /** 移除对象中未设置的生成参数。 */
159
+ function cleanRecord(value) {
160
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined && item !== ""));
161
+ }
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ /** Canvas Agent 对外提供的工具名称。 */
2
3
  export declare const toolNames: readonly ["site_navigate", "canvas_list_projects", "canvas_get_state", "canvas_get_selection", "canvas_export_snapshot", "canvas_export_media", "canvas_apply_ops", "canvas_create_node", "canvas_create_attachment_nodes", "canvas_create_text_node", "canvas_create_text_nodes", "canvas_create_config_node", "canvas_create_image_prompt_flow", "canvas_create_generation_flow", "canvas_generate_text", "canvas_generate_image", "canvas_generate_video", "canvas_generate_audio", "canvas_update_node", "canvas_update_node_text", "canvas_move_nodes", "canvas_resize_node", "canvas_delete_nodes", "canvas_connect_nodes", "canvas_select_nodes", "canvas_set_viewport", "canvas_run_generation", "generation_get_status", "workbench_image_get_config", "workbench_image_generate", "workbench_video_get_config", "workbench_video_generate", "prompts_search", "assets_list", "assets_add"];
3
4
  export type ToolName = (typeof toolNames)[number];
4
5
  export declare const canvasOpSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
@@ -545,18 +546,18 @@ export declare const toolInputSchemas: {
545
546
  height: z.ZodOptional<z.ZodNumber>;
546
547
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
547
548
  }, "strip", z.ZodTypeAny, {
548
- nodeType: "image" | "text" | "config" | "video" | "audio";
549
+ nodeType: "text" | "image" | "config" | "video" | "audio";
550
+ title?: string | undefined;
549
551
  x?: number | undefined;
550
552
  y?: number | undefined;
551
- title?: string | undefined;
552
553
  width?: number | undefined;
553
554
  height?: number | undefined;
554
555
  metadata?: Record<string, unknown> | undefined;
555
556
  }, {
556
- nodeType: "image" | "text" | "config" | "video" | "audio";
557
+ nodeType: "text" | "image" | "config" | "video" | "audio";
558
+ title?: string | undefined;
557
559
  x?: number | undefined;
558
560
  y?: number | undefined;
559
- title?: string | undefined;
560
561
  width?: number | undefined;
561
562
  height?: number | undefined;
562
563
  metadata?: Record<string, unknown> | undefined;
@@ -589,16 +590,16 @@ export declare const toolInputSchemas: {
589
590
  height: z.ZodOptional<z.ZodNumber>;
590
591
  }, "strip", z.ZodTypeAny, {
591
592
  text?: string | undefined;
593
+ title?: string | undefined;
592
594
  x?: number | undefined;
593
595
  y?: number | undefined;
594
- title?: string | undefined;
595
596
  width?: number | undefined;
596
597
  height?: number | undefined;
597
598
  }, {
598
599
  text?: string | undefined;
600
+ title?: string | undefined;
599
601
  x?: number | undefined;
600
602
  y?: number | undefined;
601
- title?: string | undefined;
602
603
  width?: number | undefined;
603
604
  height?: number | undefined;
604
605
  }>;
@@ -612,16 +613,16 @@ export declare const toolInputSchemas: {
612
613
  height: z.ZodOptional<z.ZodNumber>;
613
614
  }, "strip", z.ZodTypeAny, {
614
615
  text: string;
616
+ title?: string | undefined;
615
617
  x?: number | undefined;
616
618
  y?: number | undefined;
617
- title?: string | undefined;
618
619
  width?: number | undefined;
619
620
  height?: number | undefined;
620
621
  }, {
621
622
  text: string;
623
+ title?: string | undefined;
622
624
  x?: number | undefined;
623
625
  y?: number | undefined;
624
- title?: string | undefined;
625
626
  width?: number | undefined;
626
627
  height?: number | undefined;
627
628
  }>, "many">;
@@ -632,9 +633,9 @@ export declare const toolInputSchemas: {
632
633
  }, "strip", z.ZodTypeAny, {
633
634
  items: {
634
635
  text: string;
636
+ title?: string | undefined;
635
637
  x?: number | undefined;
636
638
  y?: number | undefined;
637
- title?: string | undefined;
638
639
  width?: number | undefined;
639
640
  height?: number | undefined;
640
641
  }[];
@@ -645,9 +646,9 @@ export declare const toolInputSchemas: {
645
646
  }, {
646
647
  items: {
647
648
  text: string;
649
+ title?: string | undefined;
648
650
  x?: number | undefined;
649
651
  y?: number | undefined;
650
- title?: string | undefined;
651
652
  width?: number | undefined;
652
653
  height?: number | undefined;
653
654
  }[];
@@ -853,11 +854,11 @@ export declare const toolInputSchemas: {
853
854
  prompt: z.ZodOptional<z.ZodString>;
854
855
  }, "strip", z.ZodTypeAny, {
855
856
  nodeId: string;
856
- mode?: "image" | "text" | "video" | "audio" | undefined;
857
+ mode?: "text" | "image" | "video" | "audio" | undefined;
857
858
  prompt?: string | undefined;
858
859
  }, {
859
860
  nodeId: string;
860
- mode?: "image" | "text" | "video" | "audio" | undefined;
861
+ mode?: "text" | "image" | "video" | "audio" | undefined;
861
862
  prompt?: string | undefined;
862
863
  }>;
863
864
  generation_get_status: z.ZodObject<{
@@ -866,15 +867,15 @@ export declare const toolInputSchemas: {
866
867
  nodeIds: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
867
868
  limit: z.ZodOptional<z.ZodNumber>;
868
869
  }, "strip", z.ZodTypeAny, {
869
- limit?: number | undefined;
870
870
  nodeIds?: string[] | undefined;
871
871
  scope?: "image" | "video" | "all" | "canvas" | undefined;
872
872
  taskId?: string | undefined;
873
- }, {
874
873
  limit?: number | undefined;
874
+ }, {
875
875
  nodeIds?: string[] | undefined;
876
876
  scope?: "image" | "video" | "all" | "canvas" | undefined;
877
877
  taskId?: string | undefined;
878
+ limit?: number | undefined;
878
879
  }>;
879
880
  workbench_image_get_config: z.ZodObject<{}, "passthrough", z.ZodTypeAny, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">, z.objectInputType<{}, z.ZodTypeAny, "passthrough">>;
880
881
  workbench_image_generate: z.ZodObject<{
@@ -886,17 +887,17 @@ export declare const toolInputSchemas: {
886
887
  run: z.ZodOptional<z.ZodBoolean>;
887
888
  }, "strip", z.ZodTypeAny, {
888
889
  prompt: string;
890
+ count?: number | undefined;
889
891
  model?: string | undefined;
890
892
  size?: string | undefined;
891
893
  quality?: string | undefined;
892
- count?: number | undefined;
893
894
  run?: boolean | undefined;
894
895
  }, {
895
896
  prompt: string;
897
+ count?: number | undefined;
896
898
  model?: string | undefined;
897
899
  size?: string | undefined;
898
900
  quality?: string | undefined;
899
- count?: number | undefined;
900
901
  run?: boolean | undefined;
901
902
  }>;
902
903
  workbench_video_get_config: z.ZodObject<{}, "passthrough", z.ZodTypeAny, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">, z.objectInputType<{}, z.ZodTypeAny, "passthrough">>;
@@ -953,15 +954,15 @@ export declare const toolInputSchemas: {
953
954
  page: z.ZodOptional<z.ZodNumber>;
954
955
  pageSize: z.ZodOptional<z.ZodNumber>;
955
956
  }, "strip", z.ZodTypeAny, {
957
+ kind?: "text" | "image" | "video" | "all" | undefined;
956
958
  keyword?: string | undefined;
957
959
  page?: number | undefined;
958
960
  pageSize?: number | undefined;
959
- kind?: "image" | "text" | "video" | "all" | undefined;
960
961
  }, {
962
+ kind?: "text" | "image" | "video" | "all" | undefined;
961
963
  keyword?: string | undefined;
962
964
  page?: number | undefined;
963
965
  pageSize?: number | undefined;
964
- kind?: "image" | "text" | "video" | "all" | undefined;
965
966
  }>;
966
967
  assets_add: z.ZodObject<{
967
968
  kind: z.ZodEnum<["text", "image"]>;
@@ -973,7 +974,7 @@ export declare const toolInputSchemas: {
973
974
  note: z.ZodOptional<z.ZodString>;
974
975
  }, "strip", z.ZodTypeAny, {
975
976
  title: string;
976
- kind: "image" | "text";
977
+ kind: "text" | "image";
977
978
  source?: string | undefined;
978
979
  content?: string | undefined;
979
980
  tags?: string[] | undefined;
@@ -981,7 +982,7 @@ export declare const toolInputSchemas: {
981
982
  note?: string | undefined;
982
983
  }, {
983
984
  title: string;
984
- kind: "image" | "text";
985
+ kind: "text" | "image";
985
986
  source?: string | undefined;
986
987
  content?: string | undefined;
987
988
  tags?: string[] | undefined;