gx-code-agent 0.0.0-stage → 0.1.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 (45) hide show
  1. package/README.md +276 -2
  2. package/bin/gx-stdio-mcp.js +145 -0
  3. package/bin/gx.js +99 -0
  4. package/contracts/README.md +40 -0
  5. package/contracts/alignment.schema.json +99 -0
  6. package/contracts/code-chunk.schema.json +28 -0
  7. package/contracts/evidence.schema.json +108 -0
  8. package/contracts/examples/alignment.example.json +26 -0
  9. package/contracts/examples/code-chunk.example.json +20 -0
  10. package/contracts/examples/evidence.add-field.json +49 -0
  11. package/contracts/examples/evidence.verify-manifest.json +52 -0
  12. package/contracts/examples/ext-method.example.json +14 -0
  13. package/contracts/examples/manifest.example.json +13 -0
  14. package/contracts/examples/unknown.extension-coverage.json +29 -0
  15. package/contracts/ext-method.schema.json +57 -0
  16. package/contracts/manifest.schema.json +27 -0
  17. package/contracts/unknown.schema.json +119 -0
  18. package/eval/README.md +52 -0
  19. package/eval/code-search-eval.json +216 -0
  20. package/package.json +37 -4
  21. package/src/adapters/cli/args.js +94 -0
  22. package/src/adapters/cli/output.js +66 -0
  23. package/src/adapters/platform/mcpClient.js +202 -0
  24. package/src/application/applyWorkflow.js +138 -0
  25. package/src/application/auditCommand.js +130 -0
  26. package/src/application/auditWorkflow.js +41 -0
  27. package/src/application/chunksCommand.js +87 -0
  28. package/src/application/errors.js +27 -0
  29. package/src/application/evalSearchCommand.js +148 -0
  30. package/src/application/initWorkflow.js +563 -0
  31. package/src/application/pullWorkflow.js +74 -0
  32. package/src/application/scanCommand.js +71 -0
  33. package/src/application/scanWorkflow.js +689 -0
  34. package/src/application/verifyWorkflow.js +142 -0
  35. package/src/domain/canonicalJson.js +72 -0
  36. package/src/domain/chunker.js +297 -0
  37. package/src/domain/digest.js +60 -0
  38. package/src/domain/java/JavaAstChunker.java +139 -0
  39. package/src/domain/java/JavaAstScanner.java +107 -0
  40. package/src/domain/javaAst.js +186 -0
  41. package/src/domain/jdk.js +66 -0
  42. package/src/domain/keepBlocks.js +122 -0
  43. package/src/store/backupStore.js +106 -0
  44. package/src/store/localStore.js +79 -0
  45. package/templates/platform-rules.md +29 -0
