@whispering233/ai-editor-shared 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/constants/entity.d.ts +21 -0
  2. package/dist/constants/entity.d.ts.map +1 -0
  3. package/dist/constants/entity.js +35 -0
  4. package/dist/constants/entity.js.map +1 -0
  5. package/dist/constants/hook.d.ts +21 -0
  6. package/dist/constants/hook.d.ts.map +1 -0
  7. package/dist/constants/hook.js +35 -0
  8. package/dist/constants/hook.js.map +1 -0
  9. package/dist/constants/index.d.ts +5 -0
  10. package/dist/constants/index.d.ts.map +1 -0
  11. package/dist/constants/index.js +7 -0
  12. package/dist/constants/index.js.map +1 -0
  13. package/dist/constants/outline.d.ts +5 -0
  14. package/dist/constants/outline.d.ts.map +1 -0
  15. package/dist/constants/outline.js +7 -0
  16. package/dist/constants/outline.js.map +1 -0
  17. package/dist/constants/tool.d.ts +35 -0
  18. package/dist/constants/tool.d.ts.map +1 -0
  19. package/dist/constants/tool.js +82 -0
  20. package/dist/constants/tool.js.map +1 -0
  21. package/dist/index.d.ts +6 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +5 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/types/api.d.ts +728 -0
  26. package/dist/types/api.d.ts.map +1 -0
  27. package/dist/types/api.js +693 -0
  28. package/dist/types/api.js.map +1 -0
  29. package/dist/types/chat.d.ts +42 -0
  30. package/dist/types/chat.d.ts.map +1 -0
  31. package/dist/types/chat.js +4 -0
  32. package/dist/types/chat.js.map +1 -0
  33. package/dist/types/entity.d.ts +179 -0
  34. package/dist/types/entity.d.ts.map +1 -0
  35. package/dist/types/entity.js +6 -0
  36. package/dist/types/entity.js.map +1 -0
  37. package/dist/types/index.d.ts +7 -0
  38. package/dist/types/index.d.ts.map +1 -0
  39. package/dist/types/index.js +2 -0
  40. package/dist/types/index.js.map +1 -0
  41. package/dist/types/outline.d.ts +99 -0
  42. package/dist/types/outline.d.ts.map +1 -0
  43. package/dist/types/outline.js +6 -0
  44. package/dist/types/outline.js.map +1 -0
  45. package/dist/types/project.d.ts +33 -0
  46. package/dist/types/project.d.ts.map +1 -0
  47. package/dist/types/project.js +4 -0
  48. package/dist/types/project.js.map +1 -0
  49. package/dist/types/tool.d.ts +304 -0
  50. package/dist/types/tool.d.ts.map +1 -0
  51. package/dist/types/tool.js +296 -0
  52. package/dist/types/tool.js.map +1 -0
  53. package/dist/utils/format.d.ts +23 -0
  54. package/dist/utils/format.d.ts.map +1 -0
  55. package/dist/utils/format.js +57 -0
  56. package/dist/utils/format.js.map +1 -0
  57. package/dist/utils/id.d.ts +28 -0
  58. package/dist/utils/id.d.ts.map +1 -0
  59. package/dist/utils/id.js +48 -0
  60. package/dist/utils/id.js.map +1 -0
  61. package/dist/utils/index.d.ts +4 -0
  62. package/dist/utils/index.d.ts.map +1 -0
  63. package/dist/utils/index.js +6 -0
  64. package/dist/utils/index.js.map +1 -0
  65. package/dist/utils/mapping.d.ts +28 -0
  66. package/dist/utils/mapping.d.ts.map +1 -0
  67. package/dist/utils/mapping.js +223 -0
  68. package/dist/utils/mapping.js.map +1 -0
  69. package/package.json +42 -0
