@botbotgo/agent-harness 0.0.167 → 0.0.169

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 CHANGED
@@ -902,6 +902,7 @@ Primary exports:
902
902
  - `createAcpServer`
903
903
  - `serveAcpHttp`
904
904
  - `serveAcpStdio`
905
+ - `serveA2aHttp`
905
906
  - `serveAgUiHttp`
906
907
  - `createRuntimeMcpServer`
907
908
  - `serveRuntimeMcpOverStdio`
@@ -926,7 +927,9 @@ ACP transport notes:
926
927
 
927
928
  - `serveAcpStdio(runtime)` exposes newline-delimited JSON-RPC over stdio for local IDE, CLI, or subprocess clients.
928
929
  - `serveAcpHttp(runtime)` exposes JSON-RPC over HTTP plus SSE runtime events so remote operator surfaces can connect without importing the runtime in-process.
930
+ - `serveA2aHttp(runtime)` exposes a minimal A2A-compatible HTTP JSON-RPC bridge plus agent card discovery, mapping `message/send`, `tasks/get`, and `tasks/cancel` onto the existing session/request runtime surface.
929
931
  - `serveAgUiHttp(runtime)` exposes a minimal AG-UI-compatible HTTP SSE bridge that projects `run + output.delta + final result` onto `RUN_STARTED`, `TEXT_MESSAGE_*`, and `RUN_FINISHED` events for UI clients.
930
932
  - `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.
931
933
  - `exportRunPackage(...)` and `exportSessionPackage(...)` package stable runtime records, transcript, approvals, events, and artifacts for operator tooling without reaching into persistence internals.
932
934
  - `runtime/default.governance.remoteMcp` can now deny or allow specific MCP servers, raise approval requirements by transport, and stamp transport-based risk tiers into runtime governance bundles.
935
+ - detailed A2A adapter guidance lives in [`docs/a2a-bridge.md`](docs/a2a-bridge.md)
package/README.zh.md CHANGED
@@ -861,6 +861,7 @@ spec:
861
861
  - `createAcpServer`
862
862
  - `serveAcpHttp`
863
863
  - `serveAcpStdio`
864
+ - `serveA2aHttp`
864
865
  - `serveAgUiHttp`
865
866
  - `createRuntimeMcpServer`
866
867
  - `serveRuntimeMcpOverStdio`
@@ -885,7 +886,9 @@ ACP transport 说明:
885
886
 
886
887
  - `serveAcpStdio(runtime)` 提供基于 stdio 的 newline-delimited JSON-RPC,适合本地 IDE、CLI 或子进程客户端。
887
888
  - `serveAcpHttp(runtime)` 提供基于 HTTP 的 JSON-RPC 与 SSE runtime events,适合远程 operator surface 或独立控制面接入。
889
+ - `serveA2aHttp(runtime)` 提供最小可用的 A2A HTTP JSON-RPC bridge 与 agent card discovery,把 `message/send`、`tasks/get`、`tasks/cancel` 映射到现有 session/request runtime surface。
888
890
  - `serveAgUiHttp(runtime)` 提供最小可用的 AG-UI HTTP SSE bridge,把现有 `run + output.delta + final result` 投影成 `RUN_STARTED`、`TEXT_MESSAGE_*` 与 `RUN_FINISHED` 事件,便于 UI 客户端直接接入。
889
891
  - `createRuntimeMcpServer(runtime)` 与 `serveRuntimeMcpOverStdio(runtime)` 会把持久化 runtime 控制面本身暴露成 MCP tools,包括 sessions、requests、approvals、artifacts、events 与 package export helpers。
890
892
  - `exportRunPackage(...)` 与 `exportSessionPackage(...)` 可把稳定 runtime 记录、transcript、approvals、events 和 artifacts 打包给 operator tooling,而不必直接访问 persistence 内部实现。
891
893
  - `runtime/default.governance.remoteMcp` 现在可以按 MCP server 或 transport 做 allow/deny、审批升级,并把 transport 风险等级写进 runtime governance bundles。
894
+ - 更详细的 A2A 适配层开发说明见 [`docs/a2a-bridge.md`](docs/a2a-bridge.md)
package/dist/api.d.ts CHANGED
@@ -10,6 +10,7 @@ export { buildFlowGraph, exportFlowGraphToMermaid, exportFlowGraphToSequenceMerm
10
10
  export { createUpstreamTimelineReducer } from "./upstream-events.js";
11
11
  export type { ListMemoriesInput, ListMemoriesResult, MemoryDecision, MemoryKind, MemoryRecord, MemoryScope, MemorizeInput, MemorizeResult, RecallInput, RecallResult, RemoveMemoryInput, RuntimeEvaluationExport, RuntimeEvaluationExportInput, RuntimeEvaluationReplayInput, RuntimeEvaluationReplayResult, RuntimeRunPackage, RuntimeRunPackageInput, RuntimeSessionPackage, RuntimeSessionPackageInput, UpdateMemoryInput, } from "./contracts/types.js";
12
12
  export type { AcpHttpServer, AcpHttpServerOptions } from "./protocol/acp/http.js";
13
+ export type { A2aAgentCard, A2aHttpServer, A2aHttpServerOptions, A2aTask, A2aTaskState } from "./protocol/a2a/http.js";
13
14
  export type { AcpStdioServer, AcpStdioServerOptions } from "./protocol/acp/stdio.js";
14
15
  export type { AgUiEvent, AgUiHttpServer, AgUiHttpServerOptions, AgUiRunAgentInput } from "./protocol/ag-ui/http.js";
15
16
  type PublicApprovalRecord = {
@@ -99,6 +100,7 @@ export declare function replayEvaluationBundle(runtime: AgentHarnessRuntime, inp
99
100
  export declare function serveAcpStdio(runtime: AgentHarnessRuntime, options?: import("./protocol/acp/stdio.js").AcpStdioServerOptions): import("./protocol/acp/stdio.js").AcpStdioServer;
100
101
  export declare function serveAcpHttp(runtime: AgentHarnessRuntime, options?: import("./protocol/acp/http.js").AcpHttpServerOptions): Promise<import("./protocol/acp/http.js").AcpHttpServer>;
101
102
  export declare function serveAgUiHttp(runtime: AgentHarnessRuntime, options?: import("./protocol/ag-ui/http.js").AgUiHttpServerOptions): Promise<import("./protocol/ag-ui/http.js").AgUiHttpServer>;
103
+ export declare function serveA2aHttp(runtime: AgentHarnessRuntime, options?: import("./protocol/a2a/http.js").A2aHttpServerOptions): Promise<import("./protocol/a2a/http.js").A2aHttpServer>;
102
104
  export declare function listAgentSkills(runtime: AgentHarnessRuntime, agentId: string, options?: RequirementAssessmentOptions): InventorySkillRecord[];
103
105
  export declare function getAgent(runtime: AgentHarnessRuntime, agentId: string, options?: RequirementAssessmentOptions): InventoryAgentRecord | null;
104
106
  export declare function describeInventory(runtime: AgentHarnessRuntime, options?: RequirementAssessmentOptions): {
package/dist/api.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { AgentHarnessRuntime } from "./runtime/harness.js";
2
+ import { serveA2aOverHttp } from "./protocol/a2a/http.js";
2
3
  import { serveAgUiOverHttp } from "./protocol/ag-ui/http.js";
3
4
  import { serveAcpOverHttp } from "./protocol/acp/http.js";
4
5
  import { serveAcpOverStdio } from "./protocol/acp/stdio.js";
@@ -216,6 +217,9 @@ export async function serveAcpHttp(runtime, options) {
216
217
  export async function serveAgUiHttp(runtime, options) {
217
218
  return serveAgUiOverHttp(runtime, options);
218
219
  }
220
+ export async function serveA2aHttp(runtime, options) {
221
+ return serveA2aOverHttp(runtime, options);
222
+ }
219
223
  export function listAgentSkills(runtime, agentId, options) {
220
224
  return runtime.listAgentSkills(agentId, options);
221
225
  }
package/dist/cli.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createAgentHarness } from "./api.js";
3
+ import { serveA2aOverHttp } from "./protocol/a2a/http.js";
3
4
  import { serveAgUiOverHttp } from "./protocol/ag-ui/http.js";
4
5
  import { serveAcpOverHttp } from "./protocol/acp/http.js";
5
6
  import { serveAcpOverStdio } from "./protocol/acp/stdio.js";
@@ -11,6 +12,7 @@ type CliIo = {
11
12
  };
12
13
  type CliDeps = {
13
14
  createAgentHarness?: typeof createAgentHarness;
15
+ serveA2aOverHttp?: typeof serveA2aOverHttp;
14
16
  serveAgUiOverHttp?: typeof serveAgUiOverHttp;
15
17
  serveAcpOverHttp?: typeof serveAcpOverHttp;
16
18
  serveAcpOverStdio?: typeof serveAcpOverStdio;
package/dist/cli.js CHANGED
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
4
  import { createAgentHarness } from "./api.js";
5
5
  import { initProject } from "./init-project.js";
6
+ import { serveA2aOverHttp } from "./protocol/a2a/http.js";
6
7
  import { serveAgUiOverHttp } from "./protocol/ag-ui/http.js";
7
8
  import { serveAcpOverHttp } from "./protocol/acp/http.js";
8
9
  import { serveAcpOverStdio } from "./protocol/acp/stdio.js";
@@ -11,6 +12,7 @@ function renderUsage() {
11
12
  return `Usage:
