@whispering233/ai-editor-shared 0.0.21 → 0.0.29
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/dist/constants/backup.d.ts +5 -5
- package/dist/constants/backup.d.ts.map +1 -1
- package/dist/constants/backup.js +5 -7
- package/dist/constants/backup.js.map +1 -1
- package/dist/constants/entity.d.ts +7 -7
- package/dist/constants/entity.d.ts.map +1 -1
- package/dist/constants/entity.js +7 -8
- package/dist/constants/entity.js.map +1 -1
- package/dist/constants/hook.d.ts +4 -4
- package/dist/constants/hook.d.ts.map +1 -1
- package/dist/constants/hook.js +4 -5
- package/dist/constants/hook.js.map +1 -1
- package/dist/constants/index.js +1 -1
- package/dist/constants/index.js.map +1 -1
- package/dist/constants/outline.d.ts +1 -1
- package/dist/constants/outline.d.ts.map +1 -1
- package/dist/constants/outline.js +2 -3
- package/dist/constants/outline.js.map +1 -1
- package/dist/constants/tool.d.ts +6 -6
- package/dist/constants/tool.d.ts.map +1 -1
- package/dist/constants/tool.js +9 -10
- package/dist/constants/tool.js.map +1 -1
- package/dist/types/api.d.ts +69 -45
- package/dist/types/api.d.ts.map +1 -1
- package/dist/types/api.js +173 -162
- package/dist/types/api.js.map +1 -1
- package/dist/types/chat.d.ts +7 -7
- package/dist/types/chat.d.ts.map +1 -1
- package/dist/types/chat.js +0 -1
- package/dist/types/chat.js.map +1 -1
- package/dist/types/entity.d.ts +31 -31
- package/dist/types/entity.d.ts.map +1 -1
- package/dist/types/entity.js +1 -2
- package/dist/types/entity.js.map +1 -1
- package/dist/types/outline.d.ts +25 -25
- package/dist/types/outline.d.ts.map +1 -1
- package/dist/types/outline.js +2 -3
- package/dist/types/outline.js.map +1 -1
- package/dist/types/project.d.ts +18 -18
- package/dist/types/project.d.ts.map +1 -1
- package/dist/types/project.js +0 -1
- package/dist/types/project.js.map +1 -1
- package/dist/types/tool.d.ts +15 -15
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/types/tool.js +35 -39
- package/dist/types/tool.js.map +1 -1
- package/dist/utils/backup.d.ts +14 -14
- package/dist/utils/backup.d.ts.map +1 -1
- package/dist/utils/backup.js +28 -31
- package/dist/utils/backup.js.map +1 -1
- package/dist/utils/format.d.ts +3 -3
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +3 -4
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/id.d.ts +6 -6
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +7 -9
- package/dist/utils/id.js.map +1 -1
- package/dist/utils/index.js +2 -2
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/mapping.d.ts +4 -4
- package/dist/utils/mapping.d.ts.map +1 -1
- package/dist/utils/mapping.js +20 -20
- package/dist/utils/mapping.js.map +1 -1
- package/dist/utils/reference-file.d.ts +4 -4
- package/dist/utils/reference-file.d.ts.map +1 -1
- package/dist/utils/reference-file.js +14 -16
- package/dist/utils/reference-file.js.map +1 -1
- package/package.json +1 -1
package/dist/types/api.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
// API
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// 命名约定(endpoints.md):请求体/查询参数 snake_case,响应体 camelCase;
|
|
5
|
-
// 嵌套 data 对象内部字段原样透传(snake_case,如 expected_payoff)。
|
|
1
|
+
// API Zod schema(@whispering233/ai-editor-shared/types/api.ts,单一事实来源)
|
|
2
|
+
// 命名约定():请求体/查询参数 snake_case,响应体 camelCase;
|
|
3
|
+
// 嵌套 data 对象内部字段原样透传(snake_case,如 expected_payoff)。
|
|
6
4
|
// **校验执行边界(2026-08 修订)**:schema 定义于此,但**校验仅在服务端执行**——
|
|
7
|
-
//
|
|
5
|
+
// client 只消费推断出的类型与常量,不打包校验函数(避免 50KB 级依赖进浏览器包)。
|
|
8
6
|
// zod 版本:^4(注意 v4 API:z.record 必须两参、z.enum 接受 readonly 数组)
|
|
9
7
|
import { z } from "zod";
|
|
10
8
|
import { ENTITY_TYPES, RELATION_TYPES } from "../constants/entity.js";
|
|
@@ -12,47 +10,47 @@ import { HOOK_STATUSES, PAYOFF_TIMING } from "../constants/hook.js";
|
|
|
12
10
|
import { CONFLICT_LEVELS } from "../constants/outline.js";
|
|
13
11
|
import { BACKUP_FREQUENCIES } from "../constants/backup.js";
|
|
14
12
|
// ============ 基础 schema ============
|
|
15
|
-
/** 实体类型(
|
|
13
|
+
/** 实体类型( entities 表 CHECK 约束;与 ENTITY_TYPES 常量对齐) */
|
|
16
14
|
export const entityTypeSchema = z.enum(ENTITY_TYPES);
|
|
17
|
-
/** 项目语言(
|
|
15
|
+
/** 项目语言( project.json ) */
|
|
18
16
|
export const projectLanguageSchema = z.enum(["zh", "en"]);
|
|
19
|
-
// ============ ErrorCode(单一来源:REST / SSE /
|
|
17
|
+
// ============ ErrorCode(单一来源:REST / SSE / 工具共用,「错误码」) ============
|
|
20
18
|
/**
|
|
21
19
|
* 错误码全量枚举
|
|
22
|
-
* 文档出处:
|
|
20
|
+
* 文档出处: 各端点错误响应;DELTA_CONFLICT 为 2026-08 修订废弃码
|
|
23
21
|
* (computeState 改为 skipped/conflicts 字段呈现,不再返回 409——保留枚举兼容历史引用);
|
|
24
|
-
* 末尾四个为
|
|
22
|
+
* 末尾四个为 命名(文档未给具体码名,按语义命名,供 SSE error 事件使用)
|
|
25
23
|
*/
|
|
26
24
|
export const ERROR_CODES = [
|
|
27
|
-
// ----
|
|
25
|
+
// ---- 提取(现行)----
|
|
28
26
|
"VALIDATION_ERROR", // 400 参数校验失败(entity/delta/outline 创建等)
|
|
29
27
|
"ENTITY_NOT_FOUND", // 404 实体不存在(详情/更新/删除/restore)
|
|
30
28
|
"RELATION_EXISTS", // 409 关系已存在
|
|
31
|
-
"EVENT_ALREADY_MOUNTED", // 409 事件已挂载时间点,occurs_at 1:n 重复挂载拒绝(G2
|
|
29
|
+
"EVENT_ALREADY_MOUNTED", // 409 事件已挂载时间点,occurs_at 1:n 重复挂载拒绝(G2)
|
|
32
30
|
"RELATION_NOT_FOUND", // 404 关系不存在
|
|
33
31
|
"OUTLINE_NODE_NOT_FOUND", // 404 大纲节点不存在(compute / path / restore / purge)
|
|
34
|
-
"OUTLINE_ANCESTOR_DELETED", // 409 restore
|
|
35
|
-
"INVALID_PROJECT_PATH", // 400 create/open
|
|
36
|
-
"PROPOSAL_STALE", // 409
|
|
37
|
-
"PROPOSAL_NOT_FOUND", // 404 proposal_id
|
|
38
|
-
"PROPOSAL_PROJECT_MISMATCH", // 409 提案所属项目 ≠
|
|
39
|
-
"SCHEMA_VERSION_MISMATCH", // 409 导入 zip 的 data.db user_version
|
|
40
|
-
"PROJECT_VERSION_NEWER", // 409 open 时项目 data.db user_version
|
|
41
|
-
"BACKUP_TARGET_EXISTS", // 409
|
|
42
|
-
"REFERENCE_FILE_MISSING", // 409 参考资料 file
|
|
32
|
+
"OUTLINE_ANCESTOR_DELETED", // 409 restore 时存在软删祖先
|
|
33
|
+
"INVALID_PROJECT_PATH", // 400 create/open 路径校验失败
|
|
34
|
+
"PROPOSAL_STALE", // 409 确认时引用快照不一致
|
|
35
|
+
"PROPOSAL_NOT_FOUND", // 404 proposal_id 不存在
|
|
36
|
+
"PROPOSAL_PROJECT_MISMATCH", // 409 提案所属项目 ≠ 当前项目
|
|
37
|
+
"SCHEMA_VERSION_MISMATCH", // 409 导入 zip 的 data.db user_version 与当前程序版本不匹配(拒绝导入,不静默重建)
|
|
38
|
+
"PROJECT_VERSION_NEWER", // 409 open 时项目 data.db user_version 高于当前程序版本(拒绝打开并提示升级程序,堵降级数据丢失)
|
|
39
|
+
"BACKUP_TARGET_EXISTS", // 409 重命名备份目标文件名已存在(B2.6:renameSync 目标存在会静默覆盖——显式拒绝防数据丢失)
|
|
40
|
+
"REFERENCE_FILE_MISSING", // 409 参考资料 file 类文件缺失(PUT 更新时读原文件失败——外部删除,提示先扫描同步)
|
|
43
41
|
// ---- 废弃(保留兼容)----
|
|
44
42
|
"DELTA_CONFLICT", // 已废弃(2026-08 修订:computeState 以 conflicts 字段替代 409)
|
|
45
|
-
// ----
|
|
46
|
-
"TOOL_RESULT_TOO_LARGE", // 工具结果 token
|
|
47
|
-
"AGENT_DISPATCH_ERROR", // 工具调度器缺陷(S7.3 防御:结果条数不符 / id 错位 /
|
|
48
|
-
"AGENT_INTERNAL_ERROR", // agent 循环内部未知异常(S7.3 防御路径——chatStream
|
|
49
|
-
"AGENT_MAX_ITERATIONS", // agent 循环超 8 轮上限,发 error
|
|
50
|
-
"AGENT_TIMEOUT", // 单轮 120s
|
|
51
|
-
"AGENT_TOKEN_BUDGET", // 上下文 token
|
|
43
|
+
// ---- 命名(SSE error 事件用)----
|
|
44
|
+
"TOOL_RESULT_TOO_LARGE", // 工具结果 token 预算超限:截断/拒绝该工具结果
|
|
45
|
+
"AGENT_DISPATCH_ERROR", // 工具调度器缺陷(S7.3 防御:结果条数不符 / id 错位 / 调度器抛错),终止循环
|
|
46
|
+
"AGENT_INTERNAL_ERROR", // agent 循环内部未知异常(S7.3 防御路径——chatStream 不 throw,理论不可达)
|
|
47
|
+
"AGENT_MAX_ITERATIONS", // agent 循环超 8 轮上限,发 error 事件终止
|
|
48
|
+
"AGENT_TIMEOUT", // 单轮 120s 超时终止
|
|
49
|
+
"AGENT_TOKEN_BUDGET", // 上下文 token 预算超限终止
|
|
52
50
|
];
|
|
53
51
|
/** ErrorCode 枚举 schema */
|
|
54
52
|
export const errorCodeSchema = z.enum(ERROR_CODES);
|
|
55
|
-
// ============
|
|
53
|
+
// ============ 通用响应包裹(「通用约定」) ============
|
|
56
54
|
/** 成功响应包裹:{ success: true, data: T } */
|
|
57
55
|
export function apiSuccessSchema(dataSchema) {
|
|
58
56
|
return z.object({
|
|
@@ -69,16 +67,16 @@ export const apiErrorSchema = z.object({
|
|
|
69
67
|
fields: z.array(z.string()).optional(), // 校验失败时指出具体字段(VALIDATION_ERROR)
|
|
70
68
|
}),
|
|
71
69
|
});
|
|
72
|
-
// ============ 实体 data 字段 schema(
|
|
70
|
+
// ============ 实体 data 字段 schema( 创建接口 + + ) ============
|
|
73
71
|
/**
|
|
74
|
-
* character
|
|
75
|
-
* 注意:data 嵌套对象内部字段原样透传(snake_case,如 custom_fields
|
|
72
|
+
* character 专属字段(:role/gender/age/personality[]/motivation/abilities[]/status/custom_fields)
|
|
73
|
+
* 注意:data 嵌套对象内部字段原样透传(snake_case,如 custom_fields),顶层字段才是 camelCase
|
|
76
74
|
*/
|
|
77
75
|
export const characterDataSchema = z
|
|
78
76
|
.object({
|
|
79
77
|
role: z.string().optional(),
|
|
80
78
|
gender: z.string().optional(),
|
|
81
|
-
age: z.union([z.string(), z.number()]).optional(), // 年龄文本或数字皆可(
|
|
79
|
+
age: z.union([z.string(), z.number()]).optional(), // 年龄文本或数字皆可( 未定死类型)
|
|
82
80
|
personality: z.array(z.string()).optional(),
|
|
83
81
|
motivation: z.string().optional(),
|
|
84
82
|
abilities: z.array(z.string()).optional(),
|
|
@@ -86,10 +84,10 @@ export const characterDataSchema = z
|
|
|
86
84
|
custom_fields: z.record(z.string(), z.unknown()).optional(),
|
|
87
85
|
})
|
|
88
86
|
.passthrough(); // 允许未知字段(创作工具,用户自定义字段自由)
|
|
89
|
-
/** setting
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
87
|
+
/** setting 专属字段(:description/tags/rules/custom_fields;
|
|
88
|
+
* `tags` = 分类标签( K2,2026-08:分类统一字段,前后端同名);
|
|
89
|
+
* `rules` = 规则条款(恢复原始语义,仅设定详情页编辑);
|
|
90
|
+
* `parent_id`( 层级 belongs_to)与 `category`( 废弃)不参与新字段,旧残留 passthrough 容错) */
|
|
93
91
|
export const settingDataSchema = z
|
|
94
92
|
.object({
|
|
95
93
|
description: z.string().optional(),
|
|
@@ -98,7 +96,7 @@ export const settingDataSchema = z
|
|
|
98
96
|
custom_fields: z.record(z.string(), z.unknown()).optional(),
|
|
99
97
|
})
|
|
100
98
|
.passthrough();
|
|
101
|
-
/** location
|
|
99
|
+
/** location 专属字段(:type/parent_id/description/custom_fields) */
|
|
102
100
|
export const locationDataSchema = z
|
|
103
101
|
.object({
|
|
104
102
|
type: z.string().optional(),
|
|
@@ -107,42 +105,42 @@ export const locationDataSchema = z
|
|
|
107
105
|
custom_fields: z.record(z.string(), z.unknown()).optional(),
|
|
108
106
|
})
|
|
109
107
|
.passthrough();
|
|
110
|
-
/** hook
|
|
108
|
+
/** hook 专属字段(:status/category/expected_payoff/payoff_timing/half_life/is_core/notes/expected_resolve_node_id) */
|
|
111
109
|
export const hookDataSchema = z
|
|
112
110
|
.object({
|
|
113
111
|
status: z.enum(HOOK_STATUSES).optional(),
|
|
114
112
|
category: z.string().optional(), // 自由填(HOOK_CATEGORIES 仅为前端建议值)
|
|
115
113
|
expected_payoff: z.string().optional(),
|
|
116
114
|
payoff_timing: z.enum(PAYOFF_TIMING).optional(),
|
|
117
|
-
half_life: z.number().int().positive().optional(), //
|
|
115
|
+
half_life: z.number().int().positive().optional(), // 章数;缺省映射见
|
|
118
116
|
is_core: z.boolean().optional(),
|
|
119
117
|
notes: z.string().optional(),
|
|
120
|
-
expected_resolve_node_id: z.string().nullable().optional(), //
|
|
118
|
+
expected_resolve_node_id: z.string().nullable().optional(), // ready_to_resolve 依据
|
|
121
119
|
})
|
|
122
120
|
.passthrough();
|
|
123
|
-
/** event
|
|
121
|
+
/** event 专属字段( 时间轴事件:description/tags[];字段名 snake_case) */
|
|
124
122
|
export const eventDataSchema = z
|
|
125
123
|
.object({
|
|
126
124
|
description: z.string().optional(),
|
|
127
125
|
tags: z.array(z.string()).optional(),
|
|
128
126
|
})
|
|
129
127
|
.passthrough(); // 允许未知字段(创作工具,用户自定义字段自由)
|
|
130
|
-
/** timepoint 专属字段(G2
|
|
128
|
+
/** timepoint 专属字段(G2 时间标签点):data 空——时间标签文本 = name,可重命名,YAGNI 不加 data 字段 */
|
|
131
129
|
export const timepointDataSchema = z.object({}).passthrough();
|
|
132
|
-
/** reference
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
130
|
+
/** reference 专属字段( 参考资料:type 分类 / content 全文长文本 / source 来源 / tags 标签数组;
|
|
131
|
+
* type 为自由文本分类(不再预置枚举,缺省 material 写入侧兜底);
|
|
132
|
+
* 两类承载——kind = file(本地 md 文档:file_name 相对路径 + content 正文镜像 + file_mtime
|
|
133
|
+
* 上次同步快照)/ link(外源链接:url 必填 + content 可选备注);kind 缺省视为 link(存量条目运行时兼容);
|
|
134
|
+
* source 仅存量旧条目使用(新建不再写入)) */
|
|
137
135
|
export const referenceDataSchema = z
|
|
138
136
|
.object({
|
|
139
|
-
type: z.string().optional(), //
|
|
140
|
-
kind: z.enum(["file", "link"]).optional(), //
|
|
137
|
+
type: z.string().optional(), // 自由文本分类(取消预置枚举;缺省 material 写入侧兜底)
|
|
138
|
+
kind: z.enum(["file", "link"]).optional(), // 缺省视为 link
|
|
141
139
|
file_name: z.string().optional(), // file 类:references/ 下相对路径(服务端写入,客户端只读)
|
|
142
140
|
file_mtime: z.string().optional(), // file 类:上次同步时文件 mtime(scan 比对基准,服务端写入)
|
|
143
141
|
url: z.string().optional(), // link 类:外源链接 URL(创建时必填校验在服务端 route 层)
|
|
144
142
|
content: z.string().optional(),
|
|
145
|
-
source: z.string().nullable().optional(), //
|
|
143
|
+
source: z.string().nullable().optional(), // 存量旧条目兼容(新建不再写入)
|
|
146
144
|
tags: z.array(z.string()).optional(),
|
|
147
145
|
})
|
|
148
146
|
.passthrough(); // 允许未知字段(创作工具,用户自定义字段自由)
|
|
@@ -158,31 +156,31 @@ export const ENTITY_DATA_SCHEMAS = {
|
|
|
158
156
|
hook: hookDataSchema,
|
|
159
157
|
event: eventDataSchema,
|
|
160
158
|
timepoint: timepointDataSchema, // G2 时间标签点:data 空
|
|
161
|
-
reference: referenceDataSchema, //
|
|
159
|
+
reference: referenceDataSchema, // 参考资料
|
|
162
160
|
};
|
|
163
|
-
// ============ project
|
|
161
|
+
// ============ project 端点(「项目管理」) ============
|
|
164
162
|
/** ProjectConfig 响应(GET /api/v1/project/config;与 types/project.ts 的 ProjectConfig 对齐;
|
|
165
|
-
*
|
|
163
|
+
* `prompt` 已废弃不再返回——项目规则唯一事实源改为项目目录 ) */
|
|
166
164
|
export const projectConfigSchema = z.object({
|
|
167
165
|
id: z.string(),
|
|
168
166
|
name: z.string(),
|
|
169
167
|
language: projectLanguageSchema,
|
|
170
|
-
schemaVersion: z.number().int(), //
|
|
171
|
-
currentPosition: z.string().nullable(), // 「当前位置」节点 id;null =
|
|
172
|
-
backupFrequencyMinutes: z.number().int().nullable(), //
|
|
168
|
+
schemaVersion: z.number().int(), //
|
|
169
|
+
currentPosition: z.string().nullable(), // 「当前位置」节点 id;null = 未设置
|
|
170
|
+
backupFrequencyMinutes: z.number().int().nullable(), // 自动备份频率;null = 关闭;缺省 10 由读侧兜底
|
|
173
171
|
createdAt: z.string(),
|
|
174
172
|
updatedAt: z.string(),
|
|
175
173
|
});
|
|
176
174
|
// POST /api/v1/project/create
|
|
177
175
|
export const projectCreateReqSchema = z
|
|
178
176
|
.object({
|
|
179
|
-
path: z.string(), //
|
|
177
|
+
path: z.string(), // 项目目录绝对路径( 校验)
|
|
180
178
|
config: z
|
|
181
179
|
.object({
|
|
182
180
|
name: z.string().optional(),
|
|
183
181
|
language: projectLanguageSchema.optional(),
|
|
184
|
-
// prompt
|
|
185
|
-
// 项目规则改由 PUT /api/v1/project/agents 写入
|
|
182
|
+
// prompt 已废弃:不再接受(strict schema 传入 → 400 VALIDATION_ERROR);
|
|
183
|
+
// 项目规则改由 PUT /api/v1/project/agents 写入
|
|
186
184
|
})
|
|
187
185
|
.strict()
|
|
188
186
|
.optional(),
|
|
@@ -230,51 +228,51 @@ export const projectConfigUpdateReqSchema = z
|
|
|
230
228
|
.object({
|
|
231
229
|
name: z.string().optional(),
|
|
232
230
|
language: projectLanguageSchema.optional(),
|
|
233
|
-
// prompt
|
|
234
|
-
// 项目规则改由 PUT /api/v1/project/agents 写入
|
|
231
|
+
// prompt 已废弃:不再接受(strict schema 传入 → 400 VALIDATION_ERROR);
|
|
232
|
+
// 项目规则改由 PUT /api/v1/project/agents 写入
|
|
235
233
|
current_position: z.string().nullable().optional(), // 须指向存在的非软删大纲节点(服务端校验)
|
|
236
234
|
/**
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
235
|
+
* 自动备份频率( + 批次十四修订):仅接受枚举 1/5/10/15/30/60(BACKUP_FREQUENCIES),其他(含 0)→ 400
|
|
236
|
+
* VALIDATION_ERROR;null = 关闭(写入 null)——0 仅读侧兼容旧数据语义,写侧一律用 null 表示关闭
|
|
237
|
+
*/
|
|
240
238
|
backup_frequency_minutes: z.union(BACKUP_FREQUENCIES.map((v) => z.literal(v))).nullable().optional(),
|
|
241
239
|
})
|
|
242
240
|
.strict();
|
|
243
241
|
export const projectConfigUpdateResSchema = z.object({
|
|
244
242
|
updated: z.literal(true),
|
|
245
243
|
});
|
|
246
|
-
// GET /api/v1/project/agents
|
|
244
|
+
// GET /api/v1/project/agents(项目规则文件 ——唯一事实源,取代 project.json `prompt`)
|
|
247
245
|
// 语义:无当前项目 → 409 NO_PROJECT_OPEN;文件不存在不报错(exists:false + 空串);
|
|
248
|
-
//
|
|
246
|
+
// updatedAt = 文件 mtime(ISO 8601,外部修改检测依据);读取每次实时读文件不缓存
|
|
249
247
|
export const projectAgentsGetResSchema = z.object({
|
|
250
|
-
content: z.string(), //
|
|
248
|
+
content: z.string(), // 文件内容(文件不存在 → 空串)
|
|
251
249
|
exists: z.boolean(), // 文件是否存在(false 时 content 为空串)
|
|
252
250
|
updatedAt: z.string().nullable(), // 文件 mtime(ISO 8601;文件不存在 → null)
|
|
253
251
|
});
|
|
254
|
-
// PUT /api/v1/project/agents
|
|
252
|
+
// PUT /api/v1/project/agents(设置页直接编辑 文件内容)
|
|
255
253
|
// 语义:整体替换(非追加);空串 = 清空规则(保留空文件不删除);文件不存在自动创建;
|
|
256
|
-
//
|
|
254
|
+
// 写入走原子写( 同款);写入后返回新 mtime(前端更新本地比对基线)
|
|
257
255
|
export const projectAgentsPutReqSchema = z
|
|
258
256
|
.object({
|
|
259
|
-
content: z.string(), //
|
|
257
|
+
content: z.string(), // 完整内容(整体替换;空串 = 清空规则文件,保留空文件不删除)
|
|
260
258
|
})
|
|
261
259
|
.strict();
|
|
262
260
|
export const projectAgentsPutResSchema = z.object({
|
|
263
261
|
saved: z.literal(true),
|
|
264
262
|
updatedAt: z.string(), // 写入后的文件 mtime(ISO 8601)——前端更新本地比对基线
|
|
265
263
|
});
|
|
266
|
-
// POST /api/v1/project/backup
|
|
264
|
+
// POST /api/v1/project/backup( 新增:手动备份可携带自定义名称)
|
|
267
265
|
// - 请求体可选 `name`(string);空串/缺省 → 无自定义名称(纯时间戳文件名)。
|
|
268
266
|
// - **形状校验仅限类型**(oracle 审核 P2-1:zod 与 sanitize 的「.zip 剥离 + 长度」判定
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
//
|
|
267
|
+
// 顺序曾在 schema 内重复实现导致误拒——如 29 字符 + ".zip" schema 判超长而 sanitize 判合法);
|
|
268
|
+
// **名称规则(trim/.zip 剥离/长度/字符集)权威判定全部收敛在 shared sanitizeBackupName**——
|
|
269
|
+
// writeBackup 为唯一执行点,非法 → 400 VALIDATION_ERROR。
|
|
272
270
|
export const projectBackupReqSchema = z
|
|
273
271
|
.object({
|
|
274
272
|
name: z.string().optional(),
|
|
275
273
|
})
|
|
276
274
|
.strict();
|
|
277
|
-
// POST /api/v1/project/backup/rename
|
|
275
|
+
// POST /api/v1/project/backup/rename:重命名备份(只改名称段,时间戳与 kind 保持)
|
|
278
276
|
// - 请求体 fileName 必填;name 可选——非空 → sanitize(非法 400);空串/缺省 → 清除名称段
|
|
279
277
|
export const projectBackupRenameReqSchema = z
|
|
280
278
|
.object({
|
|
@@ -282,46 +280,46 @@ export const projectBackupRenameReqSchema = z
|
|
|
282
280
|
name: z.string().optional(),
|
|
283
281
|
})
|
|
284
282
|
.strict();
|
|
285
|
-
// ============
|
|
283
|
+
// ============ 导出/导入端点(产品承诺「数据主权归用户」) ============
|
|
286
284
|
/**
|
|
287
|
-
* 导出 zip 内固定三文件名(
|
|
285
|
+
* 导出 zip 内固定三文件名(GET /api/v1/project/export 的 zip 条目名与数据文件
|
|
288
286
|
* 原名一致——import 侧按此固定名校验,缺失即坏包)
|
|
289
287
|
*/
|
|
290
288
|
export const PROJECT_EXPORT_FILE_NAMES = ["project.json", "outline.json", "data.db"];
|
|
291
289
|
/**
|
|
292
|
-
* GET /api/v1/project/export
|
|
293
|
-
* - **响应为二进制 zip(application/zip),非 JSON
|
|
294
|
-
*
|
|
295
|
-
*
|
|
290
|
+
* GET /api/v1/project/export:
|
|
291
|
+
* - **响应为二进制 zip(application/zip),非 JSON 包裹**——「成功响应
|
|
292
|
+
* {success,data}」通用约定的显式例外;Content-Disposition: attachment;
|
|
293
|
+
* filename*=UTF-8''<书名>.zip(RFC 5987)
|
|
296
294
|
* - zip 内三文件:project.json + outline.json + data.db(导出前 wal_checkpoint(TRUNCATE)
|
|
297
|
-
*
|
|
295
|
+
* 保证 data.db 主文件完整快照; key 存用户级配置,天然不入包)
|
|
298
296
|
* - 错误:无当前项目 → 409 NO_PROJECT_OPEN(服务端补充码,与 /config 一致);
|
|
299
|
-
*
|
|
300
|
-
* - 二进制响应不走 Zod parse
|
|
297
|
+
* 三文件缺失任一 → 500 INTERNAL_ERROR(打开的项目三文件必然齐全,缺失即损坏)
|
|
298
|
+
* - 二进制响应不走 Zod parse——以本注释 + PROJECT_EXPORT_FILE_NAMES 常量表达
|
|
301
299
|
*/
|
|
302
|
-
// POST /api/v1/project/import(
|
|
300
|
+
// POST /api/v1/project/import( 已落)
|
|
303
301
|
// - 请求:multipart/form-data 文件上传——field "file"(zip 备份包)+ field "name"(书名,
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
// -
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
// -
|
|
310
|
-
//
|
|
311
|
-
//
|
|
302
|
+
// 必填;禁路径分隔符/纯点/控制字符,与 client 新建项目同规则)——目标目录为
|
|
303
|
+
// 服务端决定的 创作根/books/<name>/(客户端不可指定路径,防越权)
|
|
304
|
+
// - 服务端流程:解压到临时目录 → 校验(条目白名单 = PROJECT_EXPORT_FILE_NAMES
|
|
305
|
+
// 三文件名 + project.json/outline.json 顶层data.db user_version 匹配)→
|
|
306
|
+
// 原子搬入新书目录(新建,不覆盖现有项目)→ 返回 200
|
|
307
|
+
// - 错误码:坏包/缺文件/未知条目/不符 → 400 VALIDATION_ERROR;data.db user_version
|
|
308
|
+
// 与当前程序版本不匹配 → 409 SCHEMA_VERSION_MISMATCH(拒绝导入,不静默重建);
|
|
309
|
+
// 目标书名已存在 → 409 PROJECT_ALREADY_EXISTS(服务端补充码,与 create 同语义)
|
|
312
310
|
export const projectImportResSchema = z.object({
|
|
313
311
|
imported: z.literal(true),
|
|
314
312
|
id: z.string(), // 项目 project_id(覆盖恢复 = 书架目标项目原 id;导入新书 = 沿用 zip 内 project.json 的 id)
|
|
315
313
|
path: z.string(), // 书目录绝对路径(创作根/books/<name>/ 或去重名 books/<name> (N)/)
|
|
316
|
-
name: z.string(), // 书名(新书目录名;project.json 内部 name 同此——「目录名 =
|
|
317
|
-
/**
|
|
314
|
+
name: z.string(), // 书名(新书目录名;project.json 内部 name 同此——「目录名 = 书名」不变式)
|
|
315
|
+
/** 分流:restored = zip 内 id 匹配书架 → 覆盖恢复;new = 导入为新书(前端按此提示 toast) */
|
|
318
316
|
mode: z.enum(["restored", "new"]),
|
|
319
317
|
});
|
|
320
|
-
// ============ entity
|
|
318
|
+
// ============ entity 端点(「实体 CRUD」) ============
|
|
321
319
|
/**
|
|
322
320
|
* 关系(GET /api/v1/relation depth=1 项;与 types/entity.ts RelationRecord 对齐)
|
|
323
321
|
* 定义于此处供 entity 详情响应(relations: RelationSummary[],形状同 RelationRecord,
|
|
324
|
-
*
|
|
322
|
+
* 未单独列字段)与 relation 查询共用,避免同结构两处定义漂移
|
|
325
323
|
*/
|
|
326
324
|
export const relationRecordSchema = z.object({
|
|
327
325
|
id: z.string(),
|
|
@@ -341,10 +339,10 @@ export const entitySummarySchema = z.object({
|
|
|
341
339
|
type: entityTypeSchema,
|
|
342
340
|
name: z.string(),
|
|
343
341
|
summary: z.record(z.string(), z.unknown()), // 从 data 提取的关键摘要字段
|
|
344
|
-
// M2(2026-08 批次六):仅 setting
|
|
342
|
+
// M2(2026-08 批次六):仅 setting 列表填充( 层级 = belongs_to)
|
|
345
343
|
parentId: z.string().optional(),
|
|
346
344
|
parentName: z.string().optional(),
|
|
347
|
-
//
|
|
345
|
+
// 手动排序位(2026-08 批次十三):仅 setting 类型填充(entities.sort_order 列)
|
|
348
346
|
sortOrder: z.number().int().optional(),
|
|
349
347
|
createdAt: z.string(),
|
|
350
348
|
updatedAt: z.string(),
|
|
@@ -356,10 +354,10 @@ export const entityListQuerySchema = z.object({
|
|
|
356
354
|
limit: z.coerce.number().int().min(1).max(200).default(50),
|
|
357
355
|
sort: z.enum(["name", "created_at", "updated_at"]).optional(),
|
|
358
356
|
order: z.enum(["asc", "desc"]).optional(),
|
|
359
|
-
//
|
|
357
|
+
// 标签包含筛选(2026-08):data 数组字段(setting.rules / event.tags)包含该标签即命中
|
|
360
358
|
tag: z.string().optional(),
|
|
361
|
-
//
|
|
362
|
-
// (belongs_to
|
|
359
|
+
// 上级设定筛选(2026-08,仅 setting 类型生效,其他类型路由层忽略):匹配 = 实体在设定层级树
|
|
360
|
+
// (belongs_to)中直接或间接属于该上级(递归子树,不含上级自身);复用 listSettingHierarchyEdges
|
|
363
361
|
// 建邻接表 DFS 收集后代集合走 db JS 过滤路径(total = 过滤后总数);与 q/tag/排序/分页组合(AND);
|
|
364
362
|
// 指向不存在的设定(含已软删)→ 空结果(宽松,同 tag 无匹配不 404);不传 = 不过滤
|
|
365
363
|
parent_id: z.string().optional(),
|
|
@@ -376,7 +374,7 @@ export const entityDetailResSchema = z.object({
|
|
|
376
374
|
type: entityTypeSchema,
|
|
377
375
|
name: z.string(),
|
|
378
376
|
data: z.record(z.string(), z.unknown()), // 完整字段(嵌套 snake_case 原样透传)
|
|
379
|
-
relations: z.array(relationRecordSchema), // RelationSummary(形状同 RelationRecord
|
|
377
|
+
relations: z.array(relationRecordSchema), // RelationSummary(形状同 RelationRecord,)
|
|
380
378
|
deltaCount: z.number().int(),
|
|
381
379
|
createdAt: z.string(),
|
|
382
380
|
updatedAt: z.string(),
|
|
@@ -406,7 +404,7 @@ export const entityUpdateResSchema = z.object({
|
|
|
406
404
|
id: z.string(),
|
|
407
405
|
updated: z.literal(true),
|
|
408
406
|
});
|
|
409
|
-
// DELETE /api/v1/entity/:type/:id(软删 +
|
|
407
|
+
// DELETE /api/v1/entity/:type/:id(软删 + 级联计数)
|
|
410
408
|
export const entityDeleteResSchema = z.object({
|
|
411
409
|
deleted: z.literal(true),
|
|
412
410
|
cascaded: z.object({
|
|
@@ -414,9 +412,9 @@ export const entityDeleteResSchema = z.object({
|
|
|
414
412
|
deltas: z.number().int(),
|
|
415
413
|
}),
|
|
416
414
|
});
|
|
417
|
-
// ============ relation
|
|
415
|
+
// ============ relation 端点(「关系管理」) ============
|
|
418
416
|
// relationRecordSchema 定义于 entity 区(entity 详情 relations 与 relation 查询共用,避免重复定义)
|
|
419
|
-
/** 路径结构(depth>=2
|
|
417
|
+
/** 路径结构(depth>=2,) */
|
|
420
418
|
export const relationPathSchema = z.object({
|
|
421
419
|
nodes: z.array(z.object({ type: z.string(), id: z.string(), name: z.string() })),
|
|
422
420
|
edges: z.array(z.object({ from: z.string(), to: z.string(), relationType: z.string() })),
|
|
@@ -434,7 +432,7 @@ export const relationQueryResSchema = z.object({
|
|
|
434
432
|
relations: z.array(relationRecordSchema),
|
|
435
433
|
paths: z.array(relationPathSchema).optional(), // depth>=2 时返回
|
|
436
434
|
});
|
|
437
|
-
// POST /api/v1/relation(relation_type 限定
|
|
435
|
+
// POST /api/v1/relation(relation_type 限定 预定义 16 种)
|
|
438
436
|
export const relationCreateReqSchema = z
|
|
439
437
|
.object({
|
|
440
438
|
source_type: z.string(),
|
|
@@ -455,18 +453,18 @@ export const relationCreateResSchema = z.object({
|
|
|
455
453
|
relationType: z.string(),
|
|
456
454
|
}),
|
|
457
455
|
});
|
|
458
|
-
// PUT /api/v1/relation/:id
|
|
456
|
+
// PUT /api/v1/relation/:id(「PUT /relation/:id」:metadata **整体替换**,含清空传 {})
|
|
459
457
|
export const relationUpdateMetaReqSchema = z
|
|
460
458
|
.object({
|
|
461
459
|
metadata: z.record(z.string(), z.unknown()),
|
|
462
460
|
})
|
|
463
461
|
.strict();
|
|
464
|
-
// DELETE /api/v1/relation/:id
|
|
462
|
+
// DELETE /api/v1/relation/:id(物理删除,不进回收站)
|
|
465
463
|
export const relationDeleteResSchema = z.object({
|
|
466
464
|
deleted: z.literal(true),
|
|
467
465
|
});
|
|
468
|
-
// ============ delta
|
|
469
|
-
/** 变更操作类型(2026-08 修订语义:set/update/add/remove,见
|
|
466
|
+
// ============ delta 端点(「Delta 变更追踪」) ============
|
|
467
|
+
/** 变更操作类型(2026-08 修订语义:set/update/add/remove,见 ) */
|
|
470
468
|
export const deltaOpSchema = z.enum(["set", "update", "add", "remove"]);
|
|
471
469
|
/** 单条属性变更 */
|
|
472
470
|
export const deltaChangeSchema = z.object({
|
|
@@ -507,7 +505,7 @@ export const deltaByNodeResSchema = z.object({
|
|
|
507
505
|
nodeId: z.string(),
|
|
508
506
|
deltas: z.array(deltaRecordSchema),
|
|
509
507
|
});
|
|
510
|
-
// POST /api/v1/delta/compute
|
|
508
|
+
// POST /api/v1/delta/compute(只沿大纲树父链累积)
|
|
511
509
|
export const deltaComputeReqSchema = z
|
|
512
510
|
.object({
|
|
513
511
|
target_type: z.string(),
|
|
@@ -531,7 +529,7 @@ export const deltaComputeResSchema = z.object({
|
|
|
531
529
|
expected: z.unknown(),
|
|
532
530
|
actual: z.unknown(),
|
|
533
531
|
}))
|
|
534
|
-
.optional(), //
|
|
532
|
+
.optional(), // op=update 且当前值 ≠ from 时跳过该 change
|
|
535
533
|
})),
|
|
536
534
|
conflicts: z.array(z.object({
|
|
537
535
|
deltaId: z.string(),
|
|
@@ -540,14 +538,14 @@ export const deltaComputeResSchema = z.object({
|
|
|
540
538
|
actual: z.unknown(),
|
|
541
539
|
})),
|
|
542
540
|
});
|
|
543
|
-
// ============ outline
|
|
541
|
+
// ============ outline 端点(「大纲操作」,严格三层) ============
|
|
544
542
|
/**
|
|
545
|
-
* 大纲节点 data 字段 schema
|
|
543
|
+
* 大纲节点 data 字段 schema(麦基《故事》字段集, outline.json「节点结构化信息」节):
|
|
546
544
|
* scene——goal/conflict_levels/value_from/value_to;chapter——reversal/climax_scene;
|
|
547
545
|
* volume——climax_scene/inciting_scene。
|
|
548
546
|
* 宽松语义与 ENTITY_DATA_SCHEMAS 一致:
|
|
549
|
-
* - `.passthrough
|
|
550
|
-
* - 引用字段(climax_scene/inciting_scene
|
|
547
|
+
* - `.passthrough` 允许未知字段(创作工具,用户自定义字段自由,未知字段原样保留透传)
|
|
548
|
+
* - 引用字段(climax_scene/inciting_scene)仅类型校验(字符串),不校验存在性/范围(MVP 宽松)
|
|
551
549
|
* 请求体 data 本体使用宽松 record(outlineCreateReqSchema),精确校验在服务端 route 层按层级选用
|
|
552
550
|
*/
|
|
553
551
|
export const sceneDataSchema = z
|
|
@@ -588,9 +586,9 @@ export const outlineNodeSchema = z.lazy(() => z.object({
|
|
|
588
586
|
type: z.enum(["volume", "chapter", "scene"]),
|
|
589
587
|
title: z.string(),
|
|
590
588
|
summary: z.string().optional(),
|
|
591
|
-
data: z.record(z.string(), z.unknown()).optional(), //
|
|
592
|
-
updatedAt: z.string(), //
|
|
593
|
-
deleted: z.boolean().optional(), //
|
|
589
|
+
data: z.record(z.string(), z.unknown()).optional(), // 节点结构化信息(内部字段原样透传)
|
|
590
|
+
updatedAt: z.string(), // 节点版本戳
|
|
591
|
+
deleted: z.boolean().optional(), // 软删标记(管理视图)
|
|
594
592
|
deletedAt: z.string().optional(),
|
|
595
593
|
children: z.array(outlineNodeSchema).optional(),
|
|
596
594
|
metadata: z
|
|
@@ -605,26 +603,26 @@ export const outlineNodeSchema = z.lazy(() => z.object({
|
|
|
605
603
|
export const outlineTreeSchema = z.object({
|
|
606
604
|
id: z.literal("root"),
|
|
607
605
|
type: z.literal("root"),
|
|
608
|
-
schemaVersion: z.number().int(), // outline.json 顶层 schema_version
|
|
606
|
+
schemaVersion: z.number().int(), // outline.json 顶层 schema_version
|
|
609
607
|
children: z.array(outlineNodeSchema),
|
|
610
608
|
});
|
|
611
609
|
// GET /api/v1/outline(Query)
|
|
612
610
|
export const outlineGetQuerySchema = z.object({
|
|
613
|
-
// 显式字符串布尔:z.coerce.boolean
|
|
611
|
+
// 显式字符串布尔:z.coerce.boolean 会把 "false" 解析为 true(反向问题),
|
|
614
612
|
// 改为枚举 + transform:显式传 false → false;不传 → undefined(默认关闭 metadata 统计)
|
|
615
613
|
with_metadata: z
|
|
616
614
|
.enum(["true", "false"])
|
|
617
615
|
.transform((v) => v === "true")
|
|
618
616
|
.optional(), // 跨 outline.json × data.db 联查统计
|
|
619
617
|
});
|
|
620
|
-
// POST /api/v1/outline(parent_id
|
|
618
|
+
// POST /api/v1/outline(parent_id 必填,无默认值)
|
|
621
619
|
export const outlineCreateReqSchema = z
|
|
622
620
|
.object({
|
|
623
621
|
type: z.enum(["volume", "chapter", "scene"]),
|
|
624
622
|
title: z.string().min(1).max(200),
|
|
625
623
|
parent_id: z.string(), // volume→root;chapter→volume 或 root;scene→必须 chapter
|
|
626
624
|
summary: z.string().optional(),
|
|
627
|
-
data: z.record(z.string(), z.unknown()).optional(), //
|
|
625
|
+
data: z.record(z.string(), z.unknown()).optional(), // 节点结构化信息(宽松 record,按层级 schema 精校验)
|
|
628
626
|
})
|
|
629
627
|
.strict();
|
|
630
628
|
export const outlineCreateResSchema = z.object({
|
|
@@ -639,7 +637,7 @@ export const outlineUpdateReqSchema = z
|
|
|
639
637
|
.object({
|
|
640
638
|
title: z.string().min(1).max(200).optional(),
|
|
641
639
|
summary: z.string().optional(),
|
|
642
|
-
data: z.record(z.string(), z.unknown()).optional(), //
|
|
640
|
+
data: z.record(z.string(), z.unknown()).optional(), // 部分合并(按层级 schema 精校验)
|
|
643
641
|
})
|
|
644
642
|
.strict();
|
|
645
643
|
export const outlineUpdateResSchema = z.object({
|
|
@@ -657,10 +655,10 @@ export const outlineMoveResSchema = z.object({
|
|
|
657
655
|
previousParentId: z.string(),
|
|
658
656
|
newParentId: z.string(),
|
|
659
657
|
});
|
|
660
|
-
// PUT /api/v1/entity/event/:id/move
|
|
658
|
+
// PUT /api/v1/entity/event/:id/move(时间轴事件重排,;命名风格同 outlineMoveReqSchema)
|
|
661
659
|
export const entityMoveReqSchema = z
|
|
662
660
|
.object({
|
|
663
|
-
// 0-based
|
|
661
|
+
// 0-based 全局事件线性序():超过当前事件总数 → clamp 到末尾(不返回 4xx);
|
|
664
662
|
// 负数由本 schema 拒绝(400 VALIDATION_ERROR)——db 层 moveEvent 对负数 clamp 至 0
|
|
665
663
|
// 仅为内部防御语义(HTTP 路径不可达)
|
|
666
664
|
order: z.number().int().min(0),
|
|
@@ -669,8 +667,8 @@ export const entityMoveReqSchema = z
|
|
|
669
667
|
export const entityMoveResSchema = z.object({
|
|
670
668
|
moved: z.literal(true),
|
|
671
669
|
});
|
|
672
|
-
// PUT /api/v1/entity/setting/:id/move(设定同级重排 / 改父 +
|
|
673
|
-
//
|
|
670
|
+
// PUT /api/v1/entity/setting/:id/move(设定同级重排 / 改父 + 重排,,2026-08 批次十三;
|
|
671
|
+
// 修订「设定无 sort_order 语义」约束——复用 entities.sort_order 列,无 DDL 迁移)
|
|
674
672
|
export const settingMoveReqSchema = z
|
|
675
673
|
.object({
|
|
676
674
|
// 目标父设定 id;null = 移为顶层根(无上级)。与当前父相同(含同为根)→ 仅重排
|
|
@@ -680,8 +678,8 @@ export const settingMoveReqSchema = z
|
|
|
680
678
|
order: z.number().int().min(0).optional(),
|
|
681
679
|
})
|
|
682
680
|
.strict();
|
|
683
|
-
// POST /api/v1/entity/event/:id/move_to(跨组挂载复合写,G2
|
|
684
|
-
// 区块 = 改挂载 +
|
|
681
|
+
// POST /api/v1/entity/event/:id/move_to(跨组挂载复合写,G2 事件拖到另一时间点
|
|
682
|
+
// 区块 = 改挂载 + 重排一次提交;服务端事务内原子完成——「G2 跨组拖拽」的复合端点实现)
|
|
685
683
|
export const eventMoveToReqSchema = z
|
|
686
684
|
.object({
|
|
687
685
|
// 目标时间点 id;null = 移出挂载区(仅重排,归入时间轴「未挂载」兜底区)。
|
|
@@ -691,7 +689,7 @@ export const eventMoveToReqSchema = z
|
|
|
691
689
|
order: z.number().int().min(0),
|
|
692
690
|
})
|
|
693
691
|
.strict();
|
|
694
|
-
// DELETE /api/v1/outline/:nodeId(软删 +
|
|
692
|
+
// DELETE /api/v1/outline/:nodeId(软删 + 递归级联)
|
|
695
693
|
export const outlineDeleteResSchema = z.object({
|
|
696
694
|
deleted: z.literal(true),
|
|
697
695
|
cascaded: z.object({
|
|
@@ -705,13 +703,13 @@ export const outlinePathResSchema = z.object({
|
|
|
705
703
|
nodeId: z.string(),
|
|
706
704
|
path: z.array(z.string()), // 如 ["root", "vol-1", "ch-3", "sc-15"]
|
|
707
705
|
});
|
|
708
|
-
// ============ trash
|
|
706
|
+
// ============ trash 端点(「回收站」) ============
|
|
709
707
|
// GET /api/v1/trash(deletedAt 为 camelCase——响应体约定)
|
|
710
708
|
export const trashListResSchema = z.object({
|
|
711
709
|
entities: z.array(z.object({ id: z.string(), type: z.string(), name: z.string(), deletedAt: z.string() })),
|
|
712
710
|
nodes: z.array(z.object({ id: z.string(), type: z.string(), title: z.string(), deletedAt: z.string() })),
|
|
713
711
|
});
|
|
714
|
-
// POST /api/v1/trash/entity/:type/:id/restore
|
|
712
|
+
// POST /api/v1/trash/entity/:type/:id/restore(级联还原)
|
|
715
713
|
export const trashRestoreEntityResSchema = z.object({
|
|
716
714
|
restored: z.literal(true),
|
|
717
715
|
restoredRelations: z.number().int(),
|
|
@@ -728,7 +726,7 @@ export const trashRestoreNodeResSchema = z.object({
|
|
|
728
726
|
export const trashPurgeResSchema = z.object({
|
|
729
727
|
purged: z.literal(true),
|
|
730
728
|
});
|
|
731
|
-
// ============ chat
|
|
729
|
+
// ============ chat 端点(「AI 对话」, 持久化) ============
|
|
732
730
|
// POST /api/v1/chat(POST + SSE;消息落 chat_messages 表)
|
|
733
731
|
export const chatSendReqSchema = z
|
|
734
732
|
.object({
|
|
@@ -762,11 +760,11 @@ export const chatMessagesResSchema = z.object({
|
|
|
762
760
|
role: z.enum(["user", "assistant", "tool"]),
|
|
763
761
|
content: z.string().nullable().optional(),
|
|
764
762
|
toolCalls: z.array(z.unknown()).optional(), // assistant 消息的工具调用数组
|
|
765
|
-
toolCallId: z.string().nullable().optional(), // tool 消息关联的调用 id
|
|
763
|
+
toolCallId: z.string().nullable().optional(), // tool 消息关联的调用 id
|
|
766
764
|
createdAt: z.string(),
|
|
767
765
|
})),
|
|
768
766
|
});
|
|
769
|
-
// ============ proposal
|
|
767
|
+
// ============ proposal 端点(「提案确认」) ============
|
|
770
768
|
// POST /api/v1/proposal/:proposalId/confirm(409 PROPOSAL_STALE / 404 PROPOSAL_NOT_FOUND / 409 PROPOSAL_PROJECT_MISMATCH)
|
|
771
769
|
export const proposalConfirmResSchema = z.object({
|
|
772
770
|
confirmed: z.literal(true),
|
|
@@ -776,8 +774,8 @@ export const proposalConfirmResSchema = z.object({
|
|
|
776
774
|
export const proposalRejectResSchema = z.object({
|
|
777
775
|
rejected: z.literal(true),
|
|
778
776
|
});
|
|
779
|
-
// ============ settings
|
|
780
|
-
/**
|
|
777
|
+
// ============ settings 端点(「系统设置」) ============
|
|
778
|
+
/** 思考强度(参考 pi 的 ThinkingLevel 档位——off / minimal / low / medium / high / xhigh / max;off = 不加 reasoning 参数) */
|
|
781
779
|
export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
782
780
|
/** 模型目录条目(GET /settings/llm 返回,供前端模型下拉与上下文占用分母) */
|
|
783
781
|
export const modelInfoSchema = z.object({
|
|
@@ -788,42 +786,55 @@ export const modelInfoSchema = z.object({
|
|
|
788
786
|
maxTokens: z.number(),
|
|
789
787
|
reasoning: z.boolean(),
|
|
790
788
|
});
|
|
791
|
-
|
|
792
|
-
export const
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
apiKeySet: z.boolean(),
|
|
789
|
+
/** 单 provider 条目(批次十六:GET /settings/llm providers[]——目录 + 该家有效 key 状态) */
|
|
790
|
+
export const settingsProviderSchema = z.object({
|
|
791
|
+
id: z.string(), // provider 目录 id(deepseek / opencode-go)
|
|
792
|
+
displayName: z.string(), // 分组标题(如 "OpenCode Go")
|
|
793
|
+
apiKeySet: z.boolean(), // 该家解析链(env > config api_keys > pi-agent auth.json)是否有有效 key
|
|
796
794
|
apiKeyMasked: z.string().optional(), // 掩码展示(utils/format.ts maskApiKey)
|
|
797
|
-
models: z.array(modelInfoSchema), //
|
|
795
|
+
models: z.array(modelInfoSchema), // 该家模型目录( getAvailableModels(provider))
|
|
798
796
|
});
|
|
799
|
-
//
|
|
797
|
+
// GET /api/v1/settings/llm(批次十六:多 provider——provider/model 激活一对 + 各家目录/key 状态;key 不回传明文)
|
|
798
|
+
export const settingsLlmGetResSchema = z.object({
|
|
799
|
+
provider: z.string(), // 激活 provider id(缺省 "deepseek")
|
|
800
|
+
model: z.string(), // 当前模型名(属于 provider 目录,缺省 "deepseek-v4-flash")
|
|
801
|
+
thinkingLevel: z.enum(THINKING_LEVELS), // 思考强度(全局,不分 provider)
|
|
802
|
+
providers: z.array(settingsProviderSchema), // 全量注册 provider(前端下拉分组/禁用依据)
|
|
803
|
+
});
|
|
804
|
+
// PUT /api/v1/settings/llm(写入 ~/.ai-editor/config.json,绝不入项目文件;批次十六 v2)
|
|
805
|
+
// 语义:provider + model 成对(跨 provider 激活);api_keys 写谁谁变、空串 = 清除该家;
|
|
806
|
+
// server 校验 model ∈ provider 目录(不符 → VALIDATION_ERROR);api_key 旧字段不再接受(前端已升 api_keys)
|
|
800
807
|
export const settingsLlmPutReqSchema = z
|
|
801
808
|
.object({
|
|
809
|
+
provider: z.string().optional(),
|
|
802
810
|
model: z.string().optional(),
|
|
803
811
|
thinking_level: z.enum(THINKING_LEVELS).optional(),
|
|
804
|
-
|
|
812
|
+
api_keys: z.record(z.string(), z.string()).optional(), // 各 provider key;空字符串 = 清除该家
|
|
805
813
|
})
|
|
806
814
|
.strict();
|
|
807
815
|
export const settingsLlmPutResSchema = z.object({
|
|
808
816
|
saved: z.literal(true),
|
|
809
817
|
});
|
|
810
|
-
// ============
|
|
811
|
-
// 契约来源:doc/design/decisions.md 决策 48、doc/api/endpoints.md「用户级配置文件」节。
|
|
818
|
+
// ============ 用户级配置文件(批次十四):~/.ai-editor/config.json schema v2(批次十六多 provider) ============
|
|
812
819
|
// 设计要点:
|
|
813
820
|
// - 非 strict(宽松读取):config.json 是用户自有文件,未来版本追加字段不应使整份配置失效
|
|
814
|
-
//
|
|
815
|
-
// - schema_version 可选:缺省 = v0
|
|
816
|
-
//
|
|
821
|
+
// (zod 默认 strip 未知字段,safeParse 仍成功)
|
|
822
|
+
// - schema_version 可选:缺省 = v0 旧格式;v0/v1 与 v2 均**读侧兼容不迁移不写回**
|
|
823
|
+
// (用户下次在设置页保存时自然落新格式);schema_version 非 1/2 的版本 → 整份失效(空配置默认值)
|
|
824
|
+
// - provider/api_keys 为 v2 字段:缺省 provider=deepseek(服务端解析时校验目录);
|
|
825
|
+
// 旧 api_key 字段读侧视为 api_keys["deepseek"]
|
|
817
826
|
// - 校验仅在服务端执行(settings.ts 消费);client 只消费推断类型
|
|
818
827
|
export const userConfigFileSchema = z
|
|
819
828
|
.object({
|
|
820
|
-
schema_version: z.literal(1).optional(), // 格式版本;缺省 = v0 旧格式(同结构兼容)
|
|
829
|
+
schema_version: z.union([z.literal(1), z.literal(2)]).optional(), // 格式版本;缺省 = v0 旧格式(同结构兼容)
|
|
830
|
+
provider: z.string().optional(), // 激活 provider id(缺省 deepseek;未知 id 由服务端解析兜底)
|
|
821
831
|
model: z.string().optional(), // 当前模型名(缺省 deepseek-v4-flash)
|
|
822
|
-
thinking_level: z.enum(THINKING_LEVELS).optional(), //
|
|
823
|
-
api_key: z.string().optional(), //
|
|
832
|
+
thinking_level: z.enum(THINKING_LEVELS).optional(), // 思考强度(缺省 high)
|
|
833
|
+
api_key: z.string().optional(), // v1 旧字段(读侧视为 api_keys["deepseek"],不写回)
|
|
834
|
+
api_keys: z.record(z.string(), z.string()).optional(), // v2:各 provider API key(不入项目文件)
|
|
824
835
|
})
|
|
825
836
|
.passthrough(); // 未知字段保留不校验(用户自有文件,未来版本追加字段不应使整份配置失效)
|
|
826
|
-
// ============ names
|
|
837
|
+
// ============ names 端点(「POST /api/v1/names/resolve」, 工具调用人类可读化) ============
|
|
827
838
|
// 批量名称解析:把工具参数中的 id 解析为人类可读名称(label = 类型中文,name = 实体名/节点标题)
|
|
828
839
|
// 前缀分流(char-/set-/loc-/hook-/ev-/tp-/ref- → 实体;vol-/ch-/sc- → 大纲节点;rel- → null;其余 → null)
|
|
829
840
|
export const namesResolveReqSchema = z
|
|
@@ -834,14 +845,14 @@ export const namesResolveReqSchema = z
|
|
|
834
845
|
export const namesResolveResSchema = z.object({
|
|
835
846
|
names: z.record(z.string(), z.object({ label: z.string(), name: z.string() }).nullable()),
|
|
836
847
|
});
|
|
837
|
-
// ============ SSE 事件(
|
|
838
|
-
/** 心跳 ping(每 15-30s
|
|
848
|
+
// ============ SSE 事件( chat 端点事件流,第 738-765 行) ============
|
|
849
|
+
/** 心跳 ping(每 15-30s):空 payload */
|
|
839
850
|
export const ssePingEventSchema = z.object({});
|
|
840
851
|
/** tool_call:AI 调用了工具 */
|
|
841
852
|
export const sseToolCallEventSchema = z.object({
|
|
842
853
|
tool: z.string(),
|
|
843
854
|
args: z.record(z.string(), z.unknown()),
|
|
844
|
-
id: z.string(), // call_
|
|
855
|
+
id: z.string(), // call_ 前缀( 成对重组依据)
|
|
845
856
|
});
|
|
846
857
|
/** tool_result:工具执行结果 */
|
|
847
858
|
export const sseToolResultEventSchema = z.object({
|
|
@@ -855,7 +866,7 @@ export const sseTextEventSchema = z.object({
|
|
|
855
866
|
});
|
|
856
867
|
/** proposal:AI 发出提案(完整预览仅经此事件推送 GUI,tool_result 不含预览,2026-08 修订) */
|
|
857
868
|
export const sseProposalEventSchema = z.object({
|
|
858
|
-
proposal_id: z.string(), // prop_
|
|
869
|
+
proposal_id: z.string(), // prop_ 前缀
|
|
859
870
|
type: z.string(), // 提案对应工具名(如 "propose_create_entity")
|
|
860
871
|
preview: z.unknown(),
|
|
861
872
|
});
|