@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 +3 -0
- package/README.zh.md +3 -0
- package/dist/api.d.ts +2 -0
- package/dist/api.js +4 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +37 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/protocol/a2a/http.d.ts +86 -0
- package/dist/protocol/a2a/http.js +431 -0
- package/dist/runtime/adapter/flow/stream-runtime.js +2 -2
- package/dist/runtime/adapter/runtime-shell.js +2 -2
- package/dist/runtime/parsing/output-parsing.d.ts +2 -0
- package/dist/runtime/parsing/output-parsing.js +23 -1
- package/package.json +1 -1
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
|
|
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.
|
|
1
|
+
export declare const AGENT_HARNESS_VERSION = "0.0.168";
|
package/dist/package-version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const AGENT_HARNESS_VERSION = "0.0.
|
|
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 {
|
|
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 (!
|
|
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,
|
|
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 || !
|
|
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 (!
|
|
543
|
+
if (!isToolCallRecoveryFailure(error)) {
|
|
522
544
|
throw error;
|
|
523
545
|
}
|
|
524
546
|
const retryArgs = [...args];
|