12
13
  agent-harness init <project-name> [--template deep-research|single-agent] [--provider <provider>] [--model <model>] [--with-web-search|--no-web-search]
13
14
  agent-harness acp serve [--workspace <path>] [--transport stdio|http] [--host <hostname>] [--port <port>]
15
+ agent-harness a2a serve [--workspace <path>] [--host <hostname>] [--port <port>]
14
16
  agent-harness ag-ui serve [--workspace <path>] [--host <hostname>] [--port <port>]
15
17
  agent-harness runtime-mcp serve [--workspace <path>]
16
18
  `;
@@ -108,7 +110,7 @@ function parseAcpServeOptions(args) {
108
110
  }
109
111
  return { workspaceRoot, transport, hostname, port };
110
112
  }
111
- function parseHttpServeOptions(args) {
113
+ function parseHttpServeOptions(args, serviceLabel = "HTTP") {
112
114
  let workspaceRoot;
113
115
  let hostname;
114
116
  let port;
@@ -139,7 +141,7 @@ function parseHttpServeOptions(args) {
139
141
  }
140
142
  const parsedPort = Number.parseInt(value, 10);
141
143
  if (!Number.isFinite(parsedPort) || parsedPort < 0) {
142
- return { workspaceRoot, hostname, error: `Invalid AG-UI port: ${value}` };
144
+ return { workspaceRoot, hostname, error: `Invalid ${serviceLabel} port: ${value}` };
143
145
  }
144
146
  port = parsedPort;
145
147
  index += 1;
@@ -155,6 +157,7 @@ export async function runCli(argv, io = {}, deps = {}) {
155
157
  const stderr = io.stderr ?? ((message) => process.stderr.write(message));
156
158
  const [command, projectName, ...rest] = argv;
157
159
  const createHarness = deps.createAgentHarness ?? createAgentHarness;
160
+ const serveA2a = deps.serveA2aOverHttp ?? serveA2aOverHttp;
158
161
  const serveAgUi = deps.serveAgUiOverHttp ?? serveAgUiOverHttp;
159
162
  const serveAcpHttp = deps.serveAcpOverHttp ?? serveAcpOverHttp;
160
163
  const serveAcp = deps.serveAcpOverStdio ?? serveAcpOverStdio;
@@ -232,7 +235,7 @@ export async function runCli(argv, io = {}, deps = {}) {
232
235
  stderr(renderUsage());
233
236
  return 1;
234
237
  }
235
- const parsed = parseHttpServeOptions(subcommandArgs);
238
+ const parsed = parseHttpServeOptions(subcommandArgs, "AG-UI");
236
239
  if (parsed.error) {
237
240
  stderr(`${parsed.error}\n`);
238
241
  stderr(renderUsage());
@@ -256,13 +259,43 @@ export async function runCli(argv, io = {}, deps = {}) {
256
259
  return 1;
257
260
  }
258
261
  }
262
+ if (command === "a2a") {
263
+ const [subcommand, ...subcommandArgs] = [projectName, ...rest];
264
+ if (subcommand !== "serve") {
265
+ stderr(renderUsage());
266
+ return 1;
267
+ }
268
+ const parsed = parseHttpServeOptions(subcommandArgs, "A2A");
269
+ if (parsed.error) {
270
+ stderr(`${parsed.error}\n`);
271
+ stderr(renderUsage());
272
+ return 1;
273
+ }
274
+ try {
275
+ const runtime = await createHarness(path.resolve(cwd, parsed.workspaceRoot ?? "."));
276
+ const workspacePath = path.resolve(cwd, parsed.workspaceRoot ?? ".");
277
+ const server = await serveA2a(runtime, {
278
+ hostname: parsed.hostname,
279
+ port: parsed.port,
280
+ });
281
+ stderr(`Serving A2A over http from ${workspacePath} at ${server.rpcUrl} (card ${server.agentCardUrl})\n`);
282
+ await server.completed;
283
+ await runtime.stop();
284
+ return 0;
285
+ }
286
+ catch (error) {
287
+ const message = error instanceof Error ? error.message : String(error);
288
+ stderr(`${message}\n`);
289
+ return 1;
290
+ }
291
+ }
259
292
  if (command === "runtime-mcp") {
260
293
  const [subcommand, ...subcommandArgs] = [projectName, ...rest];
261
294
  if (subcommand !== "serve") {
262
295
  stderr(renderUsage());
263
296
  return 1;
264
297
  }
265
- const parsed = parseHttpServeOptions(subcommandArgs);
298
+ const parsed = parseHttpServeOptions(subcommandArgs, "runtime MCP");
266
299
  if (parsed.error) {
267
300
  stderr(`${parsed.error}\n`);
268
301
  stderr(renderUsage());
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- export { AgentHarnessAcpServer, AgentHarnessRuntime, buildFlowGraph, cancelRun, createAgentHarness, createAcpServer, createRuntimeMcpServer, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, exportEvaluationBundle, exportRunPackage, exportSessionPackage, replayEvaluationBundle, getArtifact, getAgent, getApproval, getRequest, getHealth, listMemories, listRunEvents, getSession, listAgentSkills, listArtifacts, listApprovals, listRequests, listSessions, memorize, normalizeUserChatInput, recall, removeMemory, resolveApproval, run, serveAcpHttp, serveAcpStdio, serveAgUiHttp, serveRuntimeMcpOverStdio, serveToolsOverStdio, subscribe, stop, updateMemory, exportFlowGraphToMermaid, exportFlowGraphToSequenceMermaid, } from "./api.js";
1
+ export { AgentHarnessAcpServer, AgentHarnessRuntime, buildFlowGraph, cancelRun, createAgentHarness, createAcpServer, createRuntimeMcpServer, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, exportEvaluationBundle, exportRunPackage, exportSessionPackage, replayEvaluationBundle, getArtifact, getAgent, getApproval, getRequest, getHealth, listMemories, listRunEvents, getSession, listAgentSkills, listArtifacts, listApprovals, listRequests, listSessions, memorize, normalizeUserChatInput, recall, removeMemory, resolveApproval, run, serveA2aHttp, serveAcpHttp, serveAcpStdio, serveAgUiHttp, serveRuntimeMcpOverStdio, serveToolsOverStdio, subscribe, stop, updateMemory, exportFlowGraphToMermaid, exportFlowGraphToSequenceMermaid, } from "./api.js";
2
2
  export type { AcpApproval, AcpArtifact, AcpEventNotification, AcpJsonRpcError, AcpJsonRpcRequest, AcpJsonRpcResponse, AcpJsonRpcSuccess, AcpRequestRecord, AcpRunRequestParams, AcpServerCapabilities, AcpSessionRecord, } from "./acp.js";
3
3
  export type { ListMemoriesInput, ListMemoriesResult, MemoryDecision, MemoryKind, MemoryRecord, MemoryScope, MemorizeInput, MemorizeResult, NormalizeUserChatInputOptions, RecallInput, RecallResult, RemoveMemoryInput, RuntimeEvaluationExport, RuntimeEvaluationExportInput, RuntimeEvaluationReplayInput, RuntimeEvaluationReplayResult, RuntimeRunPackage, RuntimeRunPackageInput, RuntimeSessionPackage, RuntimeSessionPackageInput, UpdateMemoryInput, UserChatInput, UserChatMessage, } from "./api.js";
4
4
  export type { BuildFlowGraphInput, FlowEdge, FlowEdgeKind, FlowGraph, FlowGraphMermaidOptions, FlowGraphSequenceMermaidOptions, FlowGroup, FlowGroupKind, FlowNode, FlowNodeKind, FlowNodeLayer, FlowNodeStatus, } from "./flow/index.js";
5
- export type { AcpHttpServer, AcpHttpServerOptions, AcpStdioServer, AcpStdioServerOptions, AgUiEvent, AgUiHttpServer, AgUiHttpServerOptions, AgUiRunAgentInput, } from "./api.js";
5
+ export type { A2aAgentCard, A2aHttpServer, A2aHttpServerOptions, A2aTask, A2aTaskState, AcpHttpServer, AcpHttpServerOptions, AcpStdioServer, AcpStdioServerOptions, AgUiEvent, AgUiHttpServer, AgUiHttpServerOptions, AgUiRunAgentInput, } from "./api.js";
6
6
  export type { RuntimeMcpServerOptions, ToolMcpServerOptions } from "./mcp.js";
7
7
  export { tool } from "./tools.js";
8
8
  export type { UpstreamTimelineProjection, UpstreamTimelineReducer } from "./upstream-events.js";
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { AgentHarnessAcpServer, AgentHarnessRuntime, buildFlowGraph, cancelRun, createAgentHarness, createAcpServer, createRuntimeMcpServer, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, exportEvaluationBundle, exportRunPackage, exportSessionPackage, replayEvaluationBundle, getArtifact, getAgent, getApproval, getRequest, getHealth, listMemories, listRunEvents, getSession, listAgentSkills, listArtifacts, listApprovals, listRequests, listSessions, memorize, normalizeUserChatInput, recall, removeMemory, resolveApproval, run, serveAcpHttp, serveAcpStdio, serveAgUiHttp, serveRuntimeMcpOverStdio, serveToolsOverStdio, subscribe, stop, updateMemory, exportFlowGraphToMermaid, exportFlowGraphToSequenceMermaid, } from "./api.js";
1
+ export { AgentHarnessAcpServer, AgentHarnessRuntime, buildFlowGraph, cancelRun, createAgentHarness, createAcpServer, createRuntimeMcpServer, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, exportEvaluationBundle, exportRunPackage, exportSessionPackage, replayEvaluationBundle, getArtifact, getAgent, getApproval, getRequest, getHealth, listMemories, listRunEvents, getSession, listAgentSkills, listArtifacts, listApprovals, listRequests, listSessions, memorize, normalizeUserChatInput, recall, removeMemory, resolveApproval, run, serveA2aHttp, serveAcpHttp, serveAcpStdio, serveAgUiHttp, serveRuntimeMcpOverStdio, serveToolsOverStdio, subscribe, stop, updateMemory, exportFlowGraphToMermaid, exportFlowGraphToSequenceMermaid, } from "./api.js";
2
2
  export { tool } from "./tools.js";
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.166";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.168";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.166";
1
+ export const AGENT_HARNESS_VERSION = "0.0.168";
@@ -0,0 +1,86 @@
1
+ import type { AgentHarnessRuntime } from "../../runtime/harness.js";
2
+ export type A2aHttpServerOptions = {
3
+ hostname?: string;
4
+ port?: number;
5
+ rpcPath?: string;
6
+ agentCardPath?: string;
7
+ agentName?: string;
8
+ agentDescription?: string;
9
+ documentationUrl?: string;
10
+ provider?: {
11
+ organization?: string;
12
+ url?: string;
13
+ };
14
+ defaultAgentId?: string;
15
+ };
16
+ export type A2aAgentCard = {
17
+ name: string;
18
+ description: string;
19
+ version: string;
20
+ url: string;
21
+ preferredTransport: "JSONRPC";
22
+ provider?: {
23
+ organization?: string;
24
+ url?: string;
25
+ };
26
+ documentationUrl?: string;
27
+ capabilities: {
28
+ streaming: boolean;
29
+ pushNotifications: boolean;
30
+ stateTransitionHistory: boolean;
31
+ };
32
+ defaultInputModes: string[];
33
+ defaultOutputModes: string[];
34
+ skills: Array<{
35
+ id: string;
36
+ name: string;
37
+ description: string;
38
+ tags: string[];
39
+ examples: string[];
40
+ }>;
41
+ };
42
+ export type A2aTaskState = "submitted" | "working" | "input-required" | "completed" | "failed" | "canceled";
43
+ type A2aTaskStatus = {
44
+ state: A2aTaskState;
45
+ timestamp: string;
46
+ message?: {
47
+ role: "agent";
48
+ parts: Array<{
49
+ type: "text";
50
+ text: string;
51
+ }>;
52
+ };
53
+ };
54
+ export type A2aTask = {
55
+ id: string;
56
+ contextId: string;
57
+ kind: "task";
58
+ status: A2aTaskStatus;
59
+ history: Array<{
60
+ role: "user" | "agent";
61
+ parts: Array<{
62
+ type: "text";
63
+ text: string;
64
+ }>;
65
+ }>;
66
+ artifacts: Array<{
67
+ name: string;
68
+ parts: Array<{
69
+ type: "text";
70
+ text: string;
71
+ }>;
72
+ }>;
73
+ metadata: Record<string, unknown>;
74
+ };
75
+ export type A2aHttpServer = {
76
+ hostname: string;
77
+ port: number;
78
+ rpcPath: string;
79
+ agentCardPath: string;
80
+ rpcUrl: string;
81
+ agentCardUrl: string;
82
+ completed: Promise<void>;
83
+ close: () => Promise<void>;
84
+ };
85
+ export declare function serveA2aOverHttp(runtime: AgentHarnessRuntime, options?: A2aHttpServerOptions): Promise<A2aHttpServer>;
86
+ export {};
@@ -0,0 +1,431 @@
1
+ import { createServer } from "node:http";
2
+ function normalizePath(value, fallback) {
3
+ const source = typeof value === "string" && value.trim().length > 0 ? value.trim() : fallback;
4
+ return source.startsWith("/") ? source : `/${source}`;
5
+ }
6
+ function writeJson(response, statusCode, payload) {
7
+ response.statusCode = statusCode;
8
+ response.setHeader("content-type", "application/json; charset=utf-8");
9
+ response.end(JSON.stringify(payload));
10
+ }
11
+ function readRequestBody(request) {
12
+ return new Promise((resolve, reject) => {
13
+ const chunks = [];
14
+ request.on("data", (chunk) => {
15
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
16
+ });
17
+ request.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
18
+ request.on("error", reject);
19
+ });
20
+ }
21
+ function toError(id, code, message, data) {
22
+ return {
23
+ jsonrpc: "2.0",
24
+ id,
25
+ error: {
26
+ code,
27
+ message,
28
+ ...(data === undefined ? {} : { data }),
29
+ },
30
+ };
31
+ }
32
+ function toSuccess(id, result) {
33
+ return {
34
+ jsonrpc: "2.0",
35
+ id,
36
+ result,
37
+ };
38
+ }
39
+ function parseTextPart(part) {
40
+ if (!part || typeof part !== "object" || Array.isArray(part)) {
41
+ return null;
42
+ }
43
+ const typed = part;
44
+ if (typed.type !== "text" || typeof typed.text !== "string" || typed.text.trim().length === 0) {
45
+ return null;
46
+ }
47
+ return typed.text;
48
+ }
49
+ function parseMessageSendParams(params) {
50
+ if (!params || typeof params !== "object" || Array.isArray(params)) {
51
+ throw new Error("A2A message/send requires params object.");
52
+ }
53
+ const typed = params;
54
+ const message = typed.message;
55
+ if (!message || typeof message !== "object" || Array.isArray(message)) {
56
+ throw new Error("A2A message/send requires `message`.");
57
+ }
58
+ const typedMessage = message;
59
+ const role = typeof typedMessage.role === "string" ? typedMessage.role : "";
60
+ if (role !== "user") {
61
+ throw new Error("A2A message/send currently supports only `user` role messages.");
62
+ }
63
+ const parts = Array.isArray(typedMessage.parts) ? typedMessage.parts : [];
64
+ const textParts = parts.map(parseTextPart).filter((item) => Boolean(item));
65
+ if (textParts.length === 0) {
66
+ throw new Error("A2A message/send requires at least one text part.");
67
+ }
68
+ const sessionId = typeof typed.contextId === "string" && typed.contextId.trim().length > 0
69
+ ? typed.contextId.trim()
70
+ : undefined;
71
+ const agentId = typeof typed.agentId === "string" && typed.agentId.trim().length > 0
72
+ ? typed.agentId.trim()
73
+ : undefined;
74
+ const metadata = typed.metadata;
75
+ const invocation = metadata && typeof metadata === "object" && !Array.isArray(metadata)
76
+ ? metadata
77
+ : undefined;
78
+ return {
79
+ input: textParts.join("\n\n"),
80
+ ...(agentId ? { agentId } : {}),
81
+ ...(sessionId ? { sessionId } : {}),
82
+ ...(invocation ? { invocation } : {}),
83
+ };
84
+ }
85
+ function parseTaskLocatorParams(params) {
86
+ if (!params || typeof params !== "object" || Array.isArray(params)) {
87
+ throw new Error("A2A task lookup requires params object.");
88
+ }
89
+ const typed = params;
90
+ const taskId = typeof typed.id === "string" && typed.id.trim().length > 0
91
+ ? typed.id.trim()
92
+ : typeof typed.taskId === "string" && typed.taskId.trim().length > 0
93
+ ? typed.taskId.trim()
94
+ : "";
95
+ if (!taskId) {
96
+ throw new Error("A2A task lookup requires `id`.");
97
+ }
98
+ return { taskId };
99
+ }
100
+ function mapRunState(state) {
101
+ switch (state) {
102
+ case "queued":
103
+ return "submitted";
104
+ case "running":
105
+ return "working";
106
+ case "waiting_for_approval":
107
+ return "input-required";
108
+ case "completed":
109
+ return "completed";
110
+ case "cancelled":
111
+ return "canceled";
112
+ case "failed":
113
+ default:
114
+ return "failed";
115
+ }
116
+ }
117
+ function toTextParts(text) {
118
+ if (typeof text !== "string" || text.length === 0) {
119
+ return [];
120
+ }
121
+ return [{ type: "text", text }];
122
+ }
123
+ function contentToText(content) {
124
+ if (typeof content === "string") {
125
+ return content;
126
+ }
127
+ if (!Array.isArray(content)) {
128
+ return undefined;
129
+ }
130
+ const text = content
131
+ .filter((part) => part.type === "text" && typeof part.text === "string")
132
+ .map((part) => part.text)
133
+ .join("\n\n")
134
+ .trim();
135
+ return text.length > 0 ? text : undefined;
136
+ }
137
+ function buildTaskFromSessionAndRequest(session, request, approvals, output, failureMessage) {
138
+ if (!session || !request) {
139
+ return null;
140
+ }
141
+ const latestUserMessage = [...session.messages].reverse().find((message) => message.role === "user");
142
+ const latestAgentMessage = [...session.messages].reverse().find((message) => message.role === "assistant");
143
+ const approval = approvals.find((item) => item.status === "pending");
144
+ const latestAgentText = contentToText(latestAgentMessage?.content);
145
+ const latestUserText = contentToText(latestUserMessage?.content);
146
+ const statusText = request.state === "waiting_for_approval"
147
+ ? approval
148
+ ? `Approval required for tool ${approval.toolName}.`
149
+ : "Approval required."
150
+ : output ?? latestAgentText ?? failureMessage;
151
+ return {
152
+ id: request.requestId,
153
+ contextId: session.sessionId,
154
+ kind: "task",
155
+ status: {
156
+ state: mapRunState(request.state),
157
+ timestamp: request.updatedAt,
158
+ ...(statusText ? {
159
+ message: {
160
+ role: "agent",
161
+ parts: toTextParts(statusText),
162
+ },
163
+ } : {}),
164
+ },
165
+ history: [
166
+ ...(latestUserText
167
+ ? [{
168
+ role: "user",
169
+ parts: toTextParts(latestUserText),
170
+ }]
171
+ : []),
172
+ ...(latestAgentText
173
+ ? [{
174
+ role: "agent",
175
+ parts: toTextParts(latestAgentText),
176
+ }]
177
+ : []),
178
+ ],
179
+ artifacts: [],
180
+ metadata: {
181
+ agentId: request.agentId,
182
+ sessionId: session.sessionId,
183
+ requestId: request.requestId,
184
+ state: request.state,
185
+ approvalIds: approvals.map((item) => item.approvalId),
186
+ },
187
+ };
188
+ }
189
+ async function buildTaskFromRuntime(runtime, requestId) {
190
+ const request = await runtime.getRun(requestId);
191
+ if (!request) {
192
+ return null;
193
+ }
194
+ const session = await runtime.getThread(request.threadId);
195
+ const approvals = await runtime.listApprovals({ threadId: request.threadId, runId: request.runId });
196
+ return buildTaskFromSessionAndRequest(session ? {
197
+ sessionId: session.threadId,
198
+ entryAgentId: session.entryAgentId,
199
+ currentAgentId: session.currentAgentId,
200
+ currentState: session.currentState,
201
+ latestRequestId: session.latestRunId,
202
+ createdAt: session.createdAt,
203
+ updatedAt: session.updatedAt,
204
+ messages: session.messages,
205
+ requests: session.runs.map((run) => ({
206
+ requestId: run.runId,
207
+ sessionId: run.threadId,
208
+ agentId: run.agentId,
209
+ executionMode: run.executionMode,
210
+ adapterKind: run.adapterKind,
211
+ createdAt: run.createdAt,
212
+ updatedAt: run.updatedAt,
213
+ state: run.state,
214
+ checkpointRef: run.checkpointRef,
215
+ resumable: run.resumable,
216
+ startedAt: run.startedAt,
217
+ endedAt: run.endedAt,
218
+ lastActivityAt: run.lastActivityAt,
219
+ currentAgentId: run.currentAgentId,
220
+ delegationChain: run.delegationChain,
221
+ runtimeSnapshot: run.runtimeSnapshot,
222
+ })),
223
+ pendingDecision: session.pendingDecision,
224
+ } : null, {
225
+ requestId: request.runId,
226
+ sessionId: request.threadId,
227
+ agentId: request.agentId,
228
+ executionMode: request.executionMode,
229
+ adapterKind: request.adapterKind,
230
+ createdAt: request.createdAt,
231
+ updatedAt: request.updatedAt,
232
+ state: request.state,
233
+ checkpointRef: request.checkpointRef,
234
+ resumable: request.resumable,
235
+ startedAt: request.startedAt,
236
+ endedAt: request.endedAt,
237
+ lastActivityAt: request.lastActivityAt,
238
+ currentAgentId: request.currentAgentId,
239
+ delegationChain: request.delegationChain,
240
+ runtimeSnapshot: request.runtimeSnapshot,
241
+ }, approvals);
242
+ }
243
+ function buildAgentCard(runtime, options) {
244
+ const inventory = runtime.describeWorkspaceInventory();
245
+ const skills = inventory.agents.map((agent) => ({
246
+ id: agent.id,
247
+ name: agent.id,
248
+ description: agent.description || `Agent ${agent.id}`,
249
+ tags: ["agent-harness", agent.parentAgentId ? "subagent" : "agent"],
250
+ examples: [`Send a task to ${agent.id}.`],
251
+ }));
252
+ return {
253
+ name: options.agentName,
254
+ description: options.agentDescription,
255
+ version: "0.1.0",
256
+ url: options.rpcUrl,
257
+ preferredTransport: "JSONRPC",
258
+ capabilities: {
259
+ streaming: false,
260
+ pushNotifications: false,
261
+ stateTransitionHistory: true,
262
+ },
263
+ defaultInputModes: ["text"],
264
+ defaultOutputModes: ["text"],
265
+ skills,
266
+ };
267
+ }
268
+ export async function serveA2aOverHttp(runtime, options = {}) {
269
+ const hostname = options.hostname?.trim() || "127.0.0.1";
270
+ const port = typeof options.port === "number" && Number.isFinite(options.port) ? options.port : 0;
271
+ const rpcPath = normalizePath(options.rpcPath, "/a2a");
272
+ const agentCardPath = normalizePath(options.agentCardPath, "/.well-known/agent-card.json");
273
+ const httpServer = createServer(async (request, response) => {
274
+ try {
275
+ const requestUrl = new URL(request.url ?? "/", `http://${hostname}`);
276
+ if (request.method === "GET" && (requestUrl.pathname === agentCardPath || requestUrl.pathname === "/.well-known/agent.json")) {
277
+ const card = buildAgentCard(runtime, {
278
+ agentName: options.agentName ?? "agent-harness-runtime",
279
+ agentDescription: options.agentDescription ?? "Agent-harness A2A bridge over the persisted runtime surface.",
280
+ cardUrl: `http://${hostname}:${resolvedPort}${agentCardPath}`,
281
+ rpcUrl: `http://${hostname}:${resolvedPort}${rpcPath}`,
282
+ });
283
+ writeJson(response, 200, {
284
+ ...card,
285
+ ...(options.documentationUrl ? { documentationUrl: options.documentationUrl } : {}),
286
+ ...(options.provider ? { provider: options.provider } : {}),
287
+ ...(options.defaultAgentId ? { defaultAgentId: options.defaultAgentId } : {}),
288
+ });
289
+ return;
290
+ }
291
+ if (request.method === "POST" && (requestUrl.pathname === rpcPath || requestUrl.pathname === agentCardPath)) {
292
+ let payload;
293
+ try {
294
+ payload = JSON.parse(await readRequestBody(request));
295
+ }
296
+ catch {
297
+ writeJson(response, 400, toError(null, -32700, "Invalid JSON payload."));
298
+ return;
299
+ }
300
+ if (payload.jsonrpc !== "2.0" || typeof payload.method !== "string") {
301
+ writeJson(response, 400, toError(payload.id ?? null, -32600, "Invalid JSON-RPC request."));
302
+ return;
303
+ }
304
+ try {
305
+ if (payload.method === "message/send" || payload.method === "tasks/send") {
306
+ const parsed = parseMessageSendParams(payload.params);
307
+ const result = await runtime.run({
308
+ agentId: parsed.agentId ?? options.defaultAgentId,
309
+ input: parsed.input,
310
+ ...(parsed.sessionId ? { threadId: parsed.sessionId } : {}),
311
+ ...(parsed.invocation ? { invocation: parsed.invocation } : {}),
312
+ });
313
+ const session = await runtime.getThread(result.threadId);
314
+ const requestRecord = await runtime.getRun(result.runId);
315
+ const approvals = await runtime.listApprovals({ threadId: result.threadId, runId: result.runId });
316
+ const task = buildTaskFromSessionAndRequest(session ? {
317
+ sessionId: session.threadId,
318
+ entryAgentId: session.entryAgentId,
319
+ currentAgentId: session.currentAgentId,
320
+ currentState: session.currentState,
321
+ latestRequestId: session.latestRunId,
322
+ createdAt: session.createdAt,
323
+ updatedAt: session.updatedAt,
324
+ messages: session.messages,
325
+ requests: session.runs.map((run) => ({
326
+ requestId: run.runId,
327
+ sessionId: run.threadId,
328
+ agentId: run.agentId,
329
+ executionMode: run.executionMode,
330
+ adapterKind: run.adapterKind,
331
+ createdAt: run.createdAt,
332
+ updatedAt: run.updatedAt,
333
+ state: run.state,
334
+ checkpointRef: run.checkpointRef,
335
+ resumable: run.resumable,
336
+ startedAt: run.startedAt,
337
+ endedAt: run.endedAt,
338
+ lastActivityAt: run.lastActivityAt,
339
+ currentAgentId: run.currentAgentId,
340
+ delegationChain: run.delegationChain,
341
+ runtimeSnapshot: run.runtimeSnapshot,
342
+ })),
343
+ pendingDecision: session.pendingDecision,
344
+ } : null, requestRecord ? {
345
+ requestId: requestRecord.runId,
346
+ sessionId: requestRecord.threadId,
347
+ agentId: requestRecord.agentId,
348
+ executionMode: requestRecord.executionMode,
349
+ adapterKind: requestRecord.adapterKind,
350
+ createdAt: requestRecord.createdAt,
351
+ updatedAt: requestRecord.updatedAt,
352
+ state: requestRecord.state,
353
+ checkpointRef: requestRecord.checkpointRef,
354
+ resumable: requestRecord.resumable,
355
+ startedAt: requestRecord.startedAt,
356
+ endedAt: requestRecord.endedAt,
357
+ lastActivityAt: requestRecord.lastActivityAt,
358
+ currentAgentId: requestRecord.currentAgentId,
359
+ delegationChain: requestRecord.delegationChain,
360
+ runtimeSnapshot: requestRecord.runtimeSnapshot,
361
+ } : null, approvals, result.output);
362
+ writeJson(response, 200, toSuccess(payload.id ?? null, task));
363
+ return;
364
+ }
365
+ if (payload.method === "tasks/get") {
366
+ const { taskId } = parseTaskLocatorParams(payload.params);
367
+ const task = await buildTaskFromRuntime(runtime, taskId);
368
+ if (!task) {
369
+ writeJson(response, 200, toError(payload.id ?? null, -32004, "Task not found."));
370
+ return;
371
+ }
372
+ writeJson(response, 200, toSuccess(payload.id ?? null, task));
373
+ return;
374
+ }
375
+ if (payload.method === "tasks/cancel") {
376
+ const { taskId } = parseTaskLocatorParams(payload.params);
377
+ const result = await runtime.cancelRun({ runId: taskId, reason: "Cancelled via A2A bridge." });
378
+ const task = await buildTaskFromRuntime(runtime, result.runId);
379
+ writeJson(response, 200, toSuccess(payload.id ?? null, task));
380
+ return;
381
+ }
382
+ writeJson(response, 200, toError(payload.id ?? null, -32601, `Unknown A2A method: ${payload.method}`));
383
+ }
384
+ catch (error) {
385
+ writeJson(response, 200, toError(payload.id ?? null, -32000, error instanceof Error ? error.message : String(error)));
386
+ }
387
+ return;
388
+ }
389
+ writeJson(response, 404, {
390
+ error: "Not Found",
391
+ rpcPath,
392
+ agentCardPath,
393
+ });
394
+ }
395
+ catch (error) {
396
+ writeJson(response, 500, {
397
+ error: error instanceof Error ? error.message : "A2A HTTP transport failed.",
398
+ });
399
+ }
400
+ });
401
+ const completed = new Promise((resolve, reject) => {
402
+ httpServer.once("close", resolve);
403
+ httpServer.once("error", reject);
404
+ });
405
+ await new Promise((resolve, reject) => {
406
+ httpServer.listen(port, hostname, () => resolve());
407
+ httpServer.once("error", reject);
408
+ });
409
+ const address = httpServer.address();
410
+ const resolvedPort = typeof address === "object" && address ? address.port : port;
411
+ return {
412
+ hostname,
413
+ port: resolvedPort,
414
+ rpcPath,
415
+ agentCardPath,
416
+ rpcUrl: `http://${hostname}:${resolvedPort}${rpcPath}`,
417
+ agentCardUrl: `http://${hostname}:${resolvedPort}${agentCardPath}`,
418
+ completed,
419
+ close: async () => {
420
+ await new Promise((resolve, reject) => {
421
+ httpServer.close((error) => {
422
+ if (error) {
423
+ reject(error);
424
+ return;
425
+ }
426
+ resolve();
427
+ });
428
+ });
429
+ },
430
+ };
431
+ }
@@ -1,4 +1,4 @@
1
- import { isToolCallParseFailure, sanitizeVisibleText, } from "../../parsing/output-parsing.js";
1
+ import { isToolCallRecoveryFailure, sanitizeVisibleText, } from "../../parsing/output-parsing.js";
2
2
  import { buildInvocationRequest } from "../model/invocation-request.js";
