@agent-phonon/protocol 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +70 -0
  3. package/dist/index.d.ts +24 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +24 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/json-schema/_index.json +3794 -0
  8. package/dist/json-schema/connect.hello.json +117 -0
  9. package/dist/json-schema/discovery.changed.json +190 -0
  10. package/dist/json-schema/discovery.get.json +183 -0
  11. package/dist/json-schema/discovery.list.json +182 -0
  12. package/dist/json-schema/document.prepare_upload.json +96 -0
  13. package/dist/json-schema/document.send.json +165 -0
  14. package/dist/json-schema/hook.fired.json +100 -0
  15. package/dist/json-schema/hook.resolve.json +96 -0
  16. package/dist/json-schema/interaction.cancel.json +48 -0
  17. package/dist/json-schema/interaction.request.json +376 -0
  18. package/dist/json-schema/interaction.response.json +68 -0
  19. package/dist/json-schema/project.create.json +89 -0
  20. package/dist/json-schema/project.get.json +76 -0
  21. package/dist/json-schema/project.git.deleteBranch.json +64 -0
  22. package/dist/json-schema/project.list.json +72 -0
  23. package/dist/json-schema/project.remove.json +69 -0
  24. package/dist/json-schema/project.worktree.create.json +88 -0
  25. package/dist/json-schema/project.worktree.list.json +77 -0
  26. package/dist/json-schema/project.worktree.remove.json +61 -0
  27. package/dist/json-schema/session.compress.json +72 -0
  28. package/dist/json-schema/session.create.json +128 -0
  29. package/dist/json-schema/session.inject.json +72 -0
  30. package/dist/json-schema/session.interrupt.json +58 -0
  31. package/dist/json-schema/session.list.json +127 -0
  32. package/dist/json-schema/session.send.json +132 -0
  33. package/dist/json-schema/session.status.json +94 -0
  34. package/dist/json-schema/session.switchModel.json +73 -0
  35. package/dist/json-schema/session.terminate.json +54 -0
  36. package/dist/json-schema/skill.install.json +171 -0
  37. package/dist/json-schema/skill.list.json +101 -0
  38. package/dist/json-schema/skill.uninstall.json +76 -0
  39. package/dist/json-schema/stream.ack.json +36 -0
  40. package/dist/json-schema/stream.event.json +381 -0
  41. package/dist/schemas/capabilities.d.ts +90 -0
  42. package/dist/schemas/capabilities.d.ts.map +1 -0
  43. package/dist/schemas/capabilities.js +59 -0
  44. package/dist/schemas/capabilities.js.map +1 -0
  45. package/dist/schemas/common.d.ts +66 -0
  46. package/dist/schemas/common.d.ts.map +1 -0
  47. package/dist/schemas/common.js +97 -0
  48. package/dist/schemas/common.js.map +1 -0
  49. package/dist/schemas/connect.d.ts +111 -0
  50. package/dist/schemas/connect.d.ts.map +1 -0
  51. package/dist/schemas/connect.js +46 -0
  52. package/dist/schemas/connect.js.map +1 -0
  53. package/dist/schemas/device.d.ts +221 -0
  54. package/dist/schemas/device.d.ts.map +1 -0
  55. package/dist/schemas/device.js +59 -0
  56. package/dist/schemas/device.js.map +1 -0
  57. package/dist/schemas/discovery.d.ts +892 -0
  58. package/dist/schemas/discovery.d.ts.map +1 -0
  59. package/dist/schemas/discovery.js +66 -0
  60. package/dist/schemas/discovery.js.map +1 -0
  61. package/dist/schemas/document.d.ts +351 -0
  62. package/dist/schemas/document.d.ts.map +1 -0
  63. package/dist/schemas/document.js +103 -0
  64. package/dist/schemas/document.js.map +1 -0
  65. package/dist/schemas/env.d.ts +265 -0
  66. package/dist/schemas/env.d.ts.map +1 -0
  67. package/dist/schemas/env.js +44 -0
  68. package/dist/schemas/env.js.map +1 -0
  69. package/dist/schemas/file.d.ts +274 -0
  70. package/dist/schemas/file.d.ts.map +1 -0
  71. package/dist/schemas/file.js +72 -0
  72. package/dist/schemas/file.js.map +1 -0
  73. package/dist/schemas/hook.d.ts +132 -0
  74. package/dist/schemas/hook.d.ts.map +1 -0
  75. package/dist/schemas/hook.js +58 -0
  76. package/dist/schemas/hook.js.map +1 -0
  77. package/dist/schemas/interaction.d.ts +1583 -0
  78. package/dist/schemas/interaction.d.ts.map +1 -0
  79. package/dist/schemas/interaction.js +112 -0
  80. package/dist/schemas/interaction.js.map +1 -0
  81. package/dist/schemas/jsonrpc.d.ts +314 -0
  82. package/dist/schemas/jsonrpc.d.ts.map +1 -0
  83. package/dist/schemas/jsonrpc.js +64 -0
  84. package/dist/schemas/jsonrpc.js.map +1 -0
  85. package/dist/schemas/methods.d.ts +3826 -0
  86. package/dist/schemas/methods.d.ts.map +1 -0
  87. package/dist/schemas/methods.js +311 -0
  88. package/dist/schemas/methods.js.map +1 -0
  89. package/dist/schemas/policy.d.ts +81 -0
  90. package/dist/schemas/policy.d.ts.map +1 -0
  91. package/dist/schemas/policy.js +66 -0
  92. package/dist/schemas/policy.js.map +1 -0
  93. package/dist/schemas/project.d.ts +506 -0
  94. package/dist/schemas/project.d.ts.map +1 -0
  95. package/dist/schemas/project.js +148 -0
  96. package/dist/schemas/project.js.map +1 -0
  97. package/dist/schemas/session.d.ts +730 -0
  98. package/dist/schemas/session.d.ts.map +1 -0
  99. package/dist/schemas/session.js +287 -0
  100. package/dist/schemas/session.js.map +1 -0
  101. package/dist/schemas/skill.d.ts +465 -0
  102. package/dist/schemas/skill.d.ts.map +1 -0
  103. package/dist/schemas/skill.js +103 -0
  104. package/dist/schemas/skill.js.map +1 -0
  105. package/dist/schemas/stream.d.ts +688 -0
  106. package/dist/schemas/stream.d.ts.map +1 -0
  107. package/dist/schemas/stream.js +133 -0
  108. package/dist/schemas/stream.js.map +1 -0
  109. package/package.json +52 -0
  110. package/src/index.ts +24 -0
  111. package/src/schemas/capabilities.ts +62 -0
  112. package/src/schemas/common.ts +119 -0
  113. package/src/schemas/connect.ts +50 -0
  114. package/src/schemas/device.ts +67 -0
  115. package/src/schemas/discovery.ts +80 -0
  116. package/src/schemas/document.ts +121 -0
  117. package/src/schemas/env.ts +60 -0
  118. package/src/schemas/file.ts +97 -0
  119. package/src/schemas/hook.ts +66 -0
  120. package/src/schemas/interaction.ts +135 -0
  121. package/src/schemas/jsonrpc.ts +80 -0
  122. package/src/schemas/methods.ts +414 -0
  123. package/src/schemas/policy.ts +71 -0
  124. package/src/schemas/project.ts +185 -0
  125. package/src/schemas/session.ts +336 -0
  126. package/src/schemas/skill.ts +121 -0
  127. package/src/schemas/stream.ts +149 -0
