@gt-fe/eap-cli 0.2.11 → 0.2.12

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 (75) hide show
  1. package/README.md +304 -304
  2. package/dist/static/playground/index.html +131 -131
  3. package/dist/static/playground/playground.css +209 -209
  4. package/dist/static/playground/playground.js +151 -151
  5. package/dist/static/playground-app/index.html +11 -11
  6. package/dist/templates/README.md +51 -51
  7. package/dist/templates/agent-api-orchestration/.env +16 -16
  8. package/dist/templates/agent-api-orchestration/README.md +326 -326
  9. package/dist/templates/agent-api-orchestration/agent.manifest.json +19 -19
  10. package/dist/templates/agent-api-orchestration/eap.config.json +1 -1
  11. package/dist/templates/agent-api-orchestration/eap.lock.yaml +5 -5
  12. package/dist/templates/agent-api-orchestration/gitignore +3 -3
  13. package/dist/templates/agent-api-orchestration/graph.json +35 -35
  14. package/dist/templates/agent-api-orchestration/package.json +1 -1
  15. package/dist/templates/agent-api-orchestration/scripts/api-test.mjs +443 -443
  16. package/dist/templates/agent-api-orchestration/scripts/build-runtime.mjs +26 -26
  17. package/dist/templates/agent-api-orchestration/scripts/validate.mjs +12 -12
  18. package/dist/templates/agent-api-orchestration/src/embedded-agent.ts +91 -91
  19. package/dist/templates/agent-api-orchestration/src/http/app.ts +58 -58
  20. package/dist/templates/agent-api-orchestration/src/http/config.ts +34 -34
  21. package/dist/templates/agent-api-orchestration/src/http/errors.ts +63 -63
  22. package/dist/templates/agent-api-orchestration/src/http/index.ts +4 -4
  23. package/dist/templates/agent-api-orchestration/src/http/middleware/cors.ts +17 -17
  24. package/dist/templates/agent-api-orchestration/src/http/routes/agent.ts +33 -33
  25. package/dist/templates/agent-api-orchestration/src/http/routes/health.ts +15 -15
  26. package/dist/templates/agent-api-orchestration/src/http/routes/invocations.ts +29 -29
  27. package/dist/templates/agent-api-orchestration/src/http/routes/sessions.ts +70 -70
  28. package/dist/templates/agent-api-orchestration/src/http/services/agent-service.ts +60 -60
  29. package/dist/templates/agent-api-orchestration/src/http/services/resource-service.ts +95 -95
  30. package/dist/templates/agent-api-orchestration/src/http/shared/request.ts +82 -82
  31. package/dist/templates/agent-api-orchestration/src/http/shared/sse.ts +32 -32
  32. package/dist/templates/agent-api-orchestration/src/http/types.ts +39 -39
  33. package/dist/templates/agent-api-orchestration/src/index.ts +91 -91
  34. package/dist/templates/agent-api-orchestration/src/platform-user.ts +36 -36
  35. package/dist/templates/agent-api-orchestration/src/project-config.ts +45 -45
  36. package/dist/templates/agent-api-orchestration/tsconfig.json +15 -15
  37. package/dist/templates/dag-workflow/README.md +102 -102
  38. package/dist/templates/dag-workflow/agent.manifest.json +19 -19
  39. package/dist/templates/dag-workflow/eap.config.json +1 -1
  40. package/dist/templates/dag-workflow/eap.lock.yaml +5 -5
  41. package/dist/templates/dag-workflow/gitignore +51 -51
  42. package/dist/templates/dag-workflow/graph.json +51 -51
  43. package/dist/templates/dag-workflow/package.json +10 -10
  44. package/dist/templates/dag-workflow/skills/README.md +116 -116
  45. package/dist/templates/plan-and-execute/README.md +109 -109
  46. package/dist/templates/plan-and-execute/agent.manifest.json +19 -19
  47. package/dist/templates/plan-and-execute/eap.config.json +1 -1
  48. package/dist/templates/plan-and-execute/eap.lock.yaml +5 -5
  49. package/dist/templates/plan-and-execute/gitignore +51 -51
  50. package/dist/templates/plan-and-execute/graph.json +49 -49
  51. package/dist/templates/plan-and-execute/package.json +10 -10
  52. package/dist/templates/plan-and-execute/skills/README.md +120 -120
  53. package/dist/templates/plan-and-execute/tools/echo-tool/src/index.js +17 -17
  54. package/dist/templates/plan-and-execute/tools/echo-tool/toolset.manifest.json +55 -55
  55. package/dist/templates/simple-flow/README.md +87 -87
  56. package/dist/templates/simple-flow/agent.manifest.json +19 -19
  57. package/dist/templates/simple-flow/eap.config.json +1 -1
  58. package/dist/templates/simple-flow/eap.lock.yaml +5 -5
  59. package/dist/templates/simple-flow/gitignore +51 -51
  60. package/dist/templates/simple-flow/graph.json +32 -32
  61. package/dist/templates/simple-flow/package.json +10 -10
  62. package/dist/templates/simple-flow/skills/README.md +113 -113
  63. package/dist/templates/single-agent-with-tools/README.md +138 -138
  64. package/dist/templates/single-agent-with-tools/agent.manifest.json +19 -19
  65. package/dist/templates/single-agent-with-tools/eap.config.json +1 -1
  66. package/dist/templates/single-agent-with-tools/eap.lock.yaml +5 -5
  67. package/dist/templates/single-agent-with-tools/gitignore +51 -51
  68. package/dist/templates/single-agent-with-tools/graph.json +41 -41
  69. package/dist/templates/single-agent-with-tools/package.json +10 -10
  70. package/dist/templates/single-agent-with-tools/skills/README.md +119 -119
  71. package/dist/templates/single-agent-with-tools/tools/echo-tool/src/index.js +17 -17
  72. package/dist/templates/single-agent-with-tools/tools/echo-tool/toolset.manifest.json +55 -55
  73. package/dist/templates/single-agent-with-tools/tools/http-weather/toolset.manifest.json +73 -73
  74. package/dist/templates/single-agent-with-tools/tools/web-search/toolset.manifest.json +41 -41
  75. package/package.json +3 -3