3
3
  import { buildRawModelMessages } from "../model/message-assembly.js";
4
4
  import { projectRuntimeStreamEvent, createStreamEventProjectionState } from "../stream-event-projection.js";
@@ -82,7 +82,7 @@ export async function* streamRuntimeExecution(options) {
82
82
  error.message.includes("does not support tool binding")) {
83
83
  throw error;
84
84
  }
85
- if (!isToolCallParseFailure(error)) {
85
+ if (!isToolCallRecoveryFailure(error)) {
86
86
  throw error;
87
87
  }
88
88
  const retried = await options.invoke(options.applyStrictToolJsonInstruction(options.binding), options.input, options.threadId, options.runtimeOptions.runId ?? options.threadId, undefined, options.history, options.runtimeOptions);
@@ -1,5 +1,5 @@
1
1
  import { setTimeout as sleep } from "node:timers/promises";
2
- import { extractVisibleOutput, isToolCallParseFailure, STRICT_TOOL_JSON_INSTRUCTION } from "../parsing/output-parsing.js";
2
+ import { extractVisibleOutput, isToolCallRecoveryFailure, STRICT_TOOL_JSON_INSTRUCTION } from "../parsing/output-parsing.js";
3
3
  import { readStreamDelta } from "../parsing/stream-event-parsing.js";
4
4
  import { computeRemainingTimeoutMs, isRetryableProviderError, resolveProviderRetryPolicy } from "./resilience.js";
5
5
  import { isDeepAgentBinding, isLangChainBinding, withUpdatedBindingExecutionParams, } from "../support/compiled-binding.js";
@@ -153,7 +153,7 @@ export async function callRuntimeWithToolParseRecovery(input) {
153
153
  return await input.callRuntime(input.binding, input.request);
154
154
  }
155
155
  catch (error) {
156
- if (input.resumePayload !== undefined || !isToolCallParseFailure(error)) {
156
+ if (input.resumePayload !== undefined || !isToolCallRecoveryFailure(error)) {
157
157
  throw error;
158
158
  }
159
159
  return input.callRuntime(applyStrictToolJsonInstruction(input.binding), input.request);
@@ -10,6 +10,8 @@ export declare function extractOutputContent(value: unknown): unknown;
10
10
  export declare function extractContentBlocks(value: unknown): unknown[];
11
11
  export declare function extractEmptyAssistantMessageFailure(value: unknown): string;
12
12
  export declare function isToolCallParseFailure(error: unknown): boolean;
13
+ export declare function isToolCallValidationFailure(error: unknown): boolean;
14
+ export declare function isToolCallRecoveryFailure(error: unknown): boolean;
13
15
  export declare const STRICT_TOOL_JSON_INSTRUCTION = "When calling tools, return only the tool call itself. The arguments must be a pure JSON object with no explanatory text before or after it.";
14
16
  export declare function wrapResolvedModel<T>(value: T): T;
15
17
  export declare function extractReasoningText(value: unknown): string;
@@ -492,6 +492,28 @@ export function isToolCallParseFailure(error) {
492
492
  return false;
493
493
  return /error parsing tool call:/i.test(error.message);
494
494
  }
495
+ function isStructuredValidationIssue(value) {
496
+ if (typeof value !== "object" || !value || Array.isArray(value)) {
497
+ return false;
498
+ }
499
+ const typed = value;
500
+ return typeof typed.code === "string" && Array.isArray(typed.path) && (typed.message === undefined || typeof typed.message === "string");
501
+ }
502
+ export function isToolCallValidationFailure(error) {
503
+ if (!(error instanceof Error))
504
+ return false;
505
+ const message = error.message.trim();
506
+ if (!message)
507
+ return false;
508
+ const direct = tryParseJson(message);
509
+ if (Array.isArray(direct) && direct.length > 0 && direct.every((issue) => isStructuredValidationIssue(issue) && issue.path.length > 0)) {
510
+ return true;
511
+ }
512
+ return /Invalid input:\s*expected .* received undefined/i.test(message) && /"path"\s*:\s*\[/.test(message);
513
+ }
514
+ export function isToolCallRecoveryFailure(error) {
515
+ return isToolCallParseFailure(error) || isToolCallValidationFailure(error);
516
+ }
495
517
  export const STRICT_TOOL_JSON_INSTRUCTION = "When calling tools, return only the tool call itself. The arguments must be a pure JSON object with no explanatory text before or after it.";
496
518
  function appendStrictToolInstruction(input) {
497
519
  if (Array.isArray(input)) {
@@ -518,7 +540,7 @@ export function wrapResolvedModel(value) {
518
540
  return normalizeAgentMessage(await member.apply(currentTarget, args));
519
541
  }
520
542
  catch (error) {
521
- if (!isToolCallParseFailure(error)) {
543
+ if (!isToolCallRecoveryFailure(error)) {
522
544
  throw error;
523
545
  }
524
546
  const retryArgs = [...args];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.167",
3
+ "version": "0.0.169",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",