@@ -0,0 +1,693 @@
1
+ // API 契约 Zod schema(@whispering233/ai-editor-shared/types/api.ts,单一事实来源)
2
+ // 契约来源:doc/api/endpoints.md(全部端点 Req/Res 与错误码)、doc/api/tools.md(决策 15 agent 终止语义)、
3
+ // doc/database/schema.md(entity data 字段)、doc/database/hooks.md(hook data 字段)
4
+ // 命名约定(endpoints.md):请求体/查询参数 snake_case,响应体 camelCase;
5
+ // 嵌套 data 对象内部字段原样透传(snake_case,如 expected_payoff)。
6
+ // **校验执行边界(2026-08 修订)**:schema 定义于此,但**校验仅在服务端执行**——
7
+ // client 只消费推断出的类型与常量,不打包校验函数(避免 50KB 级依赖进浏览器包)。
8
+ // zod 版本:^4(注意 v4 API:z.record 必须两参、z.enum 接受 readonly 数组)
9
+ import { z } from "zod";
10
+ import { ENTITY_TYPES, RELATION_TYPES } from "../constants/entity.js";
11
+ import { HOOK_STATUSES, PAYOFF_TIMING } from "../constants/hook.js";
12
+ import { CONFLICT_LEVELS } from "../constants/outline.js";
13
+ // ============ 基础 schema ============
14
+ /** 实体类型(schema.md entities 表 CHECK 约束;与 ENTITY_TYPES 常量对齐) */
15
+ export const entityTypeSchema = z.enum(ENTITY_TYPES);
16
+ /** 项目语言(schema.md project.json 契约) */
17
+ export const projectLanguageSchema = z.enum(["zh", "en"]);
18
+ // ============ ErrorCode(单一来源:REST / SSE / 工具共用,endpoints.md「错误码」) ============
19
+ /**
20
+ * 错误码全量枚举
21
+ * 文档出处:endpoints.md 各端点错误响应;DELTA_CONFLICT 为 2026-08 修订废弃码
22
+ * (computeState 改为 skipped/conflicts 字段呈现,不再返回 409——保留枚举兼容历史引用);
23
+ * 末尾四个为 tools.md 决策 15/16 补充命名(文档未给具体码名,按语义命名,供 SSE error 事件使用)
24
+ */
25
+ export const ERROR_CODES = [
26
+ // ---- endpoints.md 提取(现行)----
27
+ "VALIDATION_ERROR", // 400 参数校验失败(entity/delta/outline 创建等)
28
+ "ENTITY_NOT_FOUND", // 404 实体不存在(详情/更新/删除/restore)
29
+ "RELATION_EXISTS", // 409 关系已存在
30
+ "RELATION_NOT_FOUND", // 404 关系不存在
31
+ "OUTLINE_NODE_NOT_FOUND", // 404 大纲节点不存在(compute / path / restore / purge)
32
+ "OUTLINE_ANCESTOR_DELETED", // 409 restore 时存在软删祖先(决策 12 修订)
33
+ "INVALID_PROJECT_PATH", // 400 create/open 路径校验失败(决策 17)
34
+ "PROPOSAL_STALE", // 409 确认时引用快照不一致(决策 14)
35
+ "PROPOSAL_NOT_FOUND", // 404 proposal_id 不存在(决策 14)
36
+ "PROPOSAL_PROJECT_MISMATCH", // 409 提案所属项目 ≠ 当前项目(决策 14 修订)
37
+ "SCHEMA_VERSION_MISMATCH", // 409 导入 zip 的 data.db user_version 与当前程序版本不匹配(E2;拒绝导入,不静默重建,release-review §二)
38
+ "PROJECT_VERSION_NEWER", // 409 open 时项目 data.db user_version 高于当前程序版本(E4;拒绝打开并提示升级程序,堵降级数据丢失,release-review §一)
39
+ // ---- 废弃(保留兼容)----
40
+ "DELTA_CONFLICT", // 已废弃(2026-08 修订:computeState 以 conflicts 字段替代 409)
41
+ // ---- tools.md 决策 15/16 补充命名(SSE error 事件用)----
42
+ "TOOL_RESULT_TOO_LARGE", // 工具结果 token 预算超限:截断/拒绝该工具结果(决策 15)
43
+ "AGENT_DISPATCH_ERROR", // 工具调度器缺陷(S7.3 防御:结果条数不符 / id 错位 / 调度器抛错),终止循环(决策 15)
44
+ "AGENT_INTERNAL_ERROR", // agent 循环内部未知异常(S7.3 防御路径——chatStream 契约不 throw,理论不可达)
45
+ "AGENT_MAX_ITERATIONS", // agent 循环超 8 轮上限,发 error 事件终止(决策 15)
46
+ "AGENT_TIMEOUT", // 单轮 120s 超时终止(决策 15)
47
+ "AGENT_TOKEN_BUDGET", // 上下文 token 预算超限终止(决策 15)
48
+ ];
49
+ /** ErrorCode 枚举 schema */
50
+ export const errorCodeSchema = z.enum(ERROR_CODES);
51
+ // ============ 通用响应包裹(endpoints.md「通用约定」) ============
52
+ /** 成功响应包裹:{ success: true, data: T } */
53
+ export function apiSuccessSchema(dataSchema) {
54
+ return z.object({
55
+ success: z.literal(true),
56
+ data: dataSchema,
57
+ });
58
+ }
59
+ /** 错误响应包裹:{ success: false, error: { code, message, fields? } } */
60
+ export const apiErrorSchema = z.object({
61
+ success: z.literal(false),
62
+ error: z.object({
63
+ code: errorCodeSchema,
64
+ message: z.string(),
65
+ fields: z.array(z.string()).optional(), // 校验失败时指出具体字段(VALIDATION_ERROR)
66
+ }),
67
+ });
68
+ // ============ 实体 data 字段 schema(endpoints.md 创建接口 + schema.md + hooks.md) ============
69
+ /**
70
+ * character 专属字段(schema.md:role/gender/age/personality[]/motivation/abilities[]/status/custom_fields)
71
+ * 注意:data 嵌套对象内部字段原样透传(snake_case,如 custom_fields),顶层契约字段才是 camelCase
72
+ */
73
+ export const characterDataSchema = z
74
+ .object({
75
+ role: z.string().optional(),
76
+ gender: z.string().optional(),
77
+ age: z.union([z.string(), z.number()]).optional(), // 年龄文本或数字皆可(schema.md 未定死类型)
78
+ personality: z.array(z.string()).optional(),
79
+ motivation: z.string().optional(),
80
+ abilities: z.array(z.string()).optional(),
81
+ status: z.string().optional(),
82
+ custom_fields: z.record(z.string(), z.unknown()).optional(),
83
+ })
84
+ .passthrough(); // 允许未知字段(创作工具,用户自定义字段自由)
85
+ /** setting 专属字段(schema.md:category/parent_id/description/rules[]/custom_fields) */
86
+ export const settingDataSchema = z
87
+ .object({
88
+ category: z.string().optional(),
89
+ parent_id: z.string().optional(),
90
+ description: z.string().optional(),
91
+ rules: z.array(z.string()).optional(),
92
+ custom_fields: z.record(z.string(), z.unknown()).optional(),
93
+ })
94
+ .passthrough();
95
+ /** location 专属字段(schema.md:type/parent_id/description/custom_fields) */
96
+ export const locationDataSchema = z
97
+ .object({
98
+ type: z.string().optional(),
99
+ parent_id: z.string().optional(),
100
+ description: z.string().optional(),
101
+ custom_fields: z.record(z.string(), z.unknown()).optional(),
102
+ })
103
+ .passthrough();
104
+ /** hook 专属字段(hooks.md 第 30-56 行:status/category/expected_payoff/payoff_timing/half_life/is_core/notes/expected_resolve_node_id) */
105
+ export const hookDataSchema = z
106
+ .object({
107
+ status: z.enum(HOOK_STATUSES).optional(),
108
+ category: z.string().optional(), // 自由填(HOOK_CATEGORIES 仅为前端建议值)
109
+ expected_payoff: z.string().optional(),
110
+ payoff_timing: z.enum(PAYOFF_TIMING).optional(),
111
+ half_life: z.number().int().positive().optional(), // 章数;缺省映射见决策 21
112
+ is_core: z.boolean().optional(),
113
+ notes: z.string().optional(),
114
+ expected_resolve_node_id: z.string().nullable().optional(), // 决策 21 ready_to_resolve 依据
115
+ })
116
+ .passthrough();
117
+ /**
118
+ * 各类型 data schema 注册表(服务端按实体 type 选用精确 schema 校验)
119
+ * 创建/更新请求中的 data 本体使用宽松 record(entityCreateReqSchema),
120
+ * 精确校验在服务端 route 层按 type 调用对应 schema
121
+ */
122
+ export const ENTITY_DATA_SCHEMAS = {
123
+ character: characterDataSchema,
124
+ setting: settingDataSchema,
125
+ location: locationDataSchema,
126
+ hook: hookDataSchema,
127
+ };
128
+ // ============ project 端点(endpoints.md「项目管理」) ============
129
+ /** ProjectConfig 响应(GET /api/v1/project/config;与 types/project.ts 的 ProjectConfig 对齐) */
130
+ export const projectConfigSchema = z.object({
131
+ id: z.string(),
132
+ name: z.string(),
133
+ language: projectLanguageSchema,
134
+ prompt: z.string(), // 项目级提示词(决策 7)
135
+ schemaVersion: z.number().int(), // 决策 13
136
+ currentPosition: z.string().nullable(), // 「当前位置」节点 id;null = 未设置(决策 21)
137
+ createdAt: z.string(),
138
+ updatedAt: z.string(),
139
+ });
140
+ // POST /api/v1/project/create
141
+ export const projectCreateReqSchema = z
142
+ .object({
143
+ path: z.string(), // 项目目录绝对路径(决策 17 校验)
144
+ config: z
145
+ .object({
146
+ name: z.string().optional(),
147
+ language: projectLanguageSchema.optional(),
148
+ prompt: z.string().optional(),
149
+ })
150
+ .optional(),
151
+ })
152
+ .strict();
153
+ export const projectCreateResSchema = z.object({
154
+ id: z.string(),
155
+ path: z.string(),
156
+ created: z.literal(true),
157
+ });
158
+ // POST /api/v1/project/open
159
+ export const projectOpenReqSchema = z
160
+ .object({
161
+ path: z.string(), // 必须包含 project.json
162
+ })
163
+ .strict();
164
+ export const projectOpenResSchema = z.object({
165
+ id: z.string(),
166
+ name: z.string(),
167
+ language: projectLanguageSchema,
168
+ config: projectConfigSchema,
169
+ });
170
+ // POST /api/v1/project/close
171
+ export const projectCloseResSchema = z.object({
172
+ saved: z.literal(true),
173
+ });
174
+ // GET /api/v1/project/list(书架模式 S1.5:列出创作根 books/ 下的书,供 Dashboard 书架展示)
175
+ export const projectListResSchema = z.object({
176
+ /** 创作根(server 启动参数 projectRoot) */
177
+ rootPath: z.string(),
178
+ /** books/ 下含 project.json 的书,按 updatedAt 倒序(最近更新在前) */
179
+ books: z.array(z.object({
180
+ /** 目录名(书名) */
181
+ name: z.string(),
182
+ /** 书目录绝对路径(books/<name>) */
183
+ path: z.string(),
184
+ /** project.json 的 updated_at(ISO 8601,应用层写入) */
185
+ updatedAt: z.string(),
186
+ })),
187
+ });
188
+ // GET /api/v1/project/config
189
+ export const projectConfigResSchema = projectConfigSchema;
190
+ // PUT /api/v1/project/config
191
+ export const projectConfigUpdateReqSchema = z
192
+ .object({
193
+ name: z.string().optional(),
194
+ language: projectLanguageSchema.optional(),
195
+ prompt: z.string().optional(),
196
+ current_position: z.string().nullable().optional(), // 须指向存在的非软删大纲节点(服务端校验)
197
+ })
198
+ .strict();
199
+ export const projectConfigUpdateResSchema = z.object({
200
+ updated: z.literal(true),
201
+ });
202
+ // ============ 导出/导入端点(E1/E2:release-review §二,产品承诺「数据主权归用户」) ============
203
+ /**
204
+ * 导出 zip 内固定三文件名(E1:GET /api/v1/project/export 的 zip 条目名与数据文件
205
+ * 原名一致——import 侧按此固定名校验,缺失即坏包)
206
+ */
207
+ export const PROJECT_EXPORT_FILE_NAMES = ["project.json", "outline.json", "data.db"];
208
+ /**
209
+ * GET /api/v1/project/export(E1 实现,E2 依赖):
210
+ * - **响应为二进制 zip(application/zip),非 JSON 包裹**——endpoints.md「成功响应
211
+ * {success,data}」通用约定的显式例外;Content-Disposition: attachment;
212
+ * filename*=UTF-8''<书名>.zip(RFC 5987)
213
+ * - zip 内三文件:project.json + outline.json + data.db(导出前 wal_checkpoint(TRUNCATE)
214
+ * 保证 data.db 主文件完整快照;决策 17 key 存用户级配置,天然不入包)
215
+ * - 错误:无当前项目 → 409 NO_PROJECT_OPEN(服务端补充码,与 /config 一致);
216
+ * 三文件缺失任一 → 500 INTERNAL_ERROR(打开的项目三文件必然齐全,缺失即损坏)
217
+ * - 二进制响应不走 Zod parse——契约以本注释 + PROJECT_EXPORT_FILE_NAMES 常量表达
218
+ */
219
+ // POST /api/v1/project/import(E2 实现;E1 已落契约)
220
+ // - 请求:multipart/form-data 文件上传——field "file"(zip 备份包)+ field "name"(书名,
221
+ // 必填;禁路径分隔符/纯点/控制字符,与 client 新建项目同规则)——目标目录为
222
+ // 服务端决定的 创作根/books/<name>/(客户端不可指定路径,防越权)
223
+ // - 服务端流程(E2):解压到临时目录 → 校验(条目白名单 = PROJECT_EXPORT_FILE_NAMES
224
+ // 三文件名 + project.json/outline.json 顶层契约 + data.db user_version 匹配)→
225
+ // 原子搬入新书目录(新建,不覆盖现有项目)→ 返回 200
226
+ // - 错误码:坏包/缺文件/未知条目/契约不符 → 400 VALIDATION_ERROR;data.db user_version
227
+ // 与当前程序版本不匹配 → 409 SCHEMA_VERSION_MISMATCH(拒绝导入,不静默重建);
228
+ // 目标书名已存在 → 409 PROJECT_ALREADY_EXISTS(服务端补充码,与 create 同语义)
229
+ export const projectImportResSchema = z.object({
230
+ imported: z.literal(true),
231
+ id: z.string(), // 导入项目的 project_id(沿用 zip 内 project.json 的 id,数据原样恢复)
232
+ path: z.string(), // 新书目录绝对路径(创作根/books/<name>/)
233
+ name: z.string(), // 书名(即新书目录名 books/<name>/;project.json 内部 name 保持原样)
234
+ });
235
+ // ============ entity 端点(endpoints.md「实体 CRUD」) ============
236
+ /**
237
+ * 关系(GET /api/v1/relation depth=1 项;与 types/entity.ts RelationRecord 对齐)
238
+ * 定义于此处供 entity 详情响应(relations: RelationSummary[],形状同 RelationRecord,
239
+ * endpoints.md L187 未单独列字段)与 relation 查询共用,避免同结构两处定义漂移
240
+ */
241
+ export const relationRecordSchema = z.object({
242
+ id: z.string(),
243
+ sourceType: z.string(),
244
+ sourceId: z.string(),
245
+ sourceName: z.string().optional(), // 联表填充
246
+ targetType: z.string(),
247
+ targetId: z.string(),
248
+ targetName: z.string().optional(),
249
+ relationType: z.string(),
250
+ metadata: z.record(z.string(), z.unknown()).optional(),
251
+ createdAt: z.string(),
252
+ });
253
+ /** EntitySummary(GET /api/v1/entity/:type 列表项;与 types/entity.ts 对齐) */
254
+ export const entitySummarySchema = z.object({
255
+ id: z.string(),
256
+ type: entityTypeSchema,
257
+ name: z.string(),
258
+ summary: z.record(z.string(), z.unknown()), // 从 data 提取的关键摘要字段
259
+ createdAt: z.string(),
260
+ updatedAt: z.string(),
261
+ });
262
+ // GET /api/v1/entity/:type(Query:snake_case)
263
+ export const entityListQuerySchema = z.object({
264
+ q: z.string().optional(), // 模糊匹配 name
265
+ offset: z.coerce.number().int().min(0).default(0),
266
+ limit: z.coerce.number().int().min(1).max(200).default(50),
267
+ sort: z.enum(["name", "created_at", "updated_at"]).optional(),
268
+ order: z.enum(["asc", "desc"]).optional(),
269
+ });
270
+ export const entityListResSchema = z.object({
271
+ items: z.array(entitySummarySchema),
272
+ total: z.number().int(),
273
+ offset: z.number().int(),
274
+ limit: z.number().int(),
275
+ });
276
+ // GET /api/v1/entity/:type/:id(详情:含紧邻 1 跳关系与 Delta 计数)
277
+ export const entityDetailResSchema = z.object({
278
+ id: z.string(),
279
+ type: entityTypeSchema,
280
+ name: z.string(),
281
+ data: z.record(z.string(), z.unknown()), // 完整字段(嵌套 snake_case 原样透传)
282
+ relations: z.array(relationRecordSchema), // RelationSummary(形状同 RelationRecord,endpoints.md L187)
283
+ deltaCount: z.number().int(),
284
+ createdAt: z.string(),
285
+ updatedAt: z.string(),
286
+ });
287
+ // POST /api/v1/entity/:type(Req:name 必填 1-100;data 宽松 record,按 type 的精确 schema 见 ENTITY_DATA_SCHEMAS)
288
+ export const entityCreateReqSchema = z
289
+ .object({
290
+ name: z.string().min(1).max(100),
291
+ data: z.record(z.string(), z.unknown()).optional(),
292
+ })
293
+ .strict();
294
+ export const entityCreateResSchema = z.object({
295
+ id: z.string(),
296
+ type: entityTypeSchema,
297
+ name: z.string(),
298
+ data: z.record(z.string(), z.unknown()),
299
+ createdAt: z.string(),
300
+ });
301
+ // PUT /api/v1/entity/:type/:id(partial update:仅合并传入的 data 字段)
302
+ export const entityUpdateReqSchema = z
303
+ .object({
304
+ name: z.string().min(1).max(100).optional(),
305
+ data: z.record(z.string(), z.unknown()).optional(),
306
+ })
307
+ .strict();
308
+ export const entityUpdateResSchema = z.object({
309
+ id: z.string(),
310
+ updated: z.literal(true),
311
+ });
312
+ // DELETE /api/v1/entity/:type/:id(软删 + 级联计数,决策 12)
313
+ export const entityDeleteResSchema = z.object({
314
+ deleted: z.literal(true),
315
+ cascaded: z.object({
316
+ relations: z.number().int(),
317
+ deltas: z.number().int(),
318
+ }),
319
+ });
320
+ // ============ relation 端点(endpoints.md「关系管理」) ============
321
+ // relationRecordSchema 定义于 entity 区(entity 详情 relations 与 relation 查询共用,避免重复定义)
322
+ /** 路径结构(depth>=2,endpoints.md) */
323
+ export const relationPathSchema = z.object({
324
+ nodes: z.array(z.object({ type: z.string(), id: z.string(), name: z.string() })),
325
+ edges: z.array(z.object({ from: z.string(), to: z.string(), relationType: z.string() })),
326
+ });
327
+ // GET /api/v1/relation(Query:depth 必填 1|2|3)
328
+ export const relationQuerySchema = z.object({
329
+ source_type: z.string().optional(),
330
+ source_id: z.string().optional(),
331
+ target_type: z.string().optional(),
332
+ target_id: z.string().optional(),
333
+ relation_type: z.string().optional(),
334
+ depth: z.coerce.number().int().min(1).max(3), // 1=紧邻, 2=k跳, 3=全量遍历
335
+ });
336
+ export const relationQueryResSchema = z.object({
337
+ relations: z.array(relationRecordSchema),
338
+ paths: z.array(relationPathSchema).optional(), // depth>=2 时返回
339
+ });
340
+ // POST /api/v1/relation(relation_type 限定 schema.md 预定义 16 种)
341
+ export const relationCreateReqSchema = z
342
+ .object({
343
+ source_type: z.string(),
344
+ source_id: z.string(),
345
+ target_type: z.string(),
346
+ target_id: z.string(),
347
+ relation_type: z.enum(RELATION_TYPES),
348
+ metadata: z.record(z.string(), z.unknown()).optional(),
349
+ })
350
+ .strict();
351
+ export const relationCreateResSchema = z.object({
352
+ id: z.string(),
353
+ relation: z.object({
354
+ sourceType: z.string(),
355
+ sourceId: z.string(),
356
+ targetType: z.string(),
357
+ targetId: z.string(),
358
+ relationType: z.string(),
359
+ }),
360
+ });
361
+ // DELETE /api/v1/relation/:id(物理删除,不进回收站,决策 12 修订)
362
+ export const relationDeleteResSchema = z.object({
363
+ deleted: z.literal(true),
364
+ });
365
+ // ============ delta 端点(endpoints.md「Delta 变更追踪」) ============
366
+ /** 变更操作类型(2026-08 修订语义:set/update/add/remove,见 endpoints.md) */
367
+ export const deltaOpSchema = z.enum(["set", "update", "add", "remove"]);
368
+ /** 单条属性变更 */
369
+ export const deltaChangeSchema = z.object({
370
+ field: z.string(),
371
+ op: deltaOpSchema,
372
+ from: z.union([z.string(), z.number()]).nullable().optional(), // op=update 时服务端要求必填
373
+ to: z.union([z.string(), z.number()]).nullable().optional(), // op=set/update 时服务端要求必填;add/remove 用 value
374
+ value: z.union([z.string(), z.number()]).optional(), // op=add/remove 时使用
375
+ });
376
+ /** DeltaRecord(响应;与 types/entity.ts 对齐) */
377
+ export const deltaRecordSchema = z.object({
378
+ id: z.string(),
379
+ nodeId: z.string(),
380
+ targetType: z.string(),
381
+ targetId: z.string(),
382
+ targetName: z.string().optional(), // 联表填充
383
+ changes: z.array(deltaChangeSchema),
384
+ description: z.string(),
385
+ order: z.number().int(), // 服务端生成,全局单调递增
386
+ createdAt: z.string(),
387
+ });
388
+ // POST /api/v1/delta(Req:无 order 入参——服务端生成)
389
+ export const deltaCreateReqSchema = z
390
+ .object({
391
+ node_id: z.string(),
392
+ target_type: z.string(),
393
+ target_id: z.string(),
394
+ changes: z.array(deltaChangeSchema).min(1),
395
+ description: z.string(),
396
+ })
397
+ .strict();
398
+ export const deltaCreateResSchema = z.object({
399
+ id: z.string(),
400
+ applied: deltaRecordSchema,
401
+ });
402
+ // GET /api/v1/delta/node/:nodeId
403
+ export const deltaByNodeResSchema = z.object({
404
+ nodeId: z.string(),
405
+ deltas: z.array(deltaRecordSchema),
406
+ });
407
+ // POST /api/v1/delta/compute(决策 9/19:只沿大纲树父链累积)
408
+ export const deltaComputeReqSchema = z
409
+ .object({
410
+ target_type: z.string(),
411
+ target_id: z.string(),
412
+ at_node_id: z.string(), // 服务端自动计算根 → at_node 的树路径
413
+ })
414
+ .strict();
415
+ export const deltaComputeResSchema = z.object({
416
+ targetType: z.string(),
417
+ targetId: z.string(),
418
+ atNodeId: z.string(),
419
+ state: z.record(z.string(), z.unknown()), // 初始 data + 路径上所有 Delta 累积
420
+ appliedDeltas: z.array(z.object({
421
+ nodeId: z.string(),
422
+ description: z.string(),
423
+ changes: z.array(z.unknown()),
424
+ skipped: z
425
+ .array(z.object({
426
+ index: z.number().int(), // 在 changes 数组中的下标
427
+ field: z.string(),
428
+ expected: z.unknown(),
429
+ actual: z.unknown(),
430
+ }))
431
+ .optional(), // 决策 9 修订:op=update 且当前值 ≠ from 时跳过该 change
432
+ })),
433
+ conflicts: z.array(z.object({
434
+ deltaId: z.string(),
435
+ field: z.string(),
436
+ expected: z.unknown(),
437
+ actual: z.unknown(),
438
+ })),
439
+ });
440
+ // ============ outline 端点(endpoints.md「大纲操作」,严格三层决策 19) ============
441
+ /**
442
+ * 大纲节点 data 字段 schema(决策 23,麦基《故事》字段集,schema.md outline.json「节点结构化信息」节):
443
+ * scene——goal/conflict_levels/value_from/value_to;chapter——reversal/climax_scene;
444
+ * volume——climax_scene/inciting_scene。
445
+ * 宽松语义与 ENTITY_DATA_SCHEMAS 一致:
446
+ * - `.passthrough()` 允许未知字段(创作工具,用户自定义字段自由,未知字段原样保留透传)
447
+ * - 引用字段(climax_scene/inciting_scene)仅类型校验(字符串),不校验存在性/范围(决策 23:MVP 宽松)
448
+ * 请求体 data 本体使用宽松 record(outlineCreateReqSchema),精确校验在服务端 route 层按层级选用
449
+ */
450
+ export const sceneDataSchema = z
451
+ .object({
452
+ goal: z.string().max(1000).optional(), // 场景目标/欲望(麦基 Scene)
453
+ conflict_levels: z.array(z.enum(CONFLICT_LEVELS)).optional(), // 冲突三层次多选
454
+ value_from: z.string().max(200).optional(), // 开场价值
455
+ value_to: z.string().max(200).optional(), // 收场价值(「No scene that doesn't turn」)
456
+ })
457
+ .passthrough();
458
+ export const chapterDataSchema = z
459
+ .object({
460
+ reversal: z.string().max(1000).optional(), // 章末反转(单文本)
461
+ climax_scene: z.string().optional(), // 章高潮场景引用(宽松:仅字符串,MVP 不校验引用范围)
462
+ })
463
+ .passthrough();
464
+ export const volumeDataSchema = z
465
+ .object({
466
+ climax_scene: z.string().optional(), // 幕高潮场景引用(宽松)
467
+ inciting_scene: z.string().optional(), // 激励事件落位(宽松)
468
+ })
469
+ .passthrough();
470
+ /**
471
+ * 各层级 data schema 注册表(服务端按节点 type 选用精确 schema 校验,
472
+ * 与 ENTITY_DATA_SCHEMAS 同构;type 三选一 scene/chapter/volume,root 无 data)
473
+ */
474
+ export const OUTLINE_NODE_DATA_SCHEMAS = {
475
+ scene: sceneDataSchema,
476
+ chapter: chapterDataSchema,
477
+ volume: volumeDataSchema,
478
+ };
479
+ /** 大纲节点(递归 schema;type 三选一 + children 可选,比 T1.1 的判别联合宽松——
480
+ * 严格三层(卷→章→场景、scene 无 children)的类型约束由 types/outline.ts 承担,服务端负责层级校验,
481
+ * 故此处不做 z.ZodType<OutlineNode> 标注(宽松 infer 无法赋给判别联合))
482
+ */
483
+ export const outlineNodeSchema = z.lazy(() => z.object({
484
+ id: z.string(),
485
+ type: z.enum(["volume", "chapter", "scene"]),
486
+ title: z.string(),
487
+ summary: z.string().optional(),
488
+ data: z.record(z.string(), z.unknown()).optional(), // 节点结构化信息(决策 23;内部字段原样透传)
489
+ updatedAt: z.string(), // 节点版本戳(决策 19)
490
+ deleted: z.boolean().optional(), // 软删标记(决策 12,管理视图)
491
+ deletedAt: z.string().optional(),
492
+ children: z.array(outlineNodeSchema).optional(),
493
+ metadata: z
494
+ .object({
495
+ hookCount: z.number().int().optional(),
496
+ charCount: z.number().int().optional(),
497
+ deltaCount: z.number().int().optional(),
498
+ })
499
+ .optional(), // 仅 with_metadata=true 时返回
500
+ }));
501
+ /** 完整大纲树(GET /api/v1/outline) */
502
+ export const outlineTreeSchema = z.object({
503
+ id: z.literal("root"),
504
+ type: z.literal("root"),
505
+ schemaVersion: z.number().int(), // outline.json 顶层 schema_version(决策 13)
506
+ children: z.array(outlineNodeSchema),
507
+ });
508
+ // GET /api/v1/outline(Query)
509
+ export const outlineGetQuerySchema = z.object({
510
+ // 显式字符串布尔:z.coerce.boolean() 会把 "false" 解析为 true(反向问题),
511
+ // 改为枚举 + transform:显式传 false → false;不传 → undefined(默认关闭 metadata 统计)
512
+ with_metadata: z
513
+ .enum(["true", "false"])
514
+ .transform((v) => v === "true")
515
+ .optional(), // 跨 outline.json × data.db 联查统计
516
+ });
517
+ // POST /api/v1/outline(parent_id 必填,无默认值,决策 19)
518
+ export const outlineCreateReqSchema = z
519
+ .object({
520
+ type: z.enum(["volume", "chapter", "scene"]),
521
+ title: z.string().min(1).max(200),
522
+ parent_id: z.string(), // volume→root;chapter→volume 或 root;scene→必须 chapter
523
+ summary: z.string().optional(),
524
+ data: z.record(z.string(), z.unknown()).optional(), // 节点结构化信息(决策 23,宽松 record,按层级 schema 精校验)
525
+ })
526
+ .strict();
527
+ export const outlineCreateResSchema = z.object({
528
+ id: z.string(),
529
+ type: z.enum(["volume", "chapter", "scene"]),
530
+ title: z.string(),
531
+ parentId: z.string().nullable(),
532
+ updatedAt: z.string(),
533
+ });
534
+ // PUT /api/v1/outline/:nodeId
535
+ export const outlineUpdateReqSchema = z
536
+ .object({
537
+ title: z.string().min(1).max(200).optional(),
538
+ summary: z.string().optional(),
539
+ data: z.record(z.string(), z.unknown()).optional(), // 部分合并(决策 23;按层级 schema 精校验)
540
+ })
541
+ .strict();
542
+ export const outlineUpdateResSchema = z.object({
543
+ updated: z.literal(true),
544
+ });
545
+ // PUT /api/v1/outline/:nodeId/move(拖拽重排,严格三层约束同创建)
546
+ export const outlineMoveReqSchema = z
547
+ .object({
548
+ parent_id: z.string(),
549
+ order: z.number().int().min(0), // 兄弟节点中的位置(0-based)
550
+ })
551
+ .strict();
552
+ export const outlineMoveResSchema = z.object({
553
+ moved: z.literal(true),
554
+ previousParentId: z.string(),
555
+ newParentId: z.string(),
556
+ });
557
+ // DELETE /api/v1/outline/:nodeId(软删 + 递归级联,决策 12)
558
+ export const outlineDeleteResSchema = z.object({
559
+ deleted: z.literal(true),
560
+ cascaded: z.object({
561
+ children: z.number().int(), // 递归软删的子节点数
562
+ relations: z.number().int(),
563
+ deltas: z.number().int(),
564
+ }),
565
+ });
566
+ // GET /api/v1/outline/:nodeId/path
567
+ export const outlinePathResSchema = z.object({
568
+ nodeId: z.string(),
569
+ path: z.array(z.string()), // 如 ["root", "vol-1", "ch-3", "sc-15"]
570
+ });
571
+ // ============ trash 端点(endpoints.md「回收站」,决策 12) ============
572
+ // GET /api/v1/trash(deletedAt 为 camelCase——响应体约定)
573
+ export const trashListResSchema = z.object({
574
+ entities: z.array(z.object({ id: z.string(), type: z.string(), name: z.string(), deletedAt: z.string() })),
575
+ nodes: z.array(z.object({ id: z.string(), type: z.string(), title: z.string(), deletedAt: z.string() })),
576
+ });
577
+ // POST /api/v1/trash/entity/:type/:id/restore(级联还原,决策 12 修订)
578
+ export const trashRestoreEntityResSchema = z.object({
579
+ restored: z.literal(true),
580
+ restoredRelations: z.number().int(),
581
+ restoredDeltas: z.number().int(),
582
+ });
583
+ // POST /api/v1/trash/outline/:nodeId/restore(祖先链校验:软删祖先返回 409 OUTLINE_ANCESTOR_DELETED)
584
+ export const trashRestoreNodeResSchema = z.object({
585
+ restored: z.literal(true),
586
+ restoredChildren: z.number().int(),
587
+ restoredRelations: z.number().int(),
588
+ restoredDeltas: z.number().int(),
589
+ });
590
+ // DELETE /api/v1/trash/entity/:type/:id 与 /trash/outline/:nodeId(物理清除)
591
+ export const trashPurgeResSchema = z.object({
592
+ purged: z.literal(true),
593
+ });
594
+ // ============ chat 端点(endpoints.md「AI 对话」,决策 18 持久化) ============
595
+ // POST /api/v1/chat(POST + SSE;消息落 chat_messages 表)
596
+ export const chatSendReqSchema = z
597
+ .object({
598
+ message: z.string(),
599
+ session_id: z.string().optional(), // 不传则创建新会话
600
+ context: z
601
+ .object({
602
+ focus_entity_type: entityTypeSchema.optional(),
603
+ focus_entity_id: z.string().optional(),
604
+ focus_node_id: z.string().optional(),
605
+ })
606
+ .optional(),
607
+ })
608
+ .strict();
609
+ // GET /api/v1/chat/sessions(按最后活动时间倒序,仅当前项目会话)
610
+ export const chatSessionSummarySchema = z.object({
611
+ id: z.string(),
612
+ lastMessage: z.string(), // 截断摘要
613
+ messageCount: z.number().int(),
614
+ createdAt: z.string(),
615
+ updatedAt: z.string(), // 最后活动时间
616
+ });
617
+ export const chatSessionsResSchema = z.object({
618
+ sessions: z.array(chatSessionSummarySchema),
619
+ });
620
+ // GET /api/v1/chat/sessions/:id/messages(按 created_at 升序;tool 消息经 toolCallId 关联 assistant.tool_calls[].id)
621
+ export const chatMessagesResSchema = z.object({
622
+ sessionId: z.string(),
623
+ messages: z.array(z.object({
624
+ id: z.string(),
625
+ role: z.enum(["user", "assistant", "tool"]),
626
+ content: z.string().nullable().optional(),
627
+ toolCalls: z.array(z.unknown()).optional(), // assistant 消息的工具调用数组
628
+ toolCallId: z.string().nullable().optional(), // tool 消息关联的调用 id(决策 18 修订)
629
+ createdAt: z.string(),
630
+ })),
631
+ });
632
+ // ============ proposal 端点(endpoints.md「提案确认」,决策 14) ============
633
+ // POST /api/v1/proposal/:proposalId/confirm(409 PROPOSAL_STALE / 404 PROPOSAL_NOT_FOUND / 409 PROPOSAL_PROJECT_MISMATCH)
634
+ export const proposalConfirmResSchema = z.object({
635
+ confirmed: z.literal(true),
636
+ result: z.unknown(), // 执行结果(如新创建的 entity id)
637
+ });
638
+ // POST /api/v1/proposal/:proposalId/reject
639
+ export const proposalRejectResSchema = z.object({
640
+ rejected: z.literal(true),
641
+ });
642
+ // ============ settings 端点(endpoints.md「系统设置」,决策 17) ============
643
+ // GET /api/v1/settings/llm(key 不回传明文)
644
+ export const settingsLlmGetResSchema = z.object({
645
+ model: z.string(), // 默认 "deepseek-v4-flash"
646
+ apiKeySet: z.boolean(),
647
+ apiKeyMasked: z.string().optional(), // 掩码展示(utils/format.ts maskApiKey)
648
+ });
649
+ // PUT /api/v1/settings/llm(写入 ~/.ai-editor/config.json,绝不入项目文件,决策 17)
650
+ export const settingsLlmPutReqSchema = z
651
+ .object({
652
+ model: z.string().optional(),
653
+ api_key: z.string().optional(), // 空字符串 = 清除已保存 key
654
+ })
655
+ .strict();
656
+ export const settingsLlmPutResSchema = z.object({
657
+ saved: z.literal(true),
658
+ });
659
+ // ============ SSE 事件(endpoints.md chat 端点事件流,第 738-765 行) ============
660
+ /** 心跳 ping(每 15-30s,决策 20):空 payload */
661
+ export const ssePingEventSchema = z.object({});
662
+ /** tool_call:AI 调用了工具 */
663
+ export const sseToolCallEventSchema = z.object({
664
+ tool: z.string(),
665
+ args: z.record(z.string(), z.unknown()),
666
+ id: z.string(), // call_ 前缀(决策 18 成对重组依据)
667
+ });
668
+ /** tool_result:工具执行结果 */
669
+ export const sseToolResultEventSchema = z.object({
670
+ tool: z.string(),
671
+ result: z.unknown(),
672
+ id: z.string(), // 与 tool_call 的 id 成对
673
+ });
674
+ /** text:AI 文本回复片段 */
675
+ export const sseTextEventSchema = z.object({
676
+ delta: z.string(),
677
+ });
678
+ /** proposal:AI 发出提案(完整预览仅经此事件推送 GUI,tool_result 不含预览,2026-08 修订) */
679
+ export const sseProposalEventSchema = z.object({
680
+ proposal_id: z.string(), // prop_ 前缀(决策 14)
681
+ type: z.string(), // 提案对应工具名(如 "propose_create_entity")
682
+ preview: z.unknown(),
683
+ });
684
+ /** done:对话轮次结束 */
685
+ export const sseDoneEventSchema = z.object({
686
+ session_id: z.string(), // sess_ 前缀
687
+ });
688
+ /** error:流终止(客户端收到即停止解析) */
689
+ export const sseErrorEventSchema = z.object({
690
+ code: errorCodeSchema,
691
+ message: z.string(),
692
+ });
693
+ //# sourceMappingURL=api.js.map