@botbotgo/agent-harness 0.0.218 → 0.0.220
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/README.md +1 -1
- package/README.zh.md +1 -1
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/protocol/a2a/http.d.ts +1 -1
- package/dist/protocol/a2a/http.js +288 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1024,7 +1024,7 @@ ACP transport notes:
|
|
|
1024
1024
|
- `serveAcpHttp(runtime)` exposes JSON-RPC over HTTP plus SSE runtime events so remote operator surfaces can connect without importing the runtime in-process.
|
|
1025
1025
|
- ACP transport validation now covers the reference-client core flow: capability discovery, request submit, session lookup, request lookup, invalid-JSON handling, notification calls without response ids, stdio JSON-RPC, and HTTP plus SSE runtime notifications.
|
|
1026
1026
|
- For the thinnest editor or CLI starter, begin with `agent-harness acp serve --workspace . --transport stdio` and mirror the `examples/protocol-hello-world/app/acp-stdio-hello-world.mjs` wire shape. Applications that want an in-process reference client can use `createAcpStdioClient(...)` to issue JSON-RPC requests and route runtime notifications without hand-rolling line parsing.
|
|
1027
|
-
- `serveA2aHttp(runtime)` exposes an A2A-compatible HTTP JSON-RPC bridge plus agent card discovery, mapping both existing methods such as `message/send` and A2A v1.0 PascalCase methods such as `SendMessage`, `GetTask`, `ListTasks`, `CancelTask`, `SubscribeToTask`, and `GetExtendedAgentCard` onto the existing session/request runtime surface. The
|
|
1027
|
+
- `serveA2aHttp(runtime)` exposes an A2A-compatible HTTP JSON-RPC bridge plus agent card discovery, mapping both existing methods such as `message/send` and A2A v1.0 PascalCase methods such as `SendMessage`, `SendStreamingMessage`, `GetTask`, `ListTasks`, `CancelTask`, `SubscribeToTask`, and `GetExtendedAgentCard` onto the existing session/request runtime surface. The bridge now advertises both `1.0` and `0.3` JSON-RPC interfaces, validates `A2A-Version`, records `A2A-Extensions` into runtime invocation metadata, publishes `TASK_STATE_*` statuses plus the `{ task }` `SendMessage` wrapper, streams task snapshots over SSE for streaming methods, and still returns explicit unsupported errors for push notifications.
|
|
1028
1028
|
- `serveAgUiHttp(runtime)` exposes an AG-UI-compatible HTTP SSE bridge that projects runtime lifecycle, text output, upstream thinking, step progress, and tool calls onto `RUN_*`, `TEXT_MESSAGE_*`, `THINKING_TEXT_MESSAGE_*`, `STEP_*`, and `TOOL_CALL_*` events for UI clients.
|
|
1029
1029
|
- `createRuntimeMcpServer(runtime)` and `serveRuntimeMcpOverStdio(runtime)` expose the persisted runtime control surface itself as MCP tools, including sessions, requests, approvals, artifacts, events, and package export helpers.
|
|
1030
1030
|
- `listRequestEvents(...)` and `exportRequestPackage(...)` are the request-first inspection helpers.
|
package/README.zh.md
CHANGED
|
@@ -982,7 +982,7 @@ ACP transport 说明:
|
|
|
982
982
|
- `serveAcpHttp(runtime)` 提供基于 HTTP 的 JSON-RPC 与 SSE runtime events,适合远程界面或独立控制面接入。
|
|
983
983
|
- ACP transport 现已覆盖核心参考客户端流程验证:capability discovery、request submit、session lookup、request lookup、invalid JSON 处理、无 id notification 不返回响应,以及 stdio JSON-RPC 与 HTTP + SSE runtime notifications。
|
|
984
984
|
- 如果要从最薄的一层 editor / CLI starter 开始,优先用 `agent-harness acp serve --workspace . --transport stdio`,并直接参考 `examples/protocol-hello-world/app/acp-stdio-hello-world.mjs` 的 wire shape。需要在应用内使用 reference client 时,可直接用 `createAcpStdioClient(...)` 发起 JSON-RPC 请求并分流 runtime notifications,避免每个 sidecar 自己重写 line parsing。
|
|
985
|
-
- `serveA2aHttp(runtime)` 提供 A2A HTTP JSON-RPC bridge 与 agent card discovery,同时兼容 `message/send` 这类旧方法,以及 `SendMessage`、`GetTask`、`ListTasks`、`CancelTask`、`SubscribeToTask`、`GetExtendedAgentCard` 这类 A2A v1.0 PascalCase 方法,并统一映射到现有 session/request 运行记录。
|
|
985
|
+
- `serveA2aHttp(runtime)` 提供 A2A HTTP JSON-RPC bridge 与 agent card discovery,同时兼容 `message/send` 这类旧方法,以及 `SendMessage`、`SendStreamingMessage`、`GetTask`、`ListTasks`、`CancelTask`、`SubscribeToTask`、`GetExtendedAgentCard` 这类 A2A v1.0 PascalCase 方法,并统一映射到现有 session/request 运行记录。bridge 现在会同时声明 `1.0` 与 `0.3` 两个 JSON-RPC interface、校验 `A2A-Version`、把 `A2A-Extensions` 记录进 runtime invocation metadata、发布 `TASK_STATE_*` 状态与 `SendMessage` 的 `{ task }` wrapper,并在 streaming 方法上通过 SSE 输出 task snapshots;push notification 仍返回明确 unsupported error。
|
|
986
986
|
- `serveAgUiHttp(runtime)` 提供 AG-UI HTTP SSE bridge,把 runtime 生命周期、文本输出、upstream thinking、step 进度与 tool call 投影成 `RUN_*`、`TEXT_MESSAGE_*`、`THINKING_TEXT_MESSAGE_*`、`STEP_*` 与 `TOOL_CALL_*` 事件,便于 UI 客户端直接接入。
|
|
987
987
|
- `createRuntimeMcpServer(runtime)` 与 `serveRuntimeMcpOverStdio(runtime)` 会把持久化 runtime 控制面本身暴露成 MCP tools,包括 sessions、requests、approvals、artifacts、events 与 package export helpers。
|
|
988
988
|
- `listRequestEvents(...)` 与 `exportRequestPackage(...)` 是 request-first 的检查 helper。
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export declare const AGENT_HARNESS_VERSION = "0.0.219";
|
package/dist/package-version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export const AGENT_HARNESS_VERSION = "0.0.219";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
|
+
const SUPPORTED_A2A_VERSIONS = ["1.0", "0.3"];
|
|
2
3
|
function normalizePath(value, fallback) {
|
|
3
4
|
const source = typeof value === "string" && value.trim().length > 0 ? value.trim() : fallback;
|
|
4
5
|
return source.startsWith("/") ? source : `/${source}`;
|
|
@@ -8,6 +9,23 @@ function writeJson(response, statusCode, payload) {
|
|
|
8
9
|
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
9
10
|
response.end(JSON.stringify(payload));
|
|
10
11
|
}
|
|
12
|
+
function writeSseHeaders(response) {
|
|
13
|
+
response.statusCode = 200;
|
|
14
|
+
response.setHeader("content-type", "text/event-stream; charset=utf-8");
|
|
15
|
+
response.setHeader("cache-control", "no-cache, no-transform");
|
|
16
|
+
response.setHeader("connection", "keep-alive");
|
|
17
|
+
}
|
|
18
|
+
function writeSseEvent(response, event, payload) {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
response.write(`event: ${event}\ndata: ${JSON.stringify(payload)}\n\n`, (error) => {
|
|
21
|
+
if (error) {
|
|
22
|
+
reject(error);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
resolve();
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
11
29
|
function readRequestBody(request) {
|
|
12
30
|
return new Promise((resolve, reject) => {
|
|
13
31
|
const chunks = [];
|
|
@@ -36,6 +54,39 @@ function toSuccess(id, result) {
|
|
|
36
54
|
result,
|
|
37
55
|
};
|
|
38
56
|
}
|
|
57
|
+
function readServiceParameter(requestUrl, request, name) {
|
|
58
|
+
const headerValue = request.headers[name.toLowerCase()];
|
|
59
|
+
if (typeof headerValue === "string") {
|
|
60
|
+
return headerValue;
|
|
61
|
+
}
|
|
62
|
+
if (Array.isArray(headerValue)) {
|
|
63
|
+
return headerValue.join(",");
|
|
64
|
+
}
|
|
65
|
+
return requestUrl.searchParams.get(name) ?? undefined;
|
|
66
|
+
}
|
|
67
|
+
function parseA2aServiceParameters(requestUrl, request) {
|
|
68
|
+
const requestedVersion = readServiceParameter(requestUrl, request, "A2A-Version")?.trim() ?? "";
|
|
69
|
+
const normalizedVersion = requestedVersion.length === 0 ? "0.3" : requestedVersion;
|
|
70
|
+
if (!SUPPORTED_A2A_VERSIONS.includes(normalizedVersion)) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const requestedExtensions = readServiceParameter(requestUrl, request, "A2A-Extensions") ?? "";
|
|
74
|
+
return {
|
|
75
|
+
version: normalizedVersion,
|
|
76
|
+
extensions: requestedExtensions
|
|
77
|
+
.split(",")
|
|
78
|
+
.map((entry) => entry.trim())
|
|
79
|
+
.filter((entry) => entry.length > 0),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function acceptsSse(request) {
|
|
83
|
+
const accept = request.headers.accept;
|
|
84
|
+
if (accept === undefined) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
const values = Array.isArray(accept) ? accept : [accept];
|
|
88
|
+
return values.some((value) => value.includes("text/event-stream"));
|
|
89
|
+
}
|
|
39
90
|
function parseTextPart(part) {
|
|
40
91
|
if (!part || typeof part !== "object" || Array.isArray(part)) {
|
|
41
92
|
return null;
|
|
@@ -89,6 +140,20 @@ function parseMessageSendParams(params) {
|
|
|
89
140
|
...(invocation ? { invocation } : {}),
|
|
90
141
|
};
|
|
91
142
|
}
|
|
143
|
+
function attachServiceParameters(invocation, serviceParameters) {
|
|
144
|
+
return {
|
|
145
|
+
...(invocation ?? {}),
|
|
146
|
+
protocol: {
|
|
147
|
+
...(invocation?.protocol && typeof invocation.protocol === "object" && !Array.isArray(invocation.protocol)
|
|
148
|
+
? invocation.protocol
|
|
149
|
+
: {}),
|
|
150
|
+
a2a: {
|
|
151
|
+
version: serviceParameters.version,
|
|
152
|
+
...(serviceParameters.extensions.length > 0 ? { extensions: serviceParameters.extensions } : {}),
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
92
157
|
function parseTaskLocatorParams(params) {
|
|
93
158
|
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
94
159
|
throw new Error("A2A task lookup requires params object.");
|
|
@@ -310,6 +375,28 @@ async function buildTaskFromRuntime(runtime, requestId) {
|
|
|
310
375
|
const approvals = await runtime.listApprovals({ threadId: request.threadId, runId: request.runId });
|
|
311
376
|
return buildTaskFromSessionAndRequest(toSessionRecord(session), toRequestRecord(request), approvals);
|
|
312
377
|
}
|
|
378
|
+
async function hydrateTaskStateFromEvents(runtime, task) {
|
|
379
|
+
if (!task) {
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
const events = await runtime.listRunEvents(task.contextId, task.id);
|
|
383
|
+
const lastStateChange = [...events].reverse().find((event) => event.eventType === "run.state.changed");
|
|
384
|
+
const observedState = typeof lastStateChange?.payload?.state === "string" ? lastStateChange.payload.state : undefined;
|
|
385
|
+
if (!observedState) {
|
|
386
|
+
return task;
|
|
387
|
+
}
|
|
388
|
+
return {
|
|
389
|
+
...task,
|
|
390
|
+
status: {
|
|
391
|
+
...task.status,
|
|
392
|
+
state: mapRunState(observedState),
|
|
393
|
+
},
|
|
394
|
+
metadata: {
|
|
395
|
+
...task.metadata,
|
|
396
|
+
state: observedState,
|
|
397
|
+
},
|
|
398
|
+
};
|
|
399
|
+
}
|
|
313
400
|
async function listTasksFromRuntime(runtime, params) {
|
|
314
401
|
const runState = mapTaskStatusToRunState(params.state);
|
|
315
402
|
const runs = await runtime.listRuns({
|
|
@@ -353,6 +440,11 @@ function buildAgentCard(runtime, options) {
|
|
|
353
440
|
protocolBinding: "JSONRPC",
|
|
354
441
|
protocolVersion: "1.0",
|
|
355
442
|
},
|
|
443
|
+
{
|
|
444
|
+
url: options.rpcUrl,
|
|
445
|
+
protocolBinding: "JSONRPC",
|
|
446
|
+
protocolVersion: "0.3",
|
|
447
|
+
},
|
|
356
448
|
],
|
|
357
449
|
capabilities: {
|
|
358
450
|
streaming: false,
|
|
@@ -403,6 +495,174 @@ function toSendMessageResult(method, task) {
|
|
|
403
495
|
}
|
|
404
496
|
return { task };
|
|
405
497
|
}
|
|
498
|
+
function withStreamingStatusText(task, text) {
|
|
499
|
+
if (!task || typeof text !== "string" || text.length === 0) {
|
|
500
|
+
return task;
|
|
501
|
+
}
|
|
502
|
+
return {
|
|
503
|
+
...task,
|
|
504
|
+
status: {
|
|
505
|
+
...task.status,
|
|
506
|
+
message: {
|
|
507
|
+
role: "ROLE_AGENT",
|
|
508
|
+
messageId: `${task.id}:status`,
|
|
509
|
+
parts: toTextParts(text),
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
function withRunResult(task, result, streamedText) {
|
|
515
|
+
if (!task) {
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
const statusText = streamedText || result.output;
|
|
519
|
+
return {
|
|
520
|
+
...task,
|
|
521
|
+
status: {
|
|
522
|
+
...task.status,
|
|
523
|
+
state: mapRunState(result.state),
|
|
524
|
+
...(statusText
|
|
525
|
+
? {
|
|
526
|
+
message: {
|
|
527
|
+
role: "ROLE_AGENT",
|
|
528
|
+
messageId: `${task.id}:status`,
|
|
529
|
+
parts: toTextParts(statusText),
|
|
530
|
+
},
|
|
531
|
+
}
|
|
532
|
+
: {}),
|
|
533
|
+
},
|
|
534
|
+
metadata: {
|
|
535
|
+
...task.metadata,
|
|
536
|
+
state: result.state,
|
|
537
|
+
},
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
function isTerminalTaskState(state) {
|
|
541
|
+
return state === "TASK_STATE_COMPLETED" || state === "TASK_STATE_FAILED" || state === "TASK_STATE_CANCELED";
|
|
542
|
+
}
|
|
543
|
+
async function streamTaskUpdates(response, id, method, iterator) {
|
|
544
|
+
writeSseHeaders(response);
|
|
545
|
+
for await (const item of iterator) {
|
|
546
|
+
if (!item.task) {
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
await writeSseEvent(response, "task-update", toSuccess(id, toSendMessageResult(method, item.task)));
|
|
550
|
+
}
|
|
551
|
+
response.end();
|
|
552
|
+
}
|
|
553
|
+
async function* streamSendMessageTaskUpdates(runtime, options, method) {
|
|
554
|
+
let runId;
|
|
555
|
+
let streamedText = "";
|
|
556
|
+
for await (const item of runtime.streamEvents(options)) {
|
|
557
|
+
if (item.type === "event") {
|
|
558
|
+
runId = runId ?? item.event.runId;
|
|
559
|
+
if (item.event.eventType === "output.delta" && typeof item.event.payload.content === "string") {
|
|
560
|
+
streamedText += item.event.payload.content;
|
|
561
|
+
}
|
|
562
|
+
if (item.event.eventType === "run.created" || item.event.eventType === "run.state.changed" || item.event.eventType === "output.delta" || item.event.eventType === "approval.requested") {
|
|
563
|
+
const task = withStreamingStatusText(await buildTaskFromRuntime(runtime, item.event.runId), streamedText || undefined);
|
|
564
|
+
if (task) {
|
|
565
|
+
yield { task };
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
if (item.type === "result") {
|
|
571
|
+
runId = item.result.runId;
|
|
572
|
+
const task = withRunResult(await buildTaskFromRuntime(runtime, item.result.runId), item.result, streamedText || item.result.output);
|
|
573
|
+
yield { task };
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
if (item.type === "upstream-event" && runId) {
|
|
577
|
+
const task = withStreamingStatusText(await buildTaskFromRuntime(runtime, runId), streamedText || undefined);
|
|
578
|
+
if (task) {
|
|
579
|
+
yield { task };
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
async function streamExistingTaskUpdates(runtime, response, id, method, taskId) {
|
|
585
|
+
writeSseHeaders(response);
|
|
586
|
+
let currentTask = await hydrateTaskStateFromEvents(runtime, await buildTaskFromRuntime(runtime, taskId));
|
|
587
|
+
if (!currentTask) {
|
|
588
|
+
await writeSseEvent(response, "error", toError(id, -32001, "Task not found."));
|
|
589
|
+
response.end();
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
await writeSseEvent(response, "task-update", toSuccess(id, toSendMessageResult(method, currentTask)));
|
|
593
|
+
if (isTerminalTaskState(currentTask.status.state)) {
|
|
594
|
+
response.end();
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
await new Promise((resolve) => {
|
|
598
|
+
let closed = false;
|
|
599
|
+
let idleTimer;
|
|
600
|
+
let lastFingerprint = JSON.stringify({
|
|
601
|
+
state: currentTask?.status.state,
|
|
602
|
+
message: currentTask?.status.message,
|
|
603
|
+
});
|
|
604
|
+
const armIdleTimer = () => {
|
|
605
|
+
if (idleTimer) {
|
|
606
|
+
clearTimeout(idleTimer);
|
|
607
|
+
}
|
|
608
|
+
idleTimer = setTimeout(() => {
|
|
609
|
+
response.end();
|
|
610
|
+
finish();
|
|
611
|
+
}, 1_000);
|
|
612
|
+
};
|
|
613
|
+
const finish = () => {
|
|
614
|
+
if (closed) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
closed = true;
|
|
618
|
+
unsubscribe();
|
|
619
|
+
clearInterval(pollTimer);
|
|
620
|
+
if (idleTimer) {
|
|
621
|
+
clearTimeout(idleTimer);
|
|
622
|
+
}
|
|
623
|
+
resolve();
|
|
624
|
+
};
|
|
625
|
+
const emitLatestTask = async () => {
|
|
626
|
+
currentTask = await hydrateTaskStateFromEvents(runtime, await buildTaskFromRuntime(runtime, taskId));
|
|
627
|
+
if (!currentTask) {
|
|
628
|
+
await writeSseEvent(response, "error", toError(id, -32001, "Task not found."));
|
|
629
|
+
response.end();
|
|
630
|
+
finish();
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
const fingerprint = JSON.stringify({
|
|
634
|
+
state: currentTask.status.state,
|
|
635
|
+
message: currentTask.status.message,
|
|
636
|
+
});
|
|
637
|
+
if (fingerprint !== lastFingerprint) {
|
|
638
|
+
lastFingerprint = fingerprint;
|
|
639
|
+
await writeSseEvent(response, "task-update", toSuccess(id, toSendMessageResult(method, currentTask)));
|
|
640
|
+
armIdleTimer();
|
|
641
|
+
}
|
|
642
|
+
if (isTerminalTaskState(currentTask.status.state)) {
|
|
643
|
+
response.end();
|
|
644
|
+
finish();
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
const unsubscribe = runtime.subscribe((event) => {
|
|
648
|
+
if (event.runId !== taskId) {
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
if (event.eventType !== "run.state.changed"
|
|
652
|
+
&& event.eventType !== "output.delta"
|
|
653
|
+
&& event.eventType !== "approval.requested"
|
|
654
|
+
&& event.eventType !== "approval.resolved") {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
void emitLatestTask();
|
|
658
|
+
});
|
|
659
|
+
const pollTimer = setInterval(() => {
|
|
660
|
+
void emitLatestTask();
|
|
661
|
+
}, 250);
|
|
662
|
+
armIdleTimer();
|
|
663
|
+
response.on("close", finish);
|
|
664
|
+
});
|
|
665
|
+
}
|
|
406
666
|
export async function serveA2aOverHttp(runtime, options = {}) {
|
|
407
667
|
const hostname = options.hostname?.trim() || "127.0.0.1";
|
|
408
668
|
const port = typeof options.port === "number" && Number.isFinite(options.port) ? options.port : 0;
|
|
@@ -419,6 +679,16 @@ export async function serveA2aOverHttp(runtime, options = {}) {
|
|
|
419
679
|
const httpServer = createServer(async (request, response) => {
|
|
420
680
|
try {
|
|
421
681
|
const requestUrl = new URL(request.url ?? "/", `http://${hostname}`);
|
|
682
|
+
const serviceParameters = parseA2aServiceParameters(requestUrl, request);
|
|
683
|
+
if (!serviceParameters) {
|
|
684
|
+
writeJson(response, 400, {
|
|
685
|
+
error: {
|
|
686
|
+
code: -32009,
|
|
687
|
+
message: `A2A version not supported. Supported versions: ${SUPPORTED_A2A_VERSIONS.join(", ")}.`,
|
|
688
|
+
},
|
|
689
|
+
});
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
422
692
|
if (request.method === "GET" && (requestUrl.pathname === agentCardPath || requestUrl.pathname === "/.well-known/agent.json")) {
|
|
423
693
|
writeJson(response, 200, buildAgentCard(runtime, buildCardOptions()));
|
|
424
694
|
return;
|
|
@@ -443,7 +713,7 @@ export async function serveA2aOverHttp(runtime, options = {}) {
|
|
|
443
713
|
agentId: parsed.agentId ?? options.defaultAgentId,
|
|
444
714
|
input: parsed.input,
|
|
445
715
|
...(parsed.sessionId ? { threadId: parsed.sessionId } : {}),
|
|
446
|
-
|
|
716
|
+
invocation: attachServiceParameters(parsed.invocation, serviceParameters),
|
|
447
717
|
});
|
|
448
718
|
const session = await runtime.getThread(result.threadId);
|
|
449
719
|
const requestRecord = await runtime.getRun(result.runId);
|
|
@@ -452,8 +722,18 @@ export async function serveA2aOverHttp(runtime, options = {}) {
|
|
|
452
722
|
writeJson(response, 200, toSuccess(payload.id ?? null, toSendMessageResult(payload.method, task)));
|
|
453
723
|
return;
|
|
454
724
|
}
|
|
455
|
-
if (payload.method === "SendStreamingMessage") {
|
|
456
|
-
|
|
725
|
+
if (payload.method === "message/stream" || payload.method === "SendStreamingMessage") {
|
|
726
|
+
if (!acceptsSse(request)) {
|
|
727
|
+
writeJson(response, 406, toError(payload.id ?? null, -32004, "A2A streaming requires `Accept: text/event-stream`."));
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
const parsed = parseMessageSendParams(payload.params);
|
|
731
|
+
await streamTaskUpdates(response, payload.id ?? null, payload.method, streamSendMessageTaskUpdates(runtime, {
|
|
732
|
+
agentId: parsed.agentId ?? options.defaultAgentId,
|
|
733
|
+
input: parsed.input,
|
|
734
|
+
...(parsed.sessionId ? { threadId: parsed.sessionId } : {}),
|
|
735
|
+
invocation: attachServiceParameters(parsed.invocation, serviceParameters),
|
|
736
|
+
}, payload.method));
|
|
457
737
|
return;
|
|
458
738
|
}
|
|
459
739
|
if (payload.method === "tasks/get" || payload.method === "GetTask") {
|
|
@@ -486,7 +766,11 @@ export async function serveA2aOverHttp(runtime, options = {}) {
|
|
|
486
766
|
return;
|
|
487
767
|
}
|
|
488
768
|
if (payload.method === "SubscribeToTask") {
|
|
489
|
-
|
|
769
|
+
if (!acceptsSse(request)) {
|
|
770
|
+
writeJson(response, 406, toError(payload.id ?? null, -32004, "A2A streaming subscriptions require `Accept: text/event-stream`."));
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
await streamExistingTaskUpdates(runtime, response, payload.id ?? null, payload.method, taskId);
|
|
490
774
|
return;
|
|
491
775
|
}
|
|
492
776
|
writeJson(response, 200, toSuccess(payload.id ?? null, {
|