@@ -0,0 +1,121 @@
1
+ import { z } from "zod";
2
+ import { SessionId, Timestamp } from "./common.js";
3
+
4
+ /**
5
+ * 文档交换协议(design 平面③ / D20)。
6
+ *
7
+ * 场景:agent 需要/被要求发送本地文档时,在输出里 emit 一个**指令**(skill 教的格式,
8
+ * 见 DocumentDirective),phonon 解析后**负责读取本地文件**,打包成附件/其他形式发到服务端。
9
+ * 也就是说:agent 只说「路径」,读盘 + 传输由 phonon 这一层兜。
10
+ *
11
+ * 两个层次要分清:
12
+ * - DocumentDirective :agent ↔ phonon 的指令(含本地 path,skill 教)
13
+ * - document.send :phonon ↔ server 的线协议(含真实内容/附件)
14
+ */
15
+
16
+ /** 文档用途/形式。 */
17
+ export const DocumentKind = z.enum([
18
+ "attachment", // 通用附件
19
+ "document", // 富文本文档(如 .md → 服务端可转云文档)
20
+ "image", // 图片
21
+ "file", // 其他普通文件
22
+ ]);
23
+ export type DocumentKind = z.infer<typeof DocumentKind>;
24
+
25
+ /** 内容承载方式:小文件内联,大文件用 ref 走分块传输(具体待定)。 */
26
+ export const DocumentContent = z.union([
27
+ z.object({ encoding: z.literal("base64"), data: z.string() }),
28
+ z.object({ encoding: z.literal("utf8"), data: z.string() }),
29
+ z.object({ ref: z.string() }), // 分块传输句柄(大文件,开放问题)
30
+ ]);
31
+ export type DocumentContent = z.infer<typeof DocumentContent>;
32
+
33
+ /**
34
+ * agent 在输出里 emit 的指令格式(skill 教)——只含本地路径与元信息,
35
+ * phonon 据此读本地文件。**不直接进线协议。**
36
+ *
37
+ * 安全(D27 policy):默认 **project-scoped**——路径必须在绑定项目/worktree 目录内,
38
+ * 越界需 tenant policy 显式 `allowExternalDocuments`;命中 denyPathPatterns 一律拒。
39
+ */
40
+ export const DocumentDirective = z.object({
41
+ /** 文件路径(默认相对于项目/worktree 根;绝对路径需 policy 允许且在范围内)。 */
42
+ path: z.string().min(1),
43
+ /** 可选:覆盖文件名(默认取 path 的 basename)。 */
44
+ name: z.string().optional(),
45
+ kind: DocumentKind.optional(),
46
+ caption: z.string().optional(),
47
+ });
48
+ export type DocumentDirective = z.infer<typeof DocumentDirective>;
49
+
50
+ /** 单个文档的线上描述(phonon 读完本地文件后封装)。 */
51
+ export const DocumentDescriptor = z.object({
52
+ name: z.string().min(1),
53
+ /** 相对项目根的路径(审计/去重用;不暴露设备绝对路径)。 */
54
+ relativePath: z.string().optional(),
55
+ mimeType: z.string().optional(),
56
+ kind: DocumentKind.default("file"),
57
+ caption: z.string().optional(),
58
+ sizeBytes: z.number().int().nonnegative().optional(),
59
+ /** 内容 sha256(完整性校验 / 去重)。 */
60
+ sha256: z.string().optional(),
61
+ content: DocumentContent,
62
+ });
63
+ export type DocumentDescriptor = z.infer<typeof DocumentDescriptor>;
64
+
65
+ // --- document.send(phonon → server)---
66
+ export const DocumentSendParams = z.object({
67
+ /** 关联会话(通常有;纯设备级发送可省)。 */
68
+ sessionId: SessionId.optional(),
69
+ /** 关联轮次(若在某轮对话内触发)。 */
70
+ turnId: z.string().optional(),
71
+ documents: z.array(DocumentDescriptor).min(1),
72
+ at: Timestamp,
73
+ });
74
+ export type DocumentSendParams = z.infer<typeof DocumentSendParams>;
75
+
76
+ export const DocumentSendResult = z.object({
77
+ delivered: z.array(
78
+ z.object({
79
+ name: z.string(),
80
+ ok: z.boolean(),
81
+ /** 服务端落地引用(如云文档 token / 附件 id)。 */
82
+ serverRef: z.string().optional(),
83
+ error: z.string().optional(),
84
+ }),
85
+ ),
86
+ });
87
+ export type DocumentSendResult = z.infer<typeof DocumentSendResult>;
88
+
89
+ // ===========================================================================
90
+ // 大文件:凭证上传(P1-6 / Gemini#2)
91
+ // 不走 WS 发文件主体(避免内存暴涨/断线重传痛),而是:
92
+ // phonon → document.prepare_upload {filename,size,mime,sha256}
93
+ // server → 返回一个 HTTP 上传地址(预签名 URL / 一次性 token)
94
+ // phonon 本地跑标准 HTTP POST(multipart/流式/断点续传)
95
+ // 上传成功后用 document.send 用 ref 关联回 session
96
+ // ===========================================================================
97
+ export const DocumentPrepareUploadParams = z.object({
98
+ sessionId: SessionId.optional(),
99
+ turnId: z.string().optional(),
100
+ filename: z.string().min(1),
101
+ sizeBytes: z.number().int().nonnegative(),
102
+ mimeType: z.string().optional(),
103
+ sha256: z.string().optional(),
104
+ kind: DocumentKind.default("file"),
105
+ at: Timestamp,
106
+ });
107
+ export type DocumentPrepareUploadParams = z.infer<typeof DocumentPrepareUploadParams>;
108
+
109
+ export const DocumentPrepareUploadResult = z.object({
110
+ /** 上传句柄,上传成功后回填到 DocumentContent.ref。 */
111
+ uploadRef: z.string(),
112
+ /** server 给的 HTTP 上传地址(预签名 URL 等)。 */
113
+ uploadUrl: z.string(),
114
+ /** HTTP 方法(默认 PUT)。 */
115
+ method: z.enum(["PUT", "POST"]).default("PUT"),
116
+ /** 需携带的额外请求头。 */
117
+ headers: z.record(z.string()).optional(),
118
+ /** 上传地址过期时间。 */
119
+ expiresAt: Timestamp.optional(),
120
+ });
121
+ export type DocumentPrepareUploadResult = z.infer<typeof DocumentPrepareUploadResult>;
@@ -0,0 +1,60 @@
1
+ import { z } from "zod";
2
+ import { ProjectId, AgentId } from "./common.js";
3
+
4
+ /** Skill / agent 执行环境变量配置。明文存储在设备本地,list 默认脱敏。 */
5
+ export const EnvScope = z.enum(["global", "project", "skill"]);
6
+ export type EnvScope = z.infer<typeof EnvScope>;
7
+
8
+ const EnvTargetBase = z.object({
9
+ scope: EnvScope,
10
+ projectId: ProjectId.optional(),
11
+ agent: AgentId.optional(),
12
+ skillName: z.string().min(1).optional(),
13
+ });
14
+
15
+ function withEnvTargetRefine<T extends z.ZodTypeAny>(schema: T): T {
16
+ return schema
17
+ .refine((v: unknown) => (v as { scope?: string; projectId?: string }).scope !== "project" || !!(v as { projectId?: string }).projectId, { message: "project scope requires projectId", path: ["projectId"] })
18
+ .refine((v: unknown) => {
19
+ const x = v as { scope?: string; projectId?: string; agent?: string; skillName?: string };
20
+ return x.scope !== "skill" || (!!x.projectId && !!x.agent && !!x.skillName);
21
+ }, { message: "skill scope requires projectId+agent+skillName", path: ["skillName"] }) as unknown as T;
22
+ }
23
+
24
+ export const EnvTarget = withEnvTargetRefine(EnvTargetBase);
25
+ export type EnvTarget = z.infer<typeof EnvTargetBase>;
26
+
27
+ export const EnvVarDescriptor = withEnvTargetRefine(EnvTargetBase.extend({
28
+ name: z.string().min(1),
29
+ value: z.string().optional(),
30
+ redacted: z.boolean().default(true),
31
+ updatedAt: z.string().datetime({ offset: true }).optional(),
32
+ }));
33
+ export type EnvVarDescriptor = z.infer<typeof EnvVarDescriptor>;
34
+
35
+ export const EnvSetParams = withEnvTargetRefine(EnvTargetBase.extend({
36
+ clientRequestId: z.string().optional(),
37
+ name: z.string().min(1),
38
+ value: z.string(),
39
+ /** 可选:如果是 secret,list 默认永远脱敏。默认 true。 */
40
+ secret: z.boolean().default(true),
41
+ }));
42
+ export type EnvSetParams = z.infer<typeof EnvSetParams>;
43
+ export const EnvSetResult = z.object({ variable: EnvVarDescriptor });
44
+ export type EnvSetResult = z.infer<typeof EnvSetResult>;
45
+
46
+ export const EnvListParams = EnvTargetBase.partial().extend({
47
+ /** 默认 false;true 需要本地 policy allowEnvReveal。 */
48
+ reveal: z.boolean().default(false),
49
+ });
50
+ export type EnvListParams = z.infer<typeof EnvListParams>;
51
+ export const EnvListResult = z.object({ variables: z.array(EnvVarDescriptor) });
52
+ export type EnvListResult = z.infer<typeof EnvListResult>;
53
+
54
+ export const EnvDeleteParams = withEnvTargetRefine(EnvTargetBase.extend({
55
+ clientRequestId: z.string().optional(),
56
+ name: z.string().min(1),
57
+ }));
58
+ export type EnvDeleteParams = z.infer<typeof EnvDeleteParams>;
59
+ export const EnvDeleteResult = z.object({ deleted: z.literal(true), name: z.string() });
60
+ export type EnvDeleteResult = z.infer<typeof EnvDeleteResult>;
@@ -0,0 +1,97 @@
1
+ import { z } from "zod";
2
+ import { ProjectId } from "./common.js";
3
+
4
+ /**
5
+ * 受控工作区文件读写协议。
6
+ *
7
+ * 与 document.send 不同:document.send 是 agent 主动把产物发给 server;
8
+ * file.* 是 server 主动读写本地 project/worktree 内文件。
9
+ */
10
+
11
+ export const FileEncoding = z.enum(["utf8", "base64"]);
12
+ export type FileEncoding = z.infer<typeof FileEncoding>;
13
+
14
+ export const FileScope = z.object({
15
+ projectId: ProjectId,
16
+ /** 可选:指定 worktree;缺省为项目主目录。 */
17
+ worktreeId: z.string().min(1).optional(),
18
+ });
19
+ export type FileScope = z.infer<typeof FileScope>;
20
+
21
+ const RelativePath = z.string().min(1).refine((p) => !p.startsWith("/") && !p.includes("\0"), {
22
+ message: "path must be a relative path",
23
+ });
24
+
25
+ export const FileStat = z.object({
26
+ path: z.string(),
27
+ type: z.enum(["file", "directory", "symlink", "other"]),
28
+ sizeBytes: z.number().nonnegative(),
29
+ modifiedAt: z.string().datetime({ offset: true }).optional(),
30
+ });
31
+ export type FileStat = z.infer<typeof FileStat>;
32
+
33
+ export const FileReadParams = FileScope.extend({
34
+ path: RelativePath,
35
+ encoding: FileEncoding.default("utf8"),
36
+ /** 可选:最大读取字节数,避免误读超大文件。 */
37
+ maxBytes: z.number().int().positive().optional(),
38
+ });
39
+ export type FileReadParams = z.infer<typeof FileReadParams>;
40
+
41
+ export const FileReadResult = z.object({
42
+ path: z.string(),
43
+ encoding: FileEncoding,
44
+ data: z.string(),
45
+ sizeBytes: z.number().nonnegative(),
46
+ truncated: z.boolean().default(false),
47
+ });
48
+ export type FileReadResult = z.infer<typeof FileReadResult>;
49
+
50
+ export const FileWriteParams = FileScope.extend({
51
+ /** 可选:幂等键。 */
52
+ clientRequestId: z.string().optional(),
53
+ path: RelativePath,
54
+ encoding: FileEncoding.default("utf8"),
55
+ data: z.string(),
56
+ /** 缺省 true;false 时若文件已存在则报错。 */
57
+ overwrite: z.boolean().default(true),
58
+ /** 是否自动创建父目录。 */
59
+ createDirs: z.boolean().default(true),
60
+ });
61
+ export type FileWriteParams = z.infer<typeof FileWriteParams>;
62
+
63
+ export const FileWriteResult = z.object({
64
+ path: z.string(),
65
+ sizeBytes: z.number().nonnegative(),
66
+ written: z.literal(true),
67
+ });
68
+ export type FileWriteResult = z.infer<typeof FileWriteResult>;
69
+
70
+ export const FileListParams = FileScope.extend({
71
+ path: RelativePath.default("."),
72
+ recursive: z.boolean().default(false),
73
+ limit: z.number().int().positive().max(10000).default(500),
74
+ });
75
+ export type FileListParams = z.infer<typeof FileListParams>;
76
+
77
+ export const FileListResult = z.object({
78
+ path: z.string(),
79
+ entries: z.array(FileStat),
80
+ truncated: z.boolean().default(false),
81
+ });
82
+ export type FileListResult = z.infer<typeof FileListResult>;
83
+
84
+ export const FileStatParams = FileScope.extend({ path: RelativePath });
85
+ export type FileStatParams = z.infer<typeof FileStatParams>;
86
+ export const FileStatResult = z.object({ stat: FileStat });
87
+ export type FileStatResult = z.infer<typeof FileStatResult>;
88
+
89
+ export const FileMkdirParams = FileScope.extend({
90
+ /** 可选:幂等键。 */
91
+ clientRequestId: z.string().optional(),
92
+ path: RelativePath,
93
+ recursive: z.boolean().default(true),
94
+ });
95
+ export type FileMkdirParams = z.infer<typeof FileMkdirParams>;
96
+ export const FileMkdirResult = z.object({ path: z.string(), created: z.literal(true) });
97
+ export type FileMkdirResult = z.infer<typeof FileMkdirResult>;
@@ -0,0 +1,66 @@
1
+ import { z } from "zod";
2
+ import { SessionId, Timestamp } from "./common.js";
3
+ import { HookType } from "./capabilities.js";
4
+ import { ContextItem } from "./session.js";
5
+
6
+ /**
7
+ * Hook / 人在回路 HITL(design §8)。
8
+ *
9
+ * 核心:phonon 自己不实现人在回路,只做事件中转 + 阻塞等裁决。
10
+ * phonon → server : hook.fired (到 hook 点抛事件)
11
+ * server → phonon : hook.resolve (裁决;服务端决定是否问真人、怎么问、等多久)
12
+ */
13
+
14
+ /** hook 触发载荷(phonon → server)。 */
15
+ export const HookFiredParams = z.object({
16
+ sessionId: SessionId,
17
+ /** 本次 hook 的唯一 id,hook.resolve 用它配对。 */
18
+ hookId: z.string().min(1),
19
+ hookType: HookType,
20
+ /** 触发上下文:被拦截的操作详情(命令、工具名、参数、文件路径等)。 */
21
+ payload: z
22
+ .object({
23
+ toolName: z.string().optional(),
24
+ command: z.string().optional(),
25
+ filePath: z.string().optional(),
26
+ url: z.string().optional(),
27
+ /** 其余 adapter 特定字段。 */
28
+ extra: z.record(z.unknown()).optional(),
29
+ })
30
+ .default({}),
31
+ /** 该 turn 的关联 id(若在某轮对话内触发)。 */
32
+ turnId: z.string().optional(),
33
+ at: Timestamp,
34
+ });
35
+ export type HookFiredParams = z.infer<typeof HookFiredParams>;
36
+
37
+ /**
38
+ * 裁决动作(server → phonon)。
39
+ * - continue : 放行,照常执行
40
+ * - inject : 先注入上下文再继续(用 context 字段)
41
+ * - modify : 用修改后的参数继续(用 patch 字段,如改写命令)
42
+ * - abort : 中止该操作(可带原因)
43
+ */
44
+ export const HookAction = z.enum(["continue", "inject", "modify", "abort"]);
45
+ export type HookAction = z.infer<typeof HookAction>;
46
+
47
+ export const HookResolveParams = z.object({
48
+ sessionId: SessionId,
49
+ /** 必须与对应 hook.fired 的 hookId 一致。 */
50
+ hookId: z.string().min(1),
51
+ action: HookAction,
52
+ /** action=inject 时:要注入的上下文。 */
53
+ context: z.array(ContextItem).optional(),
54
+ /** action=modify 时:对被拦截操作的修改(结构与该 hook 的 payload 对应)。 */
55
+ patch: z.record(z.unknown()).optional(),
56
+ /** action=abort 时:可选原因,会回传给 agent / 记录。 */
57
+ reason: z.string().optional(),
58
+ });
59
+ export type HookResolveParams = z.infer<typeof HookResolveParams>;
60
+
61
+ export const HookResolveResult = z.object({
62
+ sessionId: SessionId,
63
+ hookId: z.string(),
64
+ applied: z.boolean(),
65
+ });
66
+ export type HookResolveResult = z.infer<typeof HookResolveResult>;
@@ -0,0 +1,135 @@
1
+ import { z } from "zod";
2
+ import { SessionId, Timestamp } from "./common.js";
3
+
4
+ /**
5
+ * 人机交互协议(design 平面③ / D21)。
6
+ *
7
+ * 场景:HITL 时、或 agent 主动想问人,发一个**可交互卡片/表单**(抽象结构),
8
+ * phonon → server 渲染给人(飞书卡片/网页/TG 按钮…由服务端决定,复用「甩锅服务端」原则),
9
+ * 人填完 → 原路返回 → 注入回 agent。
10
+ *
11
+ * 两个层次:
12
+ * - InteractionDirective :agent emit 的表单定义(skill 教,让任何模型都能发)
13
+ * - interaction.request :phonon → server 线协议(带 requestId,阻塞等回填)
14
+ * - interaction.response :server → phonon 线协议(人填完的值)
15
+ *
16
+ * 表单只定义**抽象字段结构**,不绑定任何渲染方式——服务端自由渲染。
17
+ */
18
+
19
+ /** 字段类型(抽象,渲染由服务端决定)。 */
20
+ export const FieldType = z.enum([
21
+ "text", // 单行文本
22
+ "textarea", // 多行文本
23
+ "number",
24
+ "boolean", // 开关/勾选
25
+ "select", // 单选
26
+ "multiselect", // 多选
27
+ "date",
28
+ ]);
29
+ export type FieldType = z.infer<typeof FieldType>;
30
+
31
+ export const FormFieldOption = z.object({
32
+ label: z.string(),
33
+ value: z.string(),
34
+ });
35
+ export type FormFieldOption = z.infer<typeof FormFieldOption>;
36
+
37
+ /**
38
+ * 表单字段——按 type 的 discriminated union(P2-16)。
39
+ * 每种类型携自己的 defaultValue 类型与专属字段,避免「select 该传 string 还是 string[]」的歧义。
40
+ */
41
+ const FieldBase = { key: z.string().min(1), label: z.string().min(1), required: z.boolean().default(false), help: z.string().optional() };
42
+
43
+ export const FormField = z.discriminatedUnion("type", [
44
+ z.object({ ...FieldBase, type: z.literal("text"), placeholder: z.string().optional(), defaultValue: z.string().optional() }),
45
+ z.object({ ...FieldBase, type: z.literal("textarea"), placeholder: z.string().optional(), defaultValue: z.string().optional() }),
46
+ z.object({ ...FieldBase, type: z.literal("number"), defaultValue: z.number().optional() }),
47
+ z.object({ ...FieldBase, type: z.literal("boolean"), defaultValue: z.boolean().optional() }),
48
+ z.object({ ...FieldBase, type: z.literal("select"), options: z.array(FormFieldOption), defaultValue: z.string().optional() }),
49
+ z.object({ ...FieldBase, type: z.literal("multiselect"), options: z.array(FormFieldOption), defaultValue: z.array(z.string()).optional() }),
50
+ z.object({ ...FieldBase, type: z.literal("date"), defaultValue: z.string().optional() }),
51
+ ]);
52
+ export type FormField = z.infer<typeof FormField>;
53
+
54
+ /** 表单/卡片定义(抽象)。 */
55
+ export const InteractionForm = z.object({
56
+ title: z.string().min(1),
57
+ /** 卡片正文/说明(可选)。 */
58
+ description: z.string().optional(),
59
+ fields: z.array(FormField).default([]),
60
+ /** 提交/操作按钮;纯通知类可只放一个「知道了」。 */
61
+ submitLabel: z.string().default("提交"),
62
+ cancelLabel: z.string().optional(),
63
+ });
64
+ export type InteractionForm = z.infer<typeof InteractionForm>;
65
+
66
+ /**
67
+ * agent emit 的指令(skill 教)——和 interaction.request 结构基本一致,
68
+ * 但不带 requestId(由 phonon 生成)。
69
+ */
70
+ export const InteractionDirective = z.object({
71
+ form: InteractionForm,
72
+ /** 是否阻塞等待人回填(true=问答;false=纯通知不等)。 */
73
+ blocking: z.boolean().default(true),
74
+ /** 关联 hookId(若由 HITL hook 触发,便于和 hook.resolve 合流)。 */
75
+ hookId: z.string().optional(),
76
+ });
77
+ export type InteractionDirective = z.infer<typeof InteractionDirective>;
78
+
79
+ // --- interaction.request(phonon → server)---
80
+ export const InteractionRequestParams = z.object({
81
+ /** 本次交互唯一 id,response 用它配对。 */
82
+ requestId: z.string().min(1),
83
+ sessionId: SessionId.optional(),
84
+ turnId: z.string().optional(),
85
+ form: InteractionForm,
86
+ blocking: z.boolean().default(true),
87
+ /** 关联 hookId(HITL 场景)。 */
88
+ hookId: z.string().optional(),
89
+ /**
90
+ * 可选超时(秒,P1-5)。超时后 phonon 以 timeout 收尾本次交互,agent 按预设继续。
91
+ * 0 或缺省 = 不超时(人可能去开会/带娃,长期挂起)。
92
+ */
93
+ timeoutSeconds: z.number().int().nonnegative().optional(),
94
+ at: Timestamp,
95
+ });
96
+ export type InteractionRequestParams = z.infer<typeof InteractionRequestParams>;
97
+
98
+ /** 人机交互的生命周期状态(P1-5):pending → submitted | cancelled | timeout。 */
99
+ export const InteractionStatus = z.enum(["pending", "submitted", "cancelled", "timeout"]);
100
+ export type InteractionStatus = z.infer<typeof InteractionStatus>;
101
+
102
+ /** 阻塞模式下,request 的最终结果(人填完后服务端回的)。 */
103
+ export const InteractionRequestResult = z.object({
104
+ requestId: z.string(),
105
+ /** 结果状态:submit | cancel | timeout。 */
106
+ action: z.enum(["submit", "cancel", "timeout"]),
107
+ /** 提交时各字段值(key → 值)。 */
108
+ values: z.record(z.union([z.string(), z.array(z.string()), z.boolean(), z.number()])).optional(),
109
+ });
110
+ export type InteractionRequestResult = z.infer<typeof InteractionRequestResult>;
111
+
112
+ // --- interaction.cancel(server → phonon 或 phonon 内部):主动取消一个 pending 交互 ---
113
+ export const InteractionCancelParams = z.object({
114
+ requestId: z.string().min(1),
115
+ reason: z.string().optional(),
116
+ });
117
+ export type InteractionCancelParams = z.infer<typeof InteractionCancelParams>;
118
+
119
+ export const InteractionCancelResult = z.object({
120
+ requestId: z.string(),
121
+ cancelled: z.boolean(),
122
+ });
123
+ export type InteractionCancelResult = z.infer<typeof InteractionCancelResult>;
124
+
125
+ /**
126
+ * server → phonon 的回填(非阻塞模式或异步回填走这个 notification;
127
+ * 阻塞模式可直接用 interaction.request 的 result 返回,二选一,实现可都支持)。
128
+ */
129
+ export const InteractionResponseParams = z.object({
130
+ requestId: z.string().min(1),
131
+ action: z.enum(["submit", "cancel", "timeout"]),
132
+ values: z.record(z.union([z.string(), z.array(z.string()), z.boolean(), z.number()])).optional(),
133
+ at: Timestamp,
134
+ });
135
+ export type InteractionResponseParams = z.infer<typeof InteractionResponseParams>;
@@ -0,0 +1,80 @@
1
+ import { z } from "zod";
2
+ import { PhononErrorData } from "./common.js";
3
+
4
+ /**
5
+ * JSON-RPC 2.0 信封(design D2)。
6
+ *
7
+ * 单条 WebSocket 上双向跑 JSON-RPC 2.0:两端皆可作 requester。
8
+ * - server → phonon:session.* / discovery.* / hook.resolve
9
+ * - phonon → server:stream.event / hook.fired / discovery.changed / connect.hello
10
+ *
11
+ * 这里只定义「信封」的通用形状;具体方法的 params/result 由 methods.ts 绑定。
12
+ */
13
+
14
+ export const JsonRpcVersion = z.literal("2.0");
15
+
16
+ /** 请求 id:字符串或数字(JSON-RPC 允许两者;notification 无 id)。 */
17
+ export const JsonRpcId = z.union([z.string(), z.number()]);
18
+ export type JsonRpcId = z.infer<typeof JsonRpcId>;
19
+
20
+ /** 请求(需要响应)。 */
21
+ export const JsonRpcRequest = z.object({
22
+ jsonrpc: JsonRpcVersion,
23
+ id: JsonRpcId,
24
+ method: z.string(),
25
+ params: z.unknown().optional(),
26
+ });
27
+ export type JsonRpcRequest = z.infer<typeof JsonRpcRequest>;
28
+
29
+ /** 通知(不需要响应,无 id)——用于 stream.event / hook.fired / discovery.changed。 */
30
+ export const JsonRpcNotification = z.object({
31
+ jsonrpc: JsonRpcVersion,
32
+ method: z.string(),
33
+ params: z.unknown().optional(),
34
+ });
35
+ export type JsonRpcNotification = z.infer<typeof JsonRpcNotification>;
36
+
37
+ /** 成功响应。 */
38
+ export const JsonRpcSuccess = z.object({
39
+ jsonrpc: JsonRpcVersion,
40
+ id: JsonRpcId,
41
+ result: z.unknown(),
42
+ });
43
+ export type JsonRpcSuccess = z.infer<typeof JsonRpcSuccess>;
44
+
45
+ /** JSON-RPC error 对象;data 携带 phonon 应用级错误结构。 */
46
+ export const JsonRpcErrorObject = z.object({
47
+ /** JSON-RPC 传输级 code(-32700..-32600 保留;应用错误用 data.appCode 判别)。 */
48
+ code: z.number().int(),
49
+ message: z.string(),
50
+ data: PhononErrorData.optional(),
51
+ });
52
+ export type JsonRpcErrorObject = z.infer<typeof JsonRpcErrorObject>;
53
+
54
+ /** 失败响应。 */
55
+ export const JsonRpcError = z.object({
56
+ jsonrpc: JsonRpcVersion,
57
+ id: JsonRpcId.nullable(),
58
+ error: JsonRpcErrorObject,
59
+ });
60
+ export type JsonRpcError = z.infer<typeof JsonRpcError>;
61
+
62
+ /** 任意一条 JSON-RPC 报文。 */
63
+ export const JsonRpcMessage = z.union([
64
+ JsonRpcRequest,
65
+ JsonRpcNotification,
66
+ JsonRpcSuccess,
67
+ JsonRpcError,
68
+ ]);
69
+ export type JsonRpcMessage = z.infer<typeof JsonRpcMessage>;
70
+
71
+ /** 标准 JSON-RPC 传输级错误码。 */
72
+ export const JSON_RPC_CODES = {
73
+ parseError: -32700,
74
+ invalidRequest: -32600,
75
+ methodNotFound: -32601,
76
+ invalidParams: -32602,
77
+ internalError: -32603,
78
+ /** 应用级错误统一用这个 code,细分看 data.appCode。 */
79
+ applicationError: -32000,
80
+ } as const;