@botbotgo/agent-harness 0.0.214 → 0.0.216
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 +3 -1
- package/README.zh.md +3 -1
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/protocol/a2a/http.d.ts +17 -11
- package/dist/protocol/a2a/http.js +107 -38
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -130,6 +130,7 @@ The public API spans a full product runtime—persistent records, memory and evi
|
|
|
130
130
|
- **Governed workspace runtime:** YAML-owned routing, concurrency, maintenance, MCP policy, runtime governance bundles, and approval defaults for sensitive memory or write-like MCP side effects.
|
|
131
131
|
|
|
132
132
|
If you integrate external clients, treat `deepagents-acp` as the primary protocol direction: clients connect through that surface while `agent-harness` keeps persistence, recovery, approvals, and operator control on the runtime side.
|
|
133
|
+
Keep the standard stack split explicit: MCP connects agents to resources and tools, A2A bridges task exchange between agent platforms, ACP is the client-to-runtime boundary, AG-UI is the UI event surface, and runtime MCP exposes the operator control plane.
|
|
133
134
|
|
|
134
135
|
## The problem this solves
|
|
135
136
|
|
|
@@ -267,6 +268,7 @@ These scenarios map most directly to what the runtime is built for:
|
|
|
267
268
|
|
|
268
269
|
- Enterprise internal agent runtime: approvals, restart-safe recovery, operator evidence, and policy-owned MCP access.
|
|
269
270
|
- Code modernization runtime: long-running coding flows, approval checkpoints, resumable runs, and exported evidence packages.
|
|
271
|
+
- Agent-ops and remediation workflow runtime: parallel agent attempts, human review gates, durable evidence, and operator visibility for security or maintenance work.
|
|
270
272
|
- Protocol bridge runtime: ACP, A2A, AG-UI, and runtime MCP on one stable control plane instead of bespoke per-surface glue.
|
|
271
273
|
|
|
272
274
|
Typical runtime governance defaults now look like:
|
|
@@ -1022,7 +1024,7 @@ ACP transport notes:
|
|
|
1022
1024
|
- `serveAcpHttp(runtime)` exposes JSON-RPC over HTTP plus SSE runtime events so remote operator surfaces can connect without importing the runtime in-process.
|
|
1023
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.
|
|
1024
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.
|
|
1025
|
-
- `serveA2aHttp(runtime)` exposes an A2A-compatible HTTP JSON-RPC bridge plus agent card discovery, mapping both existing methods such as `message/send` and
|
|
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`, and `SubscribeToTask` onto the existing session/request runtime surface. The v1.0 path publishes `supportedInterfaces`, `TASK_STATE_*` statuses, the `{ task }` `SendMessage` wrapper, and explicit unsupported errors for streaming or push notifications.
|
|
1026
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.
|
|
1027
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.
|
|
1028
1030
|
- `listRequestEvents(...)` and `exportRequestPackage(...)` are the request-first inspection helpers.
|
package/README.zh.md
CHANGED
|
@@ -126,6 +126,7 @@ try {
|
|
|
126
126
|
- **受治理的工作区运行时:** 由 YAML 持有的路由、并发、维护、MCP 策略、runtime governance bundles,以及针对敏感 memory 或写类 MCP 副作用的默认审批门槛。
|
|
127
127
|
|
|
128
128
|
若你的产品需要对接外部客户端,可从本节理解边界:`deepagents-acp` 是主要的外部协议接入方向;持久化、恢复、审批与运行控制仍由 `agent-harness` 在运行时侧承担。
|
|
129
|
+
协议栈分工也要保持明确:MCP 连接资源和工具,A2A 承接 agent 平台之间的 task exchange,ACP 是 client-to-runtime 边界,AG-UI 是 UI 事件面,runtime MCP 则把 operator control plane 以 MCP tools 暴露出去。
|
|
129
130
|
|
|
130
131
|
## 这能解决什么问题
|
|
131
132
|
|
|
@@ -263,6 +264,7 @@ AI 让 agent 逻辑、工具调用和工作流代码更容易生成,真正更
|
|
|
263
264
|
|
|
264
265
|
- 企业内部智能体运行时:审批、重启恢复、运维侧证据链,以及由策略约束的 MCP 访问控制。
|
|
265
266
|
- 代码现代化运行时:长链路 coding flow、审批检查点、可恢复 runs,以及可导出的运行证据包。
|
|
267
|
+
- Agent ops 与 remediation workflow runtime:并行 agent 尝试、人工 review gate、持久证据包,以及面向安全或维护任务的 operator visibility。
|
|
266
268
|
- 协议桥接运行时:ACP、A2A、AG-UI 与 runtime MCP 共用一套稳定控制面,而不是为每个对外入口各写一层集成胶水。
|
|
267
269
|
|
|
268
270
|
一套常见的 runtime 治理默认值大致如下:
|
|
@@ -980,7 +982,7 @@ ACP transport 说明:
|
|
|
980
982
|
- `serveAcpHttp(runtime)` 提供基于 HTTP 的 JSON-RPC 与 SSE runtime events,适合远程界面或独立控制面接入。
|
|
981
983
|
- ACP transport 现已覆盖核心参考客户端流程验证:capability discovery、request submit、session lookup、request lookup、invalid JSON 处理、无 id notification 不返回响应,以及 stdio JSON-RPC 与 HTTP + SSE runtime notifications。
|
|
982
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。
|
|
983
|
-
- `serveA2aHttp(runtime)` 提供 A2A HTTP JSON-RPC bridge 与 agent card discovery,同时兼容 `message/send` 这类旧方法,以及 `SendMessage`、`GetTask`、`ListTasks`、`CancelTask`、`SubscribeToTask`
|
|
985
|
+
- `serveA2aHttp(runtime)` 提供 A2A HTTP JSON-RPC bridge 与 agent card discovery,同时兼容 `message/send` 这类旧方法,以及 `SendMessage`、`GetTask`、`ListTasks`、`CancelTask`、`SubscribeToTask` 这类 A2A v1.0 PascalCase 方法,并统一映射到现有 session/request 运行记录。v1.0 路径会发布 `supportedInterfaces`、`TASK_STATE_*` 状态、`SendMessage` 的 `{ task }` wrapper,并对 streaming 或 push notification 返回明确 unsupported error。
|
|
984
986
|
- `serveAgUiHttp(runtime)` 提供 AG-UI HTTP SSE bridge,把 runtime 生命周期、文本输出、upstream thinking、step 进度与 tool call 投影成 `RUN_*`、`TEXT_MESSAGE_*`、`THINKING_TEXT_MESSAGE_*`、`STEP_*` 与 `TOOL_CALL_*` 事件,便于 UI 客户端直接接入。
|
|
985
987
|
- `createRuntimeMcpServer(runtime)` 与 `serveRuntimeMcpOverStdio(runtime)` 会把持久化 runtime 控制面本身暴露成 MCP tools,包括 sessions、requests、approvals、artifacts、events 与 package export helpers。
|
|
986
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.215";
|
package/dist/package-version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export const AGENT_HARNESS_VERSION = "0.0.215";
|
|
@@ -18,20 +18,23 @@ export type A2aAgentCard = {
|
|
|
18
18
|
description: string;
|
|
19
19
|
version: string;
|
|
20
20
|
protocolVersion?: string;
|
|
21
|
-
url
|
|
22
|
-
preferredTransport
|
|
21
|
+
url?: string;
|
|
22
|
+
preferredTransport?: "JSONRPC";
|
|
23
23
|
provider?: {
|
|
24
24
|
organization?: string;
|
|
25
25
|
url?: string;
|
|
26
26
|
};
|
|
27
27
|
documentationUrl?: string;
|
|
28
28
|
defaultAgentId?: string;
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
supportedInterfaces: Array<{
|
|
30
|
+
url: string;
|
|
31
|
+
protocolBinding: "JSONRPC";
|
|
32
|
+
protocolVersion: "1.0";
|
|
33
|
+
}>;
|
|
31
34
|
capabilities: {
|
|
32
35
|
streaming: boolean;
|
|
33
36
|
pushNotifications: boolean;
|
|
34
|
-
|
|
37
|
+
extendedAgentCard: boolean;
|
|
35
38
|
};
|
|
36
39
|
defaultInputModes: string[];
|
|
37
40
|
defaultOutputModes: string[];
|
|
@@ -43,16 +46,17 @@ export type A2aAgentCard = {
|
|
|
43
46
|
examples: string[];
|
|
44
47
|
}>;
|
|
45
48
|
};
|
|
46
|
-
export type A2aTaskState = "
|
|
49
|
+
export type A2aTaskState = "TASK_STATE_SUBMITTED" | "TASK_STATE_WORKING" | "TASK_STATE_INPUT_REQUIRED" | "TASK_STATE_COMPLETED" | "TASK_STATE_FAILED" | "TASK_STATE_CANCELED";
|
|
47
50
|
type A2aTaskStatus = {
|
|
48
51
|
state: A2aTaskState;
|
|
49
52
|
timestamp: string;
|
|
50
53
|
messageId?: string;
|
|
51
54
|
message?: {
|
|
52
|
-
role: "
|
|
55
|
+
role: "ROLE_AGENT";
|
|
56
|
+
messageId: string;
|
|
53
57
|
parts: Array<{
|
|
54
|
-
type: "text";
|
|
55
58
|
text: string;
|
|
59
|
+
mediaType: "text/plain";
|
|
56
60
|
}>;
|
|
57
61
|
};
|
|
58
62
|
};
|
|
@@ -62,17 +66,19 @@ export type A2aTask = {
|
|
|
62
66
|
kind: "task";
|
|
63
67
|
status: A2aTaskStatus;
|
|
64
68
|
history: Array<{
|
|
65
|
-
role: "
|
|
69
|
+
role: "ROLE_USER" | "ROLE_AGENT";
|
|
70
|
+
messageId: string;
|
|
66
71
|
parts: Array<{
|
|
67
|
-
type: "text";
|
|
68
72
|
text: string;
|
|
73
|
+
mediaType: "text/plain";
|
|
69
74
|
}>;
|
|
70
75
|
}>;
|
|
71
76
|
artifacts: Array<{
|
|
77
|
+
artifactId: string;
|
|
72
78
|
name: string;
|
|
73
79
|
parts: Array<{
|
|
74
|
-
type: "text";
|
|
75
80
|
text: string;
|
|
81
|
+
mediaType: "text/plain";
|
|
76
82
|
}>;
|
|
77
83
|
}>;
|
|
78
84
|
metadata: Record<string, unknown>;
|
|
@@ -41,11 +41,16 @@ function parseTextPart(part) {
|
|
|
41
41
|
return null;
|
|
42
42
|
}
|
|
43
43
|
const typed = part;
|
|
44
|
-
if (
|
|
44
|
+
if (typeof typed.text !== "string"
|
|
45
|
+
|| typed.text.trim().length === 0
|
|
46
|
+
|| (typed.type !== undefined && typed.type !== "text")) {
|
|
45
47
|
return null;
|
|
46
48
|
}
|
|
47
49
|
return typed.text;
|
|
48
50
|
}
|
|
51
|
+
function normalizeUserRole(role) {
|
|
52
|
+
return typeof role === "string" ? role.toLowerCase().replace(/^role_/, "") : "";
|
|
53
|
+
}
|
|
49
54
|
function parseMessageSendParams(params) {
|
|
50
55
|
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
51
56
|
throw new Error("A2A message/send requires params object.");
|
|
@@ -56,9 +61,9 @@ function parseMessageSendParams(params) {
|
|
|
56
61
|
throw new Error("A2A message/send requires `message`.");
|
|
57
62
|
}
|
|
58
63
|
const typedMessage = message;
|
|
59
|
-
const role =
|
|
64
|
+
const role = normalizeUserRole(typedMessage.role);
|
|
60
65
|
if (role !== "user") {
|
|
61
|
-
throw new Error("A2A message/send currently supports only `user` role messages.");
|
|
66
|
+
throw new Error("A2A message/send currently supports only `user` / `ROLE_USER` role messages.");
|
|
62
67
|
}
|
|
63
68
|
const parts = Array.isArray(typedMessage.parts) ? typedMessage.parts : [];
|
|
64
69
|
const textParts = parts.map(parseTextPart).filter((item) => Boolean(item));
|
|
@@ -67,7 +72,9 @@ function parseMessageSendParams(params) {
|
|
|
67
72
|
}
|
|
68
73
|
const sessionId = typeof typed.contextId === "string" && typed.contextId.trim().length > 0
|
|
69
74
|
? typed.contextId.trim()
|
|
70
|
-
:
|
|
75
|
+
: typeof typedMessage.contextId === "string" && typedMessage.contextId.trim().length > 0
|
|
76
|
+
? typedMessage.contextId.trim()
|
|
77
|
+
: undefined;
|
|
71
78
|
const agentId = typeof typed.agentId === "string" && typed.agentId.trim().length > 0
|
|
72
79
|
? typed.agentId.trim()
|
|
73
80
|
: undefined;
|
|
@@ -102,39 +109,71 @@ function parseTaskListParams(params) {
|
|
|
102
109
|
return { limit: 20 };
|
|
103
110
|
}
|
|
104
111
|
const typed = params;
|
|
105
|
-
const
|
|
106
|
-
?
|
|
112
|
+
const rawLimit = typeof typed.pageSize === "number" && Number.isFinite(typed.pageSize)
|
|
113
|
+
? typed.pageSize
|
|
114
|
+
: typeof typed.limit === "number" && Number.isFinite(typed.limit)
|
|
115
|
+
? typed.limit
|
|
116
|
+
: 20;
|
|
117
|
+
const limitValue = typeof rawLimit === "number" && Number.isFinite(rawLimit)
|
|
118
|
+
? Math.max(1, Math.min(100, Math.trunc(rawLimit)))
|
|
107
119
|
: 20;
|
|
108
120
|
return {
|
|
109
121
|
...(typeof typed.agentId === "string" && typed.agentId.trim().length > 0 ? { agentId: typed.agentId.trim() } : {}),
|
|
110
122
|
...(typeof typed.contextId === "string" && typed.contextId.trim().length > 0 ? { contextId: typed.contextId.trim() } : {}),
|
|
111
|
-
...(typeof typed.
|
|
112
|
-
|
|
123
|
+
...(typeof typed.status === "string" && typed.status.trim().length > 0
|
|
124
|
+
? { state: typed.status.trim() }
|
|
125
|
+
: typeof typed.state === "string" && typed.state.trim().length > 0
|
|
126
|
+
? { state: typed.state.trim() }
|
|
127
|
+
: {}),
|
|
128
|
+
...(typeof typed.pageToken === "string" && typed.pageToken.trim().length > 0
|
|
129
|
+
? { cursor: typed.pageToken.trim() }
|
|
130
|
+
: typeof typed.cursor === "string" && typed.cursor.trim().length > 0
|
|
131
|
+
? { cursor: typed.cursor.trim() }
|
|
132
|
+
: {}),
|
|
113
133
|
limit: limitValue,
|
|
114
134
|
};
|
|
115
135
|
}
|
|
116
136
|
function mapRunState(state) {
|
|
117
137
|
switch (state) {
|
|
118
138
|
case "queued":
|
|
119
|
-
return "
|
|
139
|
+
return "TASK_STATE_SUBMITTED";
|
|
120
140
|
case "running":
|
|
121
|
-
return "
|
|
141
|
+
return "TASK_STATE_WORKING";
|
|
122
142
|
case "waiting_for_approval":
|
|
123
|
-
return "
|
|
143
|
+
return "TASK_STATE_INPUT_REQUIRED";
|
|
124
144
|
case "completed":
|
|
125
|
-
return "
|
|
145
|
+
return "TASK_STATE_COMPLETED";
|
|
126
146
|
case "cancelled":
|
|
127
|
-
return "
|
|
147
|
+
return "TASK_STATE_CANCELED";
|
|
128
148
|
case "failed":
|
|
129
149
|
default:
|
|
150
|
+
return "TASK_STATE_FAILED";
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function mapTaskStatusToRunState(state) {
|
|
154
|
+
switch (state?.toLowerCase().replace(/^task_state_/, "").replace(/_/g, "-")) {
|
|
155
|
+
case "submitted":
|
|
156
|
+
return "queued";
|
|
157
|
+
case "working":
|
|
158
|
+
return "running";
|
|
159
|
+
case "input-required":
|
|
160
|
+
return "waiting_for_approval";
|
|
161
|
+
case "completed":
|
|
162
|
+
return "completed";
|
|
163
|
+
case "failed":
|
|
130
164
|
return "failed";
|
|
165
|
+
case "canceled":
|
|
166
|
+
case "cancelled":
|
|
167
|
+
return "cancelled";
|
|
168
|
+
default:
|
|
169
|
+
return undefined;
|
|
131
170
|
}
|
|
132
171
|
}
|
|
133
172
|
function toTextParts(text) {
|
|
134
173
|
if (typeof text !== "string" || text.length === 0) {
|
|
135
174
|
return [];
|
|
136
175
|
}
|
|
137
|
-
return [{
|
|
176
|
+
return [{ text, mediaType: "text/plain" }];
|
|
138
177
|
}
|
|
139
178
|
function contentToText(content) {
|
|
140
179
|
if (typeof content === "string") {
|
|
@@ -230,7 +269,8 @@ function buildTaskFromSessionAndRequest(session, request, approvals, output, fai
|
|
|
230
269
|
timestamp: request.updatedAt,
|
|
231
270
|
...(statusText ? {
|
|
232
271
|
message: {
|
|
233
|
-
role: "
|
|
272
|
+
role: "ROLE_AGENT",
|
|
273
|
+
messageId: `${request.requestId}:status`,
|
|
234
274
|
parts: toTextParts(statusText),
|
|
235
275
|
},
|
|
236
276
|
} : {}),
|
|
@@ -238,13 +278,15 @@ function buildTaskFromSessionAndRequest(session, request, approvals, output, fai
|
|
|
238
278
|
history: [
|
|
239
279
|
...(latestUserText
|
|
240
280
|
? [{
|
|
241
|
-
role: "
|
|
281
|
+
role: "ROLE_USER",
|
|
282
|
+
messageId: `${request.requestId}:user`,
|
|
242
283
|
parts: toTextParts(latestUserText),
|
|
243
284
|
}]
|
|
244
285
|
: []),
|
|
245
286
|
...(latestAgentText
|
|
246
287
|
? [{
|
|
247
|
-
role: "
|
|
288
|
+
role: "ROLE_AGENT",
|
|
289
|
+
messageId: `${request.requestId}:agent`,
|
|
248
290
|
parts: toTextParts(latestAgentText),
|
|
249
291
|
}]
|
|
250
292
|
: []),
|
|
@@ -269,18 +311,23 @@ async function buildTaskFromRuntime(runtime, requestId) {
|
|
|
269
311
|
return buildTaskFromSessionAndRequest(toSessionRecord(session), toRequestRecord(request), approvals);
|
|
270
312
|
}
|
|
271
313
|
async function listTasksFromRuntime(runtime, params) {
|
|
314
|
+
const runState = mapTaskStatusToRunState(params.state);
|
|
272
315
|
const runs = await runtime.listRuns({
|
|
273
316
|
...(params.agentId ? { agentId: params.agentId } : {}),
|
|
274
317
|
...(params.contextId ? { threadId: params.contextId } : {}),
|
|
275
|
-
...(
|
|
318
|
+
...(runState ? { state: runState } : {}),
|
|
276
319
|
});
|
|
277
320
|
const startIndex = params.cursor ? Number.parseInt(Buffer.from(params.cursor, "base64url").toString("utf8"), 10) || 0 : 0;
|
|
278
321
|
const page = runs.slice(startIndex, startIndex + params.limit);
|
|
279
322
|
const tasks = (await Promise.all(page.map((run) => buildTaskFromRuntime(runtime, run.runId)))).filter((task) => Boolean(task));
|
|
280
323
|
const nextIndex = startIndex + page.length;
|
|
324
|
+
const nextPageToken = nextIndex < runs.length ? Buffer.from(String(nextIndex), "utf8").toString("base64url") : "";
|
|
281
325
|
return {
|
|
282
326
|
tasks,
|
|
283
|
-
|
|
327
|
+
pageSize: params.limit,
|
|
328
|
+
totalSize: runs.length,
|
|
329
|
+
nextPageToken,
|
|
330
|
+
...(nextPageToken ? { nextCursor: nextPageToken } : {}),
|
|
284
331
|
};
|
|
285
332
|
}
|
|
286
333
|
function buildAgentCard(runtime, options) {
|
|
@@ -296,33 +343,36 @@ function buildAgentCard(runtime, options) {
|
|
|
296
343
|
name: options.agentName,
|
|
297
344
|
description: options.agentDescription,
|
|
298
345
|
version: "0.1.0",
|
|
346
|
+
// Older JSON-RPC clients may still read these top-level fields; A2A v1.0 discovery uses `supportedInterfaces`.
|
|
299
347
|
protocolVersion: "1.0",
|
|
300
348
|
url: options.rpcUrl,
|
|
301
349
|
preferredTransport: "JSONRPC",
|
|
302
|
-
supportsAuthenticatedExtendedCard: false,
|
|
303
350
|
supportedInterfaces: [
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
"tasks/subscribe",
|
|
310
|
-
"SendMessage",
|
|
311
|
-
"GetTask",
|
|
312
|
-
"ListTasks",
|
|
313
|
-
"CancelTask",
|
|
314
|
-
"SubscribeToTask",
|
|
351
|
+
{
|
|
352
|
+
url: options.rpcUrl,
|
|
353
|
+
protocolBinding: "JSONRPC",
|
|
354
|
+
protocolVersion: "1.0",
|
|
355
|
+
},
|
|
315
356
|
],
|
|
316
357
|
capabilities: {
|
|
317
358
|
streaming: false,
|
|
318
359
|
pushNotifications: false,
|
|
319
|
-
|
|
360
|
+
extendedAgentCard: false,
|
|
320
361
|
},
|
|
321
|
-
defaultInputModes: ["text"],
|
|
322
|
-
defaultOutputModes: ["text"],
|
|
362
|
+
defaultInputModes: ["text/plain"],
|
|
363
|
+
defaultOutputModes: ["text/plain"],
|
|
323
364
|
skills,
|
|
324
365
|
};
|
|
325
366
|
}
|
|
367
|
+
function isV1Method(method) {
|
|
368
|
+
return /^[A-Z]/.test(method);
|
|
369
|
+
}
|
|
370
|
+
function toSendMessageResult(method, task) {
|
|
371
|
+
if (!task || !isV1Method(method)) {
|
|
372
|
+
return task;
|
|
373
|
+
}
|
|
374
|
+
return { task };
|
|
375
|
+
}
|
|
326
376
|
export async function serveA2aOverHttp(runtime, options = {}) {
|
|
327
377
|
const hostname = options.hostname?.trim() || "127.0.0.1";
|
|
328
378
|
const port = typeof options.port === "number" && Number.isFinite(options.port) ? options.port : 0;
|
|
@@ -372,14 +422,18 @@ export async function serveA2aOverHttp(runtime, options = {}) {
|
|
|
372
422
|
const requestRecord = await runtime.getRun(result.runId);
|
|
373
423
|
const approvals = await runtime.listApprovals({ threadId: result.threadId, runId: result.runId });
|
|
374
424
|
const task = buildTaskFromSessionAndRequest(toSessionRecord(session), toRequestRecord(requestRecord), approvals, result.output);
|
|
375
|
-
writeJson(response, 200, toSuccess(payload.id ?? null, task));
|
|
425
|
+
writeJson(response, 200, toSuccess(payload.id ?? null, toSendMessageResult(payload.method, task)));
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (payload.method === "SendStreamingMessage") {
|
|
429
|
+
writeJson(response, 200, toError(payload.id ?? null, -32004, "A2A streaming is not supported by this bridge."));
|
|
376
430
|
return;
|
|
377
431
|
}
|
|
378
432
|
if (payload.method === "tasks/get" || payload.method === "GetTask") {
|
|
379
433
|
const { taskId } = parseTaskLocatorParams(payload.params);
|
|
380
434
|
const task = await buildTaskFromRuntime(runtime, taskId);
|
|
381
435
|
if (!task) {
|
|
382
|
-
writeJson(response, 200, toError(payload.id ?? null, -
|
|
436
|
+
writeJson(response, 200, toError(payload.id ?? null, -32001, "Task not found."));
|
|
383
437
|
return;
|
|
384
438
|
}
|
|
385
439
|
writeJson(response, 200, toSuccess(payload.id ?? null, task));
|
|
@@ -401,19 +455,34 @@ export async function serveA2aOverHttp(runtime, options = {}) {
|
|
|
401
455
|
const { taskId } = parseTaskLocatorParams(payload.params);
|
|
402
456
|
const task = await buildTaskFromRuntime(runtime, taskId);
|
|
403
457
|
if (!task) {
|
|
404
|
-
writeJson(response, 200, toError(payload.id ?? null, -
|
|
458
|
+
writeJson(response, 200, toError(payload.id ?? null, -32001, "Task not found."));
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
if (payload.method === "SubscribeToTask") {
|
|
462
|
+
writeJson(response, 200, toError(payload.id ?? null, -32004, "A2A streaming subscriptions are not supported by this bridge."));
|
|
405
463
|
return;
|
|
406
464
|
}
|
|
407
465
|
writeJson(response, 200, toSuccess(payload.id ?? null, {
|
|
408
466
|
task,
|
|
409
467
|
streamable: false,
|
|
410
468
|
pushNotifications: false,
|
|
411
|
-
nextPollAfterMs: task.status.state === "
|
|
469
|
+
nextPollAfterMs: task.status.state === "TASK_STATE_COMPLETED" || task.status.state === "TASK_STATE_FAILED" || task.status.state === "TASK_STATE_CANCELED"
|
|
412
470
|
? 0
|
|
413
471
|
: 1_000,
|
|
414
472
|
}));
|
|
415
473
|
return;
|
|
416
474
|
}
|
|
475
|
+
if (payload.method === "CreateTaskPushNotificationConfig"
|
|
476
|
+
|| payload.method === "GetTaskPushNotificationConfig"
|
|
477
|
+
|| payload.method === "ListTaskPushNotificationConfigs"
|
|
478
|
+
|| payload.method === "DeleteTaskPushNotificationConfig") {
|
|
479
|
+
writeJson(response, 200, toError(payload.id ?? null, -32003, "A2A push notifications are not supported by this bridge."));
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
if (payload.method === "GetExtendedAgentCard") {
|
|
483
|
+
writeJson(response, 200, toError(payload.id ?? null, -32004, "A2A extended agent cards are not supported by this bridge."));
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
417
486
|
writeJson(response, 200, toError(payload.id ?? null, -32601, `Unknown A2A method: ${payload.method}`));
|
|
418
487
|
}
|
|
419
488
|
catch (error) {
|