@@ -0,0 +1,216 @@
1
+ {
2
+ "planId": "1187804490695311360",
3
+ "description": "wk1 真实方案 M5 混合检索评测集(关键词 + 语义 + 标签)",
4
+ "cases": [
5
+ {
6
+ "id": "c01",
7
+ "query": "故障",
8
+ "tags": [
9
+ "keyword"
10
+ ],
11
+ "expectedAny": [
12
+ {
13
+ "path": "defaultService/src/main/java/gx/cn01/demo1/controller/WkMalfunctionController.java"
14
+ }
15
+ ]
16
+ },
17
+ {
18
+ "id": "c02",
19
+ "query": "故障开始时间",
20
+ "tags": [
21
+ "keyword"
22
+ ],
23
+ "expectedAny": [
24
+ {
25
+ "path": "defaultService/src/main/java/gx/cn01/demo1/pojo/po/WkMalfunctionPO.java"
26
+ },
27
+ {
28
+ "path": "defaultService/src/main/java/gx/cn01/demo1/pojo/dto/WkMalfunctionDTO.java"
29
+ },
30
+ {
31
+ "path": "defaultService/src/main/java/gx/cn01/demo1/pojo/vo/WkMalfunctionVO.java"
32
+ }
33
+ ]
34
+ },
35
+ {
36
+ "id": "c03",
37
+ "query": "传感器",
38
+ "tags": [
39
+ "keyword"
40
+ ],
41
+ "expectedAny": [
42
+ {
43
+ "path": "defaultService/src/main/java/gx/cn01/demo1/controller/WkSensorMappingController.java"
44
+ },
45
+ {
46
+ "path": "defaultService/src/main/java/gx/cn01/demo1/controller/WkProcSensorRealtimeController.java"
47
+ }
48
+ ]
49
+ },
50
+ {
51
+ "id": "c04",
52
+ "query": "项目",
53
+ "tags": [
54
+ "keyword"
55
+ ],
56
+ "expectedAny": [
57
+ {
58
+ "path": "defaultService/src/main/java/gx/cn01/demo1/pojo/po/WkProjectPO.java"
59
+ },
60
+ {
61
+ "pathSuffix": "wkProjectTreeLeftMenu.vue"
62
+ },
63
+ {
64
+ "path": "defaultService/src/main/java/gx/cn01/demo1/controller/WkProjectController.java"
65
+ }
66
+ ]
67
+ },
68
+ {
69
+ "id": "c05",
70
+ "query": "MQTT 指令",
71
+ "tags": [
72
+ "keyword"
73
+ ],
74
+ "expectedAny": [
75
+ {
76
+ "path": "defaultService/src/main/java/gx/cn01/demo1/controller/WkMqttCommandController.java"
77
+ }
78
+ ]
79
+ },
80
+ {
81
+ "id": "c06",
82
+ "query": "浇筑体",
83
+ "tags": [
84
+ "keyword"
85
+ ],
86
+ "expectedAny": [
87
+ {
88
+ "path": "defaultService/src/main/java/gx/cn01/demo1/controller/WkPouringBodyController.java"
89
+ },
90
+ {
91
+ "path": "defaultService/src/main/java/gx/cn01/demo1/pojo/po/WkPouringBodyPO.java"
92
+ }
93
+ ]
94
+ },
95
+ {
96
+ "id": "c07",
97
+ "query": "水温",
98
+ "tags": [
99
+ "keyword"
100
+ ],
101
+ "expectedAny": [
102
+ {
103
+ "path": "defaultService/src/main/java/gx/cn01/demo1/controller/WkCalcWaterCoolController.java"
104
+ }
105
+ ]
106
+ },
107
+ {
108
+ "id": "c08",
109
+ "query": "管路",
110
+ "tags": [
111
+ "keyword"
112
+ ],
113
+ "expectedAny": [
114
+ {
115
+ "path": "defaultService/src/main/java/gx/cn01/demo1/controller/WkPipeController.java"
116
+ },
117
+ {
118
+ "path": "defaultService/src/main/java/gx/cn01/demo1/controller/WkCalcWaterCoolController.java"
119
+ }
120
+ ]
121
+ },
122
+ {
123
+ "id": "c09",
124
+ "query": "报错之前设置默认值",
125
+ "tags": [
126
+ "semantic"
127
+ ],
128
+ "expectedAny": [
129
+ {
130
+ "signature": "WkMalfunctionHook.fillDefaultName()"
131
+ },
132
+ {
133
+ "path": "src/main/java/demo/WkMalfunctionHook.java"
134
+ }
135
+ ]
136
+ },
137
+ {
138
+ "id": "c10",
139
+ "query": "3D 管道配置",
140
+ "tags": [
141
+ "keyword"
142
+ ],
143
+ "expectedAny": [
144
+ {
145
+ "path": "defaultService/src/main/java/gx/cn01/demo1/controller/WkPipe3dConfigController.java"
146
+ },
147
+ {
148
+ "path": "defaultService/src/main/java/gx/cn01/demo1/service/impl/WkPipe3dConfigServiceImpl.java"
149
+ }
150
+ ]
151
+ },
152
+ {
153
+ "id": "c11",
154
+ "query": "实时温度",
155
+ "tags": [
156
+ "keyword"
157
+ ],
158
+ "expectedAny": [
159
+ {
160
+ "path": "defaultService/src/main/java/gx/cn01/demo1/controller/WkProcSensorRealtimeController.java"
161
+ }
162
+ ]
163
+ },
164
+ {
165
+ "id": "c12",
166
+ "query": "开孔",
167
+ "tags": [
168
+ "keyword"
169
+ ],
170
+ "expectedAny": [
171
+ {
172
+ "path": "defaultService/src/main/java/gx/cn01/demo1/controller/WkProcOpeningController.java"
173
+ },
174
+ {
175
+ "path": "defaultService/src/main/java/gx/cn01/demo1/pojo/dto/WkProcOpeningDTO.java"
176
+ },
177
+ {
178
+ "path": "defaultService/src/main/java/gx/cn01/demo1/service/impl/WkProcOpeningServiceImpl.java"
179
+ }
180
+ ]
181
+ },
182
+ {
183
+ "id": "c13",
184
+ "query": "分页查询",
185
+ "tags": [
186
+ "keyword"
187
+ ],
188
+ "expectedAny": [
189
+ {
190
+ "path": "defaultService/src/main/java/gx/cn01/demo1/controller/WkProcLayerDiffController.java"
191
+ },
192
+ {
193
+ "path": "defaultService/src/main/java/gx/cn01/demo1/controller/WkPipe3dSegmentController.java"
194
+ },
195
+ {
196
+ "path": "defaultService/src/main/java/gx/cn01/demo1/controller/WkProcPipeDiffController.java"
197
+ }
198
+ ]
199
+ },
200
+ {
201
+ "id": "c14",
202
+ "query": "树形左侧菜单",
203
+ "tags": [
204
+ "keyword"
205
+ ],
206
+ "expectedAny": [
207
+ {
208
+ "pathSuffix": "wkMalfunctionTreeLeftMenu.vue"
209
+ },
210
+ {
211
+ "pathSuffix": "wkProjectTreeLeftMenu.vue"
212
+ }
213
+ ]
214
+ }
215
+ ]
216
+ }
package/package.json CHANGED
@@ -1,6 +1,39 @@
1
1
  {
2
2
  "name": "gx-code-agent",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "GX 高代码平台伴生 CLI:注册 MCP、注入平台规范、校验生成物、本地 stdio MCP",
5
+ "type": "module",
6
+ "bin": {
7
+ "gx": "bin/gx.js",
8
+ "gx-mcp-local": "bin/gx-stdio-mcp.js"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "src",
13
+ "contracts",
14
+ "eval",
15
+ "templates",
16
+ "README.md"
17
+ ],
18
+ "engines": {
19
+ "node": ">=18"
20
+ },
21
+ "scripts": {
22
+ "test": "node --test",
23
+ "test:contracts": "node --test test/contracts.test.js",
24
+ "pack:check": "npm pack --dry-run && npm pack ./packages/gx-mcp-local --dry-run",
25
+ "publish:check": "npm test && npm pack --dry-run && npm pack ./packages/gx-mcp-local --dry-run",
26
+ "prepublishOnly": "npm test",
27
+ "publish:public": "node script/publish-public.mjs",
28
+ "publish:public:otp": "node script/publish-public.mjs"
29
+ },
30
+ "devDependencies": {
31
+ "ajv": "^8.17.1",
32
+ "ajv-formats": "^3.0.1"
33
+ },
34
+ "license": "UNLICENSED",
35
+ "publishConfig": {
36
+ "registry": "https://registry.npmjs.org/",
37
+ "access": "public"
38
+ }
39
+ }
@@ -0,0 +1,94 @@
1
+ import { UsageError } from "../../application/errors.js";
2
+
3
+ export function usage() {
4
+ console.log(`gx-code-agent 0.1.0
5
+
6
+ 用法:
7
+ gx init [选项]
8
+
9
+ 选项:
10
+ --client <opencode|claude|cline> 客户端类型,默认 opencode
11
+ --url <url> MCP Server 地址,默认 http://127.0.0.1:4013/mcp
12
+ --token <token> API Token;也可使用环境变量 GX_API_TOKEN
13
+ --token-env <name> 写入配置的环境变量名,默认 GX_API_TOKEN
14
+ --embed-token 将明文 Token 写入配置(默认写环境变量引用,更安全)
15
+ --plan <planId> 可选,记录到 .gx/config.json 并写入 AGENTS.md
16
+ --template <templateId> 可选,记录到 .gx/config.json 并写入 AGENTS.md
17
+ --generated-dir <dir> 可选,写入 AGENTS.md 的生成层目录
18
+ --dir <path> 目标项目目录,默认当前目录
19
+ --yes 非交互模式确认写入;未提供且无 TTY 时退出码 2
20
+ --dry-run 只打印将要写入的内容与 diff,不落盘
21
+ --check 只检查配置与连接,不写入;需要更新时退出码 1
22
+ --with-local 同时注册本地 stdio MCP gx-local
23
+ --opencode-format <legacy|v2> OpenCode 配置格式,默认 legacy
24
+ --timeout <ms> OpenCode 远端 MCP 超时,默认 180000
25
+ --skip-verify 跳过 init 后的 ping/tools/list 连接检查
26
+ --force gx apply 时覆盖已手改的生成层
27
+ --help 显示帮助
28
+
29
+ 命令:
30
+ gx init 注册 MCP + 注入平台规范
31
+ gx pull 从平台拉取生成文件到 .gx/generated 与 .gx/manifest.json
32
+ gx apply 将 .gx/generated 应用到项目并保留自定义块
33
+ gx verify 校验项目文件与 .gx/manifest.json 是否一致
34
+ gx scan 解析 @ExtMethod;配置了 planId + Token 时上报 record_ext_points
35
+ gx audit 上报扩展点并调用 audit_alignment,合并本地/平台 Unknown
36
+ gx chunks 生成 Java/Vue 代码切片并上报 record_code_chunks
37
+ gx eval-search 用评测集计算 search_code 的 HitRate/MRR
38
+
39
+ gx pull 选项:
40
+ --plan <planId> --template <templateId> [--model-ids a,b] [--url <url>] [--token <token>] [--dry-run]
41
+
42
+ gx scan / gx audit 选项:
43
+ --plan <planId> --url <url> --token <token> [--source-dir src/main/java] [--compile]
44
+ --report 强制上报;即使未配置 Token 也报错(scan)
45
+ --classpath 编译 classpath,多个用系统分隔符(macOS/Linux 为 :)
46
+ --no-report 只做本地扫描/对账,不调用 MCP
47
+ --strict 存在待办或覆盖不完整时退出码 1
48
+
49
+ gx eval-search 选项:
50
+ --dataset <path> 评测集路径,默认 eval/code-search-eval.json
51
+ --plan <planId> --url <url> --token <token> [--limit 5] [--out .gx/eval-search.json]
52
+
53
+ gx chunks 选项:
54
+ --plan <planId> --url <url> --token <token> [--source-dir src/main/java,src]
55
+ --report 强制上报;未配置 Token 时报错
56
+ --no-report 只生成本地 .gx/chunks.json
57
+ --no-content 上报时不上传切片内容(仅元数据)
58
+
59
+ 示例:
60
+ GX_API_TOKEN=gxmcp_xxx gx init --client opencode --yes
61
+ gx init --client claude --token gxmcp_xxx --embed-token --yes --url https://example.com/code/mcp
62
+ gx pull --plan plan-1 --template tpl-1
63
+ gx apply --dry-run
64
+ `);
65
+ }
66
+
67
+ /**
68
+ * 解析 CLI 参数。
69
+ *
70
+ * @param {string[]} argv
71
+ * @returns {Record<string, unknown> & { _: string[] }}
72
+ */
73
+ export function parseArgs(argv) {
74
+ const args = { _: [] };
75
+ for (let i = 0; i < argv.length; i += 1) {
76
+ const item = argv[i];
77
+ if (!item.startsWith("--")) {
78
+ args._.push(item);
79
+ continue;
80
+ }
81
+ const key = item.slice(2);
82
+ if (["help", "dry-run", "force", "embed-token", "yes", "strict", "compile", "report", "no-report", "json", "no-content", "check", "with-local", "skip-verify"].includes(key)) {
83
+ args[key] = true;
84
+ continue;
85
+ }
86
+ const value = argv[i + 1];
87
+ if (!value || value.startsWith("--")) {
88
+ throw new UsageError(`参数 --${key} 缺少值`);
89
+ }
90
+ args[key] = value;
91
+ i += 1;
92
+ }
93
+ return args;
94
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * 生成简单的行级 unified diff。
3
+ *
4
+ * 对超大文件退化为“整文件替换”提示,避免 O(n*m) 内存膨胀。
5
+ *
6
+ * @param {string} before
7
+ * @param {string} after
8
+ * @param {string} [label]
9
+ * @returns {string}
10
+ */
11
+ export function lineDiff(before, after, label = "") {
12
+ if (before === after) {
13
+ return "";
14
+ }
15
+ const a = before.split("\n");
16
+ const b = after.split("\n");
17
+ if (a.length * b.length > 1_000_000) {
18
+ return `${label}\n[diff 省略] 文件较大,内容将被整体替换。\n`;
19
+ }
20
+
21
+ const dp = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
22
+ for (let i = a.length - 1; i >= 0; i -= 1) {
23
+ for (let j = b.length - 1; j >= 0; j -= 1) {
24
+ dp[i][j] = a[i] === b[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
25
+ }
26
+ }
27
+
28
+ const lines = [];
29
+ let i = 0;
30
+ let j = 0;
31
+ while (i < a.length && j < b.length) {
32
+ if (a[i] === b[j]) {
33
+ lines.push(` ${a[i]}`);
34
+ i += 1;
35
+ j += 1;
36
+ } else if (dp[i + 1][j] >= dp[i][j + 1]) {
37
+ lines.push(`- ${a[i]}`);
38
+ i += 1;
39
+ } else {
40
+ lines.push(`+ ${b[j]}`);
41
+ j += 1;
42
+ }
43
+ }
44
+ while (i < a.length) {
45
+ lines.push(`- ${a[i]}`);
46
+ i += 1;
47
+ }
48
+ while (j < b.length) {
49
+ lines.push(`+ ${b[j]}`);
50
+ j += 1;
51
+ }
52
+ return `${label}\n${lines.join("\n")}\n`;
53
+ }
54
+
55
+ /**
56
+ * 打印 dry-run 计划。
57
+ *
58
+ * @param {{ path: string, before: string | null, after: string }[]} plan
59
+ */
60
+ export function printPlan(plan) {
61
+ for (const item of plan) {
62
+ const action = item.before === null ? "create" : "update";
63
+ console.log(`\n[dry-run] ${action} ${item.path}`);
64
+ console.log(lineDiff(item.before ?? "", item.after, item.path));
65
+ }
66
+ }
@@ -0,0 +1,202 @@
1
+ /**
2
+ * 最小 MCP Streamable HTTP 客户端。
3
+ *
4
+ * 支持 initialize -> notifications/initialized -> tools/call,兼容 JSON 与 SSE 响应。
5
+ */
6
+ export class McpHttpClient {
7
+ /**
8
+ * @param {{ url: string, token: string, clientName?: string, clientVersion?: string, protocolVersion?: string, timeoutMs?: number }} options
9
+ */
10
+ constructor(options) {
11
+ if (!options.url) {
12
+ throw new Error("MCP URL 不能为空");
13
+ }
14
+ if (!options.token) {
15
+ throw new Error("MCP Token 不能为空");
16
+ }
17
+ this.url = options.url;
18
+ this.token = options.token;
19
+ this.clientName = options.clientName ?? "gx-code-agent";
20
+ this.clientVersion = options.clientVersion ?? "0.1.0";
21
+ this.protocolVersion = options.protocolVersion ?? "2025-06-18";
22
+ this.timeoutMs = Number.isFinite(options.timeoutMs) && options.timeoutMs > 0 ? options.timeoutMs : 30000;
23
+ this.sessionId = null;
24
+ this.initialized = false;
25
+ this.nextId = 1;
26
+ }
27
+
28
+ async initialize() {
29
+ if (this.initialized) {
30
+ return this;
31
+ }
32
+ const response = await this.#send({
33
+ jsonrpc: "2.0",
34
+ id: this.nextId++,
35
+ method: "initialize",
36
+ params: {
37
+ protocolVersion: this.protocolVersion,
38
+ capabilities: {},
39
+ clientInfo: { name: this.clientName, version: this.clientVersion }
40
+ }
41
+ });
42
+ const result = response?.result ?? {};
43
+ this.protocolVersion = result.protocolVersion ?? this.protocolVersion;
44
+ await this.#send({
45
+ jsonrpc: "2.0",
46
+ method: "notifications/initialized",
47
+ params: {}
48
+ }, true);
49
+ this.initialized = true;
50
+ return this;
51
+ }
52
+
53
+ /**
54
+ * @param {string} name
55
+ * @param {Record<string, unknown>} args
56
+ * @returns {Promise<any>}
57
+ */
58
+ /**
59
+ * 获取工具列表。
60
+ *
61
+ * @returns {Promise<any[]>}
62
+ */
63
+ async listTools() {
64
+ await this.initialize();
65
+ const response = await this.#send({
66
+ jsonrpc: "2.0",
67
+ id: this.nextId++,
68
+ method: "tools/list",
69
+ params: {}
70
+ });
71
+ if (response?.error) {
72
+ throw new Error(`MCP tools/list 失败: ${response.error.message ?? JSON.stringify(response.error)}`);
73
+ }
74
+ return response?.result?.tools ?? [];
75
+ }
76
+
77
+ async callTool(name, args) {
78
+ await this.initialize();
79
+ const response = await this.#send({
80
+ jsonrpc: "2.0",
81
+ id: this.nextId++,
82
+ method: "tools/call",
83
+ params: { name, arguments: args }
84
+ });
85
+ if (response?.error) {
86
+ throw new Error(`MCP 调用失败: ${response.error.message ?? JSON.stringify(response.error)}`);
87
+ }
88
+ const result = response?.result;
89
+ if (result?.structuredContent !== undefined && result.structuredContent !== null) {
90
+ return result.structuredContent;
91
+ }
92
+ const text = result?.content?.find?.((item) => item.type === "text")?.text;
93
+ if (text) {
94
+ try {
95
+ return JSON.parse(text);
96
+ } catch {
97
+ return text;
98
+ }
99
+ }
100
+ return result;
101
+ }
102
+
103
+ async close() {
104
+ if (!this.sessionId) {
105
+ return;
106
+ }
107
+ try {
108
+ await fetch(this.url, {
109
+ method: "DELETE",
110
+ headers: this.#headers(),
111
+ signal: this.#signal(),
112
+ });
113
+ } catch {
114
+ // 关闭失败不影响主流程
115
+ } finally {
116
+ this.sessionId = null;
117
+ this.initialized = false;
118
+ }
119
+ }
120
+
121
+ #signal() {
122
+ return typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(this.timeoutMs) : undefined;
123
+ }
124
+
125
+ #headers() {
126
+ const headers = {
127
+ "Content-Type": "application/json",
128
+ "Accept": "application/json, text/event-stream",
129
+ "Authorization": `Bearer ${this.token}`
130
+ };
131
+ if (this.sessionId) {
132
+ headers["Mcp-Session-Id"] = this.sessionId;
133
+ headers["MCP-Protocol-Version"] = this.protocolVersion;
134
+ }
135
+ return headers;
136
+ }
137
+
138
+ async #send(payload, notification = false) {
139
+ const response = await fetch(this.url, {
140
+ method: "POST",
141
+ headers: this.#headers(),
142
+ body: JSON.stringify(payload),
143
+ signal: this.#signal()
144
+ });
145
+ const sessionId = response.headers.get("mcp-session-id");
146
+ if (sessionId) {
147
+ this.sessionId = sessionId;
148
+ }
149
+ if (notification) {
150
+ return null;
151
+ }
152
+ if (!response.ok) {
153
+ const body = await response.text();
154
+ throw new Error(`MCP HTTP ${response.status}: ${body.slice(0, 500)}`);
155
+ }
156
+ if (response.status === 202 || response.status === 204) {
157
+ return null;
158
+ }
159
+ const contentType = response.headers.get("content-type") ?? "";
160
+ if (contentType.includes("text/event-stream")) {
161
+ const text = await response.text();
162
+ const messages = parseSsePayload(text);
163
+ return messages.find((message) => message?.id === payload.id) ?? messages[0] ?? null;
164
+ }
165
+ return response.json();
166
+ }
167
+ }
168
+
169
+ /**
170
+ * 解析 SSE 响应中的 JSON-RPC 消息。
171
+ *
172
+ * @param {string} text
173
+ * @returns {any[]}
174
+ */
175
+ export function parseSsePayload(text) {
176
+ const messages = [];
177
+ let dataLines = [];
178
+ const flush = () => {
179
+ if (dataLines.length === 0) {
180
+ return;
181
+ }
182
+ const data = dataLines.join("\n");
183
+ dataLines = [];
184
+ if (!data.trim()) {
185
+ return;
186
+ }
187
+ try {
188
+ messages.push(JSON.parse(data));
189
+ } catch {
190
+ // 忽略非 JSON 事件
191
+ }
192
+ };
193
+ for (const line of text.split(/\r?\n/u)) {
194
+ if (line.startsWith("data:")) {
195
+ dataLines.push(line.slice(5).trimStart());
196
+ } else if (line.trim() === "") {
197
+ flush();
198
+ }
199
+ }
200
+ flush();
201
+ return messages;
202
+ }