@@ -1,443 +1,443 @@
1
- /**
2
- * Agent API 接口测试脚本(可直接运行)
3
- *
4
- * 用法:
5
- * node scripts/api-test.mjs
6
- *
7
- * 可选环境变量:
8
- * BASE_URL 服务地址,默认 http://127.0.0.1:3000
9
- * AGENT_CODE Agent code,默认 agent-test
10
- * TEST_MESSAGE 发送给 Agent 的测试消息,默认“你好,请用一句话介绍你自己”
11
- *
12
- * 会依次覆盖:健康检查、能力发现、会话增查、消息发送(同步调用)、
13
- * 历史消息查询、无会话同步调用、流式对话、归档、恢复(预期 501)。
14
- * 每个用例独立 try/catch,失败不中断后续用例,最后打印汇总。
15
- */
16
-
17
- const BASE_URL = (process.env.BASE_URL || "http://127.0.0.1:3000").replace(
18
- /\/+$/,
19
- "",
20
- );
21
- const AGENT_CODE = process.env.AGENT_CODE || "agent-test";
22
- const TEST_MESSAGE = process.env.TEST_MESSAGE || "你好,请用一句话介绍你自己";
23
-
24
- const api = (path) => `${BASE_URL}/api/v1/agents/${AGENT_CODE}${path}`;
25
-
26
- let step = 0;
27
- let passed = 0;
28
- let failed = 0;
29
- const failures = [];
30
-
31
- // ---------- 工具函数 ----------
32
-
33
- function header(title) {
34
- console.log("\n" + "═".repeat(70));
35
- console.log(` ${title}`);
36
- console.log("═".repeat(70));
37
- }
38
-
39
- function log(label, value) {
40
- console.log(` ▶ ${label}`);
41
- if (value !== undefined) console.log(pretty(value, 2));
42
- }
43
-
44
- function ok(msg) {
45
- passed += 1;
46
- console.log(` ✅ [PASS] ${msg}`);
47
- }
48
-
49
- function fail(msg, detail) {
50
- failed += 1;
51
- failures.push({ msg, detail });
52
- console.log(` ❌ [FAIL] ${msg}`);
53
- if (detail !== undefined) console.log(pretty(detail, 2));
54
- }
55
-
56
- function pretty(value, indent = 0) {
57
- const text =
58
- typeof value === "string" ? value : JSON.stringify(value, null, indent);
59
- const lines = text.split("\n");
60
- const head = lines.slice(0, 40);
61
- let out = " " + head.join("\n ");
62
- if (lines.length > 40) out += `\n ... (共 ${lines.length} 行,已截断)`;
63
- return out;
64
- }
65
-
66
- function truncate(value, max = 800) {
67
- const text =
68
- value == null
69
- ? ""
70
- : typeof value === "string"
71
- ? value
72
- : JSON.stringify(value);
73
- return text.length > max
74
- ? text.slice(0, max) + `... (共 ${text.length} 字符)`
75
- : text;
76
- }
77
-
78
- async function request(
79
- method,
80
- url,
81
- { body, headers = {}, timeout = 30000 } = {},
82
- ) {
83
- const controller = new AbortController();
84
- const timer = setTimeout(() => controller.abort(), timeout);
85
- try {
86
- const response = await fetch(url, {
87
- method,
88
- headers: { "Content-Type": "application/json", ...headers },
89
- ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
90
- signal: controller.signal,
91
- });
92
- const text = await response.text();
93
- let data;
94
- try {
95
- data = text ? JSON.parse(text) : null;
96
- } catch {
97
- data = text;
98
- }
99
- return { status: response.status, data, ok: response.ok };
100
- } catch (error) {
101
- const message =
102
- error?.name === "AbortError"
103
- ? `请求超时(${timeout}ms)`
104
- : (error?.message ?? String(error));
105
- return { status: 0, data: null, ok: false, error: message };
106
- } finally {
107
- clearTimeout(timer);
108
- }
109
- }
110
-
111
- /** 消费 SSE 流,返回 [{ event, data }],data 已解析为 JSON。 */
112
- async function consumeSse(url, { body, timeout = 120000 } = {}) {
113
- const controller = new AbortController();
114
- const timer = setTimeout(() => controller.abort(), timeout);
115
- const events = [];
116
- try {
117
- const response = await fetch(url, {
118
- method: "POST",
119
- headers: { "Content-Type": "application/json" },
120
- body: JSON.stringify(body),
121
- signal: controller.signal,
122
- });
123
- if (!response.ok || !response.body) {
124
- const text = await response.text();
125
- return { status: response.status, events, raw: text };
126
- }
127
- const reader = response.body.getReader();
128
- const decoder = new TextDecoder();
129
- let buffer = "";
130
- while (true) {
131
- const { done, value } = await reader.read();
132
- if (done) break;
133
- buffer += decoder.decode(value, { stream: true });
134
- const frames = buffer.split("\n\n");
135
- buffer = frames.pop() ?? "";
136
- for (const frame of frames) {
137
- const eventLine = frame.split("\n").find((l) => l.startsWith("event:"));
138
- const dataLine = frame.split("\n").find((l) => l.startsWith("data:"));
139
- if (!dataLine) continue;
140
- const type = eventLine ? eventLine.slice(6).trim() : "message";
141
- let data;
142
- try {
143
- data = JSON.parse(dataLine.slice(5).trim());
144
- } catch {
145
- data = dataLine.slice(5).trim();
146
- }
147
- events.push({ event: type, data });
148
- }
149
- }
150
- return { status: response.status, events };
151
- } finally {
152
- clearTimeout(timer);
153
- }
154
- }
155
-
156
- function assert(condition, message) {
157
- if (!condition) throw new Error(message);
158
- }
159
-
160
- // ---------- 测试用例 ----------
161
-
162
- async function testHealth() {
163
- const name = "健康检查";
164
- header(`${name} · GET /health`);
165
- const { status, data } = await request("GET", `${BASE_URL}/health`);
166
- log("响应", data);
167
- try {
168
- assert(status === 200, `期望 200,实际 ${status}`);
169
- assert(
170
- data?.status === "ok",
171
- `期望 status=ok,实际 ${JSON.stringify(data)}`,
172
- );
173
- assert(
174
- data?.agentCode === AGENT_CODE,
175
- `期望 agentCode=${AGENT_CODE},实际 ${data?.agentCode}`,
176
- );
177
- ok(name);
178
- } catch (e) {
179
- fail(name, e.message);
180
- }
181
- }
182
-
183
- async function testCapability() {
184
- const name = "能力发现";
185
- header(`${name} · GET ${api("")}`);
186
- const { status, data } = await request("GET", api(""));
187
- log("响应(截断)", truncate(data, 1200));
188
- try {
189
- assert(status === 200, `期望 200,实际 ${status}`);
190
- assert(
191
- data?.agent?.code === AGENT_CODE,
192
- `期望 agent.code=${AGENT_CODE},实际 ${data?.agent?.code}`,
193
- );
194
- assert(
195
- data?.capabilities?.stream === true,
196
- "期望 capabilities.stream=true",
197
- );
198
- assert(Array.isArray(data?.graph?.nodes), "期望 graph.nodes 为数组");
199
- ok(
200
- `${name}(版本 ${data?.agent?.version},节点 ${data?.graph?.nodes?.length ?? 0} 个)`,
201
- );
202
- } catch (e) {
203
- fail(name, e.message);
204
- }
205
- }
206
-
207
- async function testCreateSession() {
208
- const name = "创建会话";
209
- header(`${name} · POST ${api("/sessions")}`);
210
- const { status, data } = await request("POST", api("/sessions"), {
211
- body: { userId: "test-user", title: "接口测试会话" },
212
- });
213
- log("响应", data);
214
- try {
215
- assert(status === 201, `期望 201,实际 ${status}`);
216
- assert(data?.id, `期望返回 session.id,实际 ${JSON.stringify(data)}`);
217
- ok(name);
218
- return data.id;
219
- } catch (e) {
220
- fail(name, e.message);
221
- return undefined;
222
- }
223
- }
224
-
225
- async function testListSessions() {
226
- const name = "查询会话列表";
227
- header(`${name} · GET ${api("/sessions?limit=20&offset=0")}`);
228
- const { status, data } = await request(
229
- "GET",
230
- `${api("/sessions")}?limit=20&offset=0`,
231
- );
232
- log("响应", data);
233
- try {
234
- assert(status === 200, `期望 200,实际 ${status}`);
235
- assert(
236
- Array.isArray(data?.items) || Array.isArray(data),
237
- `期望返回会话列表,实际 ${JSON.stringify(data)}`,
238
- );
239
- ok(name);
240
- } catch (e) {
241
- fail(name, e.message);
242
- }
243
- }
244
-
245
- async function testGetSession(sessionId) {
246
- const name = "查询会话详情";
247
- header(`${name} · GET ${api(`/sessions/${sessionId}`)}`);
248
- const { status, data } = await request("GET", api(`/sessions/${sessionId}`));
249
- log("响应", data);
250
- try {
251
- assert(status === 200, `期望 200,实际 ${status}`);
252
- assert(data?.id === sessionId, `期望 id=${sessionId},实际 ${data?.id}`);
253
- ok(name);
254
- } catch (e) {
255
- fail(name, e.message);
256
- }
257
- }
258
-
259
- async function testSendMessage(sessionId) {
260
- const name = "发送消息(同步调用)";
261
- header(`${name} · POST ${api(`/sessions/${sessionId}/messages`)}`);
262
- const { status, data } = await request(
263
- "POST",
264
- api(`/sessions/${sessionId}/messages`),
265
- {
266
- body: { content: TEST_MESSAGE },
267
- timeout: 120000,
268
- },
269
- );
270
- log("响应", truncate(data, 1200));
271
- try {
272
- assert(status === 200, `期望 200,实际 ${status}`);
273
- const output = data?.output;
274
- log("Agent 回复", truncate(output, 1200));
275
- assert(
276
- data?.status === "success",
277
- `期望 status=success,实际 ${data?.status}${output ? `(${truncate(output, 200)})` : ""}`,
278
- );
279
- ok(name);
280
- } catch (e) {
281
- fail(name, e.message);
282
- }
283
- }
284
-
285
- async function testListMessages(sessionId) {
286
- const name = "查询历史消息";
287
- header(`${name} · GET ${api(`/sessions/${sessionId}/messages`)}`);
288
- const { status, data } = await request(
289
- "GET",
290
- api(`/sessions/${sessionId}/messages`),
291
- );
292
- log("响应", data);
293
- try {
294
- assert(status === 200, `期望 200,实际 ${status}`);
295
- ok(name);
296
- } catch (e) {
297
- fail(name, e.message);
298
- }
299
- }
300
-
301
- async function testStatelessInvoke() {
302
- const name = "无会话同步调用";
303
- header(`${name} · POST ${api("/invoke")}`);
304
- const { status, data } = await request("POST", api("/invoke"), {
305
- body: { content: TEST_MESSAGE },
306
- timeout: 120000,
307
- });
308
- log("响应", truncate(data, 800));
309
- log("Agent 回复", truncate(data?.output, 1200));
310
- try {
311
- assert(status === 200, `期望 200,实际 ${status}`);
312
- assert(
313
- data?.status === "success",
314
- `期望 status=success,实际 ${data?.status}${data?.output ? `(${truncate(data.output, 200)})` : ""}`,
315
- );
316
- ok(name);
317
- } catch (e) {
318
- fail(name, e.message);
319
- }
320
- }
321
-
322
- async function testStream(sessionId) {
323
- const name = "流式对话";
324
- header(`${name} · POST ${api(`/sessions/${sessionId}/stream`)}`);
325
- const { status, events, raw } = await consumeSse(
326
- api(`/sessions/${sessionId}/stream`),
327
- {
328
- body: { content: "继续,用一句话补充说明" },
329
- },
330
- );
331
- try {
332
- assert(status === 200, `期望 200,实际 ${status}`);
333
- assert(events.length > 0, "期望至少收到 1 个 SSE 事件");
334
- log(
335
- `共收到 ${events.length} 个事件,类型分布:`,
336
- countBy(events.map((e) => e.event)),
337
- );
338
- const chunks = events
339
- .filter((e) => e.event === "response.chunk")
340
- .map((e) => e.data?.delta ?? e.data?.text ?? e.data?.content);
341
- log("流式文本片段(前 20 个)", chunks.slice(0, 20));
342
- const text = chunks.filter(Boolean).join("");
343
- log("流式回复全文", truncate(text, 1200));
344
- const errorEvent = events.find(
345
- (e) => e.event === "error" || e.event === "task.error",
346
- );
347
- assert(!errorEvent, `流中收到错误事件:${truncate(errorEvent?.data, 300)}`);
348
- ok(name);
349
- } catch (e) {
350
- if (raw) log("原始响应", truncate(raw, 800));
351
- fail(name, e.message);
352
- }
353
- }
354
-
355
- async function testArchive(sessionId) {
356
- const name = "归档会话";
357
- header(`${name} · POST ${api(`/sessions/${sessionId}/archive`)}`);
358
- const { status, data } = await request(
359
- "POST",
360
- api(`/sessions/${sessionId}/archive`),
361
- );
362
- log("响应", data);
363
- try {
364
- assert(status === 200, `期望 200,实际 ${status}`);
365
- ok(name);
366
- } catch (e) {
367
- fail(name, e.message);
368
- }
369
- }
370
-
371
- async function testRestore(sessionId) {
372
- const name = "恢复会话(预期 501)";
373
- header(`${name} · POST ${api(`/sessions/${sessionId}/restore`)}`);
374
- const { status, data } = await request(
375
- "POST",
376
- api(`/sessions/${sessionId}/restore`),
377
- );
378
- log("响应", data);
379
- try {
380
- assert(status === 501, `期望 501 not_supported,实际 ${status}`);
381
- ok(name);
382
- } catch (e) {
383
- fail(name, e.message);
384
- }
385
- }
386
-
387
- function countBy(items) {
388
- return items.reduce(
389
- (acc, item) => ((acc[item] = (acc[item] ?? 0) + 1), acc),
390
- {},
391
- );
392
- }
393
-
394
- // ---------- 汇总 ----------
395
-
396
- function summary() {
397
- header("测试汇总");
398
- const total = passed + failed;
399
- console.log(` 总计 ${total} 项 · 通过 ${passed} · 失败 ${failed}`);
400
- if (failures.length) {
401
- console.log("\n 失败明细:");
402
- for (const f of failures) {
403
- console.log(
404
- ` ✗ ${f.msg}${f.detail ? " — " + truncate(f.detail, 300) : ""}`,
405
- );
406
- }
407
- }
408
- console.log("\n" + "─".repeat(70));
409
- process.exitCode = failed ? 1 : 0;
410
- }
411
-
412
- // ---------- 主流程 ----------
413
-
414
- async function main() {
415
- header(`Agent API 接口测试`);
416
- console.log(` 服务地址:${BASE_URL}`);
417
- console.log(` Agent code:${AGENT_CODE}`);
418
- console.log(` 测试消息:${TEST_MESSAGE}`);
419
-
420
- const sessionId = await testCreateSession();
421
- if (!sessionId) {
422
- summary();
423
- return;
424
- }
425
-
426
- await testHealth();
427
- await testCapability();
428
- await testListSessions();
429
- await testGetSession(sessionId);
430
- await testSendMessage(sessionId);
431
- await testListMessages(sessionId);
432
- await testStatelessInvoke();
433
- await testStream(sessionId);
434
- await testArchive(sessionId);
435
- await testRestore(sessionId);
436
-
437
- summary();
438
- }
439
-
440
- main().catch((error) => {
441
- console.error("测试脚本执行异常:", error);
442
- process.exitCode = 1;
443
- });
1
+ /**
2
+ * Agent API 接口测试脚本(可直接运行)
3
+ *
4
+ * 用法:
5
+ * node scripts/api-test.mjs
6
+ *
7
+ * 可选环境变量:
8
+ * BASE_URL 服务地址,默认 http://127.0.0.1:3000
9
+ * AGENT_CODE Agent code,默认 agent-test
10
+ * TEST_MESSAGE 发送给 Agent 的测试消息,默认“你好,请用一句话介绍你自己”
11
+ *
12
+ * 会依次覆盖:健康检查、能力发现、会话增查、消息发送(同步调用)、
13
+ * 历史消息查询、无会话同步调用、流式对话、归档、恢复(预期 501)。
14
+ * 每个用例独立 try/catch,失败不中断后续用例,最后打印汇总。
15
+ */
16
+
17
+ const BASE_URL = (process.env.BASE_URL || "http://127.0.0.1:3000").replace(
18
+ /\/+$/,
19
+ "",
20
+ );
21
+ const AGENT_CODE = process.env.AGENT_CODE || "agent-test";
22
+ const TEST_MESSAGE = process.env.TEST_MESSAGE || "你好,请用一句话介绍你自己";
23
+
24
+ const api = (path) => `${BASE_URL}/api/v1/agents/${AGENT_CODE}${path}`;
25
+
26
+ let step = 0;
27
+ let passed = 0;
28
+ let failed = 0;
29
+ const failures = [];
30
+
31
+ // ---------- 工具函数 ----------
32
+
33
+ function header(title) {
34
+ console.log("\n" + "═".repeat(70));
35
+ console.log(` ${title}`);
36
+ console.log("═".repeat(70));
37
+ }
38
+
39
+ function log(label, value) {
40
+ console.log(` ▶ ${label}`);
41
+ if (value !== undefined) console.log(pretty(value, 2));
42
+ }
43
+
44
+ function ok(msg) {
45
+ passed += 1;
46
+ console.log(` ✅ [PASS] ${msg}`);
47
+ }
48
+
49
+ function fail(msg, detail) {
50
+ failed += 1;
51
+ failures.push({ msg, detail });
52
+ console.log(` ❌ [FAIL] ${msg}`);
53
+ if (detail !== undefined) console.log(pretty(detail, 2));
54
+ }
55
+
56
+ function pretty(value, indent = 0) {
57
+ const text =
58
+ typeof value === "string" ? value : JSON.stringify(value, null, indent);
59
+ const lines = text.split("\n");
60
+ const head = lines.slice(0, 40);
61
+ let out = " " + head.join("\n ");
62
+ if (lines.length > 40) out += `\n ... (共 ${lines.length} 行,已截断)`;
63
+ return out;
64
+ }
65
+
66
+ function truncate(value, max = 800) {
67
+ const text =
68
+ value == null
69
+ ? ""
70
+ : typeof value === "string"
71
+ ? value
72
+ : JSON.stringify(value);
73
+ return text.length > max
74
+ ? text.slice(0, max) + `... (共 ${text.length} 字符)`
75
+ : text;
76
+ }
77
+
78
+ async function request(
79
+ method,
80
+ url,
81
+ { body, headers = {}, timeout = 30000 } = {},
82
+ ) {
83
+ const controller = new AbortController();
84
+ const timer = setTimeout(() => controller.abort(), timeout);
85
+ try {
86
+ const response = await fetch(url, {
87
+ method,
88
+ headers: { "Content-Type": "application/json", ...headers },
89
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
90
+ signal: controller.signal,
91
+ });
92
+ const text = await response.text();
93
+ let data;
94
+ try {
95
+ data = text ? JSON.parse(text) : null;
96
+ } catch {
97
+ data = text;
98
+ }
99
+ return { status: response.status, data, ok: response.ok };
100
+ } catch (error) {
101
+ const message =
102
+ error?.name === "AbortError"
103
+ ? `请求超时(${timeout}ms)`
104
+ : (error?.message ?? String(error));
105
+ return { status: 0, data: null, ok: false, error: message };
106
+ } finally {
107
+ clearTimeout(timer);
108
+ }
109
+ }
110
+
111
+ /** 消费 SSE 流,返回 [{ event, data }],data 已解析为 JSON。 */
112
+ async function consumeSse(url, { body, timeout = 120000 } = {}) {
113
+ const controller = new AbortController();
114
+ const timer = setTimeout(() => controller.abort(), timeout);
115
+ const events = [];
116
+ try {
117
+ const response = await fetch(url, {
118
+ method: "POST",
119
+ headers: { "Content-Type": "application/json" },
120
+ body: JSON.stringify(body),
121
+ signal: controller.signal,
122
+ });
123
+ if (!response.ok || !response.body) {
124
+ const text = await response.text();
125
+ return { status: response.status, events, raw: text };
126
+ }
127
+ const reader = response.body.getReader();
128
+ const decoder = new TextDecoder();
129
+ let buffer = "";
130
+ while (true) {
131
+ const { done, value } = await reader.read();
132
+ if (done) break;
133
+ buffer += decoder.decode(value, { stream: true });
134
+ const frames = buffer.split("\n\n");
135
+ buffer = frames.pop() ?? "";
136
+ for (const frame of frames) {
137
+ const eventLine = frame.split("\n").find((l) => l.startsWith("event:"));
138
+ const dataLine = frame.split("\n").find((l) => l.startsWith("data:"));
139
+ if (!dataLine) continue;
140
+ const type = eventLine ? eventLine.slice(6).trim() : "message";
141
+ let data;
142
+ try {
143
+ data = JSON.parse(dataLine.slice(5).trim());
144
+ } catch {
145
+ data = dataLine.slice(5).trim();
146
+ }
147
+ events.push({ event: type, data });
148
+ }
149
+ }
150
+ return { status: response.status, events };
151
+ } finally {
152
+ clearTimeout(timer);
153
+ }
154
+ }
155
+
156
+ function assert(condition, message) {
157
+ if (!condition) throw new Error(message);
158
+ }
159
+
160
+ // ---------- 测试用例 ----------
161
+
162
+ async function testHealth() {
163
+ const name = "健康检查";
164
+ header(`${name} · GET /health`);
165
+ const { status, data } = await request("GET", `${BASE_URL}/health`);
166
+ log("响应", data);
167
+ try {
168
+ assert(status === 200, `期望 200,实际 ${status}`);
169
+ assert(
170
+ data?.status === "ok",
171
+ `期望 status=ok,实际 ${JSON.stringify(data)}`,
172
+ );
173
+ assert(
174
+ data?.agentCode === AGENT_CODE,
175
+ `期望 agentCode=${AGENT_CODE},实际 ${data?.agentCode}`,
176
+ );
177
+ ok(name);
178
+ } catch (e) {
179
+ fail(name, e.message);
180
+ }
181
+ }
182
+
183
+ async function testCapability() {
184
+ const name = "能力发现";
185
+ header(`${name} · GET ${api("")}`);
186
+ const { status, data } = await request("GET", api(""));
187
+ log("响应(截断)", truncate(data, 1200));
188
+ try {
189
+ assert(status === 200, `期望 200,实际 ${status}`);
190
+ assert(
191
+ data?.agent?.code === AGENT_CODE,
192
+ `期望 agent.code=${AGENT_CODE},实际 ${data?.agent?.code}`,
193
+ );
194
+ assert(
195
+ data?.capabilities?.stream === true,
196
+ "期望 capabilities.stream=true",
197
+ );
198
+ assert(Array.isArray(data?.graph?.nodes), "期望 graph.nodes 为数组");
199
+ ok(
200
+ `${name}(版本 ${data?.agent?.version},节点 ${data?.graph?.nodes?.length ?? 0} 个)`,
201
+ );
202
+ } catch (e) {
203
+ fail(name, e.message);
204
+ }
205
+ }
206
+
207
+ async function testCreateSession() {
208
+ const name = "创建会话";
209
+ header(`${name} · POST ${api("/sessions")}`);
210
+ const { status, data } = await request("POST", api("/sessions"), {
211
+ body: { userId: "test-user", title: "接口测试会话" },
212
+ });
213
+ log("响应", data);
214
+ try {
215
+ assert(status === 201, `期望 201,实际 ${status}`);
216
+ assert(data?.id, `期望返回 session.id,实际 ${JSON.stringify(data)}`);
217
+ ok(name);
218
+ return data.id;
219
+ } catch (e) {
220
+ fail(name, e.message);
221
+ return undefined;
222
+ }
223
+ }
224
+
225
+ async function testListSessions() {
226
+ const name = "查询会话列表";
227
+ header(`${name} · GET ${api("/sessions?limit=20&offset=0")}`);
228
+ const { status, data } = await request(
229
+ "GET",
230
+ `${api("/sessions")}?limit=20&offset=0`,
231
+ );
232
+ log("响应", data);
233
+ try {
234
+ assert(status === 200, `期望 200,实际 ${status}`);
235
+ assert(
236
+ Array.isArray(data?.items) || Array.isArray(data),
237
+ `期望返回会话列表,实际 ${JSON.stringify(data)}`,
238
+ );
239
+ ok(name);
240
+ } catch (e) {
241
+ fail(name, e.message);
242
+ }
243
+ }
244
+
245
+ async function testGetSession(sessionId) {
246
+ const name = "查询会话详情";
247
+ header(`${name} · GET ${api(`/sessions/${sessionId}`)}`);
248
+ const { status, data } = await request("GET", api(`/sessions/${sessionId}`));
249
+ log("响应", data);
250
+ try {
251
+ assert(status === 200, `期望 200,实际 ${status}`);
252
+ assert(data?.id === sessionId, `期望 id=${sessionId},实际 ${data?.id}`);
253
+ ok(name);
254
+ } catch (e) {
255
+ fail(name, e.message);
256
+ }
257
+ }
258
+
259
+ async function testSendMessage(sessionId) {
260
+ const name = "发送消息(同步调用)";
261
+ header(`${name} · POST ${api(`/sessions/${sessionId}/messages`)}`);
262
+ const { status, data } = await request(
263
+ "POST",
264
+ api(`/sessions/${sessionId}/messages`),
265
+ {
266
+ body: { content: TEST_MESSAGE },
267
+ timeout: 120000,
268
+ },
269
+ );
270
+ log("响应", truncate(data, 1200));
271
+ try {
272
+ assert(status === 200, `期望 200,实际 ${status}`);
273
+ const output = data?.output;
274
+ log("Agent 回复", truncate(output, 1200));
275
+ assert(
276
+ data?.status === "success",
277
+ `期望 status=success,实际 ${data?.status}${output ? `(${truncate(output, 200)})` : ""}`,
278
+ );
279
+ ok(name);
280
+ } catch (e) {
281
+ fail(name, e.message);
282
+ }
283
+ }
284
+
285
+ async function testListMessages(sessionId) {
286
+ const name = "查询历史消息";
287
+ header(`${name} · GET ${api(`/sessions/${sessionId}/messages`)}`);
288
+ const { status, data } = await request(
289
+ "GET",
290
+ api(`/sessions/${sessionId}/messages`),
291
+ );
292
+ log("响应", data);
293
+ try {
294
+ assert(status === 200, `期望 200,实际 ${status}`);
295
+ ok(name);
296
+ } catch (e) {
297
+ fail(name, e.message);
298
+ }
299
+ }
300
+
301
+ async function testStatelessInvoke() {
302
+ const name = "无会话同步调用";
303
+ header(`${name} · POST ${api("/invoke")}`);
304
+ const { status, data } = await request("POST", api("/invoke"), {
305
+ body: { content: TEST_MESSAGE },
306
+ timeout: 120000,
307
+ });
308
+ log("响应", truncate(data, 800));
309
+ log("Agent 回复", truncate(data?.output, 1200));
310
+ try {
311
+ assert(status === 200, `期望 200,实际 ${status}`);
312
+ assert(
313
+ data?.status === "success",
314
+ `期望 status=success,实际 ${data?.status}${data?.output ? `(${truncate(data.output, 200)})` : ""}`,
315
+ );
316
+ ok(name);
317
+ } catch (e) {
318
+ fail(name, e.message);
319
+ }
320
+ }
321
+
322
+ async function testStream(sessionId) {
323
+ const name = "流式对话";
324
+ header(`${name} · POST ${api(`/sessions/${sessionId}/stream`)}`);
325
+ const { status, events, raw } = await consumeSse(
326
+ api(`/sessions/${sessionId}/stream`),
327
+ {
328
+ body: { content: "继续,用一句话补充说明" },
329
+ },
330
+ );
331
+ try {
332
+ assert(status === 200, `期望 200,实际 ${status}`);
333
+ assert(events.length > 0, "期望至少收到 1 个 SSE 事件");
334
+ log(
335
+ `共收到 ${events.length} 个事件,类型分布:`,
336
+ countBy(events.map((e) => e.event)),
337
+ );
338
+ const chunks = events
339
+ .filter((e) => e.event === "response.chunk")
340
+ .map((e) => e.data?.delta ?? e.data?.text ?? e.data?.content);
341
+ log("流式文本片段(前 20 个)", chunks.slice(0, 20));
342
+ const text = chunks.filter(Boolean).join("");
343
+ log("流式回复全文", truncate(text, 1200));
344
+ const errorEvent = events.find(
345
+ (e) => e.event === "error" || e.event === "task.error",
346
+ );
347
+ assert(!errorEvent, `流中收到错误事件:${truncate(errorEvent?.data, 300)}`);
348
+ ok(name);
349
+ } catch (e) {
350
+ if (raw) log("原始响应", truncate(raw, 800));
351
+ fail(name, e.message);
352
+ }
353
+ }
354
+
355
+ async function testArchive(sessionId) {
356
+ const name = "归档会话";
357
+ header(`${name} · POST ${api(`/sessions/${sessionId}/archive`)}`);
358
+ const { status, data } = await request(
359
+ "POST",
360
+ api(`/sessions/${sessionId}/archive`),
361
+ );
362
+ log("响应", data);
363
+ try {
364
+ assert(status === 200, `期望 200,实际 ${status}`);
365
+ ok(name);
366
+ } catch (e) {
367
+ fail(name, e.message);
368
+ }
369
+ }
370
+
371
+ async function testRestore(sessionId) {
372
+ const name = "恢复会话(预期 501)";
373
+ header(`${name} · POST ${api(`/sessions/${sessionId}/restore`)}`);
374
+ const { status, data } = await request(
375
+ "POST",
376
+ api(`/sessions/${sessionId}/restore`),
377
+ );
378
+ log("响应", data);
379
+ try {
380
+ assert(status === 501, `期望 501 not_supported,实际 ${status}`);
381
+ ok(name);
382
+ } catch (e) {
383
+ fail(name, e.message);
384
+ }
385
+ }
386
+
387
+ function countBy(items) {
388
+ return items.reduce(
389
+ (acc, item) => ((acc[item] = (acc[item] ?? 0) + 1), acc),
390
+ {},
391
+ );
392
+ }
393
+
394
+ // ---------- 汇总 ----------
395
+
396
+ function summary() {
397
+ header("测试汇总");
398
+ const total = passed + failed;
399
+ console.log(` 总计 ${total} 项 · 通过 ${passed} · 失败 ${failed}`);
400
+ if (failures.length) {
401
+ console.log("\n 失败明细:");
402
+ for (const f of failures) {
403
+ console.log(
404
+ ` ✗ ${f.msg}${f.detail ? " — " + truncate(f.detail, 300) : ""}`,
405
+ );
406
+ }
407
+ }
408
+ console.log("\n" + "─".repeat(70));
409
+ process.exitCode = failed ? 1 : 0;
410
+ }
411
+
412
+ // ---------- 主流程 ----------
413
+
414
+ async function main() {
415
+ header(`Agent API 接口测试`);
416
+ console.log(` 服务地址:${BASE_URL}`);
417
+ console.log(` Agent code:${AGENT_CODE}`);
418
+ console.log(` 测试消息:${TEST_MESSAGE}`);
419
+
420
+ const sessionId = await testCreateSession();
421
+ if (!sessionId) {
422
+ summary();
423
+ return;
424
+ }
425
+
426
+ await testHealth();
427
+ await testCapability();
428
+ await testListSessions();
429
+ await testGetSession(sessionId);
430
+ await testSendMessage(sessionId);
431
+ await testListMessages(sessionId);
432
+ await testStatelessInvoke();
433
+ await testStream(sessionId);
434
+ await testArchive(sessionId);
435
+ await testRestore(sessionId);
436
+
437
+ summary();
438
+ }
439
+
440
+ main().catch((error) => {
441
+ console.error("测试脚本执行异常:", error);
442
+ process.exitCode = 1;
443
+ });