@botbotgo/agent-harness 0.0.164 → 0.0.166

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
@@ -28,7 +28,7 @@
28
28
 
29
29
  <p align="center">
30
30
  <a href="https://botbotgo.github.io/agent-harness/development/">Developer docs</a>
31
- (multi-page static docs in <code>docs/development/</code>)
31
+ (<code>docs/development/</code>, English / 中文)
32
32
  </p>
33
33
 
34
34
  <p align="center">
@@ -902,6 +902,7 @@ Primary exports:
902
902
  - `createAcpServer`
903
903
  - `serveAcpHttp`
904
904
  - `serveAcpStdio`
905
+ - `serveAgUiHttp`
905
906
  - `createToolMcpServer`
906
907
  - `serveToolsOverStdio`
907
908
  - `stop`
@@ -923,5 +924,6 @@ ACP transport notes:
923
924
 
924
925
  - `serveAcpStdio(runtime)` exposes newline-delimited JSON-RPC over stdio for local IDE, CLI, or subprocess clients.
925
926
  - `serveAcpHttp(runtime)` exposes JSON-RPC over HTTP plus SSE runtime events so remote operator surfaces can connect without importing the runtime in-process.
927
+ - `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.
926
928
  - `exportRunPackage(...)` and `exportSessionPackage(...)` package stable runtime records, transcript, approvals, events, and artifacts for operator tooling without reaching into persistence internals.
927
929
  - `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.
package/README.zh.md CHANGED
@@ -28,7 +28,7 @@
28
28
 
29
29
  <p align="center">
30
30
  <a href="https://botbotgo.github.io/agent-harness/development/">开发文档</a>
31
- (多页面静态文档位于 <code>docs/development/</code>)
31
+ (多页面静态文档位于 <code>docs/development/</code>,支持 English / 中文)
32
32
  </p>
33
33
 
34
34
  <p align="center">
@@ -861,6 +861,7 @@ spec:
861
861
  - `createAcpServer`
862
862
  - `serveAcpHttp`
863
863
  - `serveAcpStdio`
864
+ - `serveAgUiHttp`
864
865
  - `createToolMcpServer`
865
866
  - `serveToolsOverStdio`
866
867
  - `stop`
@@ -882,5 +883,6 @@ ACP transport 说明:
882
883
 
883
884
  - `serveAcpStdio(runtime)` 提供基于 stdio 的 newline-delimited JSON-RPC,适合本地 IDE、CLI 或子进程客户端。
884
885
  - `serveAcpHttp(runtime)` 提供基于 HTTP 的 JSON-RPC 与 SSE runtime events,适合远程 operator surface 或独立控制面接入。
886
+ - `serveAgUiHttp(runtime)` 提供最小可用的 AG-UI HTTP SSE bridge,把现有 `run + output.delta + final result` 投影成 `RUN_STARTED`、`TEXT_MESSAGE_*` 与 `RUN_FINISHED` 事件,便于 UI 客户端直接接入。
885
887
  - `exportRunPackage(...)` 与 `exportSessionPackage(...)` 可把稳定 runtime 记录、transcript、approvals、events 和 artifacts 打包给 operator tooling,而不必直接访问 persistence 内部实现。
886
888
  - `runtime/default.governance.remoteMcp` 现在可以按 MCP server 或 transport 做 allow/deny、审批升级,并把 transport 风险等级写进 runtime governance bundles。
package/dist/api.d.ts CHANGED
@@ -11,6 +11,7 @@ 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
13
  export type { AcpStdioServer, AcpStdioServerOptions } from "./protocol/acp/stdio.js";
14
+ export type { AgUiEvent, AgUiHttpServer, AgUiHttpServerOptions, AgUiRunAgentInput } from "./protocol/ag-ui/http.js";
14
15
  type PublicApprovalRecord = {
15
16
  approvalId: string;
16
17
  pendingActionId: string;
@@ -97,6 +98,7 @@ export declare function exportEvaluationBundle(runtime: AgentHarnessRuntime, inp
97
98
  export declare function replayEvaluationBundle(runtime: AgentHarnessRuntime, input: RuntimeEvaluationReplayInput): Promise<RuntimeEvaluationReplayResult>;
98
99
  export declare function serveAcpStdio(runtime: AgentHarnessRuntime, options?: import("./protocol/acp/stdio.js").AcpStdioServerOptions): import("./protocol/acp/stdio.js").AcpStdioServer;
99
100
  export declare function serveAcpHttp(runtime: AgentHarnessRuntime, options?: import("./protocol/acp/http.js").AcpHttpServerOptions): Promise<import("./protocol/acp/http.js").AcpHttpServer>;
101
+ export declare function serveAgUiHttp(runtime: AgentHarnessRuntime, options?: import("./protocol/ag-ui/http.js").AgUiHttpServerOptions): Promise<import("./protocol/ag-ui/http.js").AgUiHttpServer>;
100
102
  export declare function listAgentSkills(runtime: AgentHarnessRuntime, agentId: string, options?: RequirementAssessmentOptions): InventorySkillRecord[];
101
103
  export declare function getAgent(runtime: AgentHarnessRuntime, agentId: string, options?: RequirementAssessmentOptions): InventoryAgentRecord | null;
102
104
  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 { serveAgUiOverHttp } from "./protocol/ag-ui/http.js";
2
3
  import { serveAcpOverHttp } from "./protocol/acp/http.js";
3
4
  import { serveAcpOverStdio } from "./protocol/acp/stdio.js";
4
5
  import { normalizeMessageContent } from "./utils/message-content.js";
@@ -212,6 +213,9 @@ export function serveAcpStdio(runtime, options) {
212
213
  export async function serveAcpHttp(runtime, options) {
213
214
  return serveAcpOverHttp(runtime, options);
214
215
  }
216
+ export async function serveAgUiHttp(runtime, options) {
217
+ return serveAgUiOverHttp(runtime, options);
218
+ }
215
219
  export function listAgentSkills(runtime, agentId, options) {
216
220
  return runtime.listAgentSkills(agentId, options);
217
221
  }
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 { 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";
5
6
  type CliIo = {
@@ -9,6 +10,7 @@ type CliIo = {
9
10
  };
10
11
  type CliDeps = {
11
12
  createAgentHarness?: typeof createAgentHarness;
13
+ serveAgUiOverHttp?: typeof serveAgUiOverHttp;
12
14
  serveAcpOverHttp?: typeof serveAcpOverHttp;
13
15
  serveAcpOverStdio?: typeof serveAcpOverStdio;
14
16
  };
package/dist/cli.js CHANGED
@@ -3,12 +3,14 @@ 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 { serveAgUiOverHttp } from "./protocol/ag-ui/http.js";
6
7
  import { serveAcpOverHttp } from "./protocol/acp/http.js";
7
8
  import { serveAcpOverStdio } from "./protocol/acp/stdio.js";
8
9
  function renderUsage() {
9
10
  return `Usage:
10
11
  agent-harness init <project-name> [--template deep-research|single-agent] [--provider <provider>] [--model <model>] [--with-web-search|--no-web-search]
11
12
  agent-harness acp serve [--workspace <path>] [--transport stdio|http] [--host <hostname>] [--port <port>]
13
+ agent-harness ag-ui serve [--workspace <path>] [--host <hostname>] [--port <port>]
12
14
  `;
13
15
  }
14
16
  function isTemplate(value) {
@@ -104,12 +106,54 @@ function parseAcpServeOptions(args) {
104
106
  }
105
107
  return { workspaceRoot, transport, hostname, port };
106
108
  }
109
+ function parseHttpServeOptions(args) {
110
+ let workspaceRoot;
111
+ let hostname;
112
+ let port;
113
+ for (let index = 0; index < args.length; index += 1) {
114
+ const arg = args[index];
115
+ if (arg === "--workspace") {
116
+ const value = args[index + 1];
117
+ if (!value) {
118
+ return { hostname, port, error: "Missing value for --workspace" };
119
+ }
120
+ workspaceRoot = value;
121
+ index += 1;
122
+ continue;
123
+ }
124
+ if (arg === "--host") {
125
+ const value = args[index + 1];
126
+ if (!value) {
127
+ return { workspaceRoot, port, error: "Missing value for --host" };
128
+ }
129
+ hostname = value;
130
+ index += 1;
131
+ continue;
132
+ }
133
+ if (arg === "--port") {
134
+ const value = args[index + 1];
135
+ if (!value) {
136
+ return { workspaceRoot, hostname, error: "Missing value for --port" };
137
+ }
138
+ const parsedPort = Number.parseInt(value, 10);
139
+ if (!Number.isFinite(parsedPort) || parsedPort < 0) {
140
+ return { workspaceRoot, hostname, error: `Invalid AG-UI port: ${value}` };
141
+ }
142
+ port = parsedPort;
143
+ index += 1;
144
+ continue;
145
+ }
146
+ return { workspaceRoot, hostname, port, error: `Unknown option: ${arg}` };
147
+ }
148
+ return { workspaceRoot, hostname, port };
149
+ }
107
150
  export async function runCli(argv, io = {}, deps = {}) {
108
151
  const cwd = io.cwd ?? process.cwd();
109
152
  const stdout = io.stdout ?? ((message) => process.stdout.write(message));
110
153
  const stderr = io.stderr ?? ((message) => process.stderr.write(message));
111
154
  const [command, projectName, ...rest] = argv;
112
155
  const createHarness = deps.createAgentHarness ?? createAgentHarness;
156
+ const serveAgUi = deps.serveAgUiOverHttp ?? serveAgUiOverHttp;
113
157
  const serveAcpHttp = deps.serveAcpOverHttp ?? serveAcpOverHttp;
114
158
  const serveAcp = deps.serveAcpOverStdio ?? serveAcpOverStdio;
115
159
  if (command === "init") {
@@ -179,6 +223,36 @@ export async function runCli(argv, io = {}, deps = {}) {
179
223
  return 1;
180
224
  }
181
225
  }
226
+ if (command === "ag-ui") {
227
+ const [subcommand, ...subcommandArgs] = [projectName, ...rest];
228
+ if (subcommand !== "serve") {
229
+ stderr(renderUsage());
230
+ return 1;
231
+ }
232
+ const parsed = parseHttpServeOptions(subcommandArgs);
233
+ if (parsed.error) {
234
+ stderr(`${parsed.error}\n`);
235
+ stderr(renderUsage());
236
+ return 1;
237
+ }
238
+ try {
239
+ const runtime = await createHarness(path.resolve(cwd, parsed.workspaceRoot ?? "."));
240
+ const workspacePath = path.resolve(cwd, parsed.workspaceRoot ?? ".");
241
+ const server = await serveAgUi(runtime, {
242
+ hostname: parsed.hostname,
243
+ port: parsed.port,
244
+ });
245
+ stderr(`Serving AG-UI over http from ${workspacePath} at ${server.runUrl}\n`);
246
+ await server.completed;
247
+ await runtime.stop();
248
+ return 0;
249
+ }
250
+ catch (error) {
251
+ const message = error instanceof Error ? error.message : String(error);
252
+ stderr(`${message}\n`);
253
+ return 1;
254
+ }
255
+ }
182
256
  stderr(renderUsage());
183
257
  return 1;
184
258
  }
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- export { AgentHarnessAcpServer, AgentHarnessRuntime, buildFlowGraph, cancelRun, createAgentHarness, createAcpServer, 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, serveToolsOverStdio, subscribe, stop, updateMemory, exportFlowGraphToMermaid, exportFlowGraphToSequenceMermaid, } from "./api.js";
1
+ export { AgentHarnessAcpServer, AgentHarnessRuntime, buildFlowGraph, cancelRun, createAgentHarness, createAcpServer, 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, 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 } from "./api.js";
5
+ export type { AcpHttpServer, AcpHttpServerOptions, AcpStdioServer, AcpStdioServerOptions, AgUiEvent, AgUiHttpServer, AgUiHttpServerOptions, AgUiRunAgentInput, } from "./api.js";
6
6
  export type { 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, 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, serveToolsOverStdio, subscribe, stop, updateMemory, exportFlowGraphToMermaid, exportFlowGraphToSequenceMermaid, } from "./api.js";
1
+ export { AgentHarnessAcpServer, AgentHarnessRuntime, buildFlowGraph, cancelRun, createAgentHarness, createAcpServer, 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, 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.163";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.165";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.163";
1
+ export const AGENT_HARNESS_VERSION = "0.0.165";
@@ -0,0 +1,56 @@
1
+ import type { AgentHarnessRuntime } from "../../runtime/harness.js";
2
+ import type { MessageContent } from "../../contracts/types.js";
3
+ export type AgUiHttpServerOptions = {
4
+ hostname?: string;
5
+ port?: number;
6
+ runPath?: string;
7
+ };
8
+ export type AgUiRunAgentInput = {
9
+ input: MessageContent;
10
+ agentId?: string;
11
+ sessionId?: string;
12
+ invocation?: Record<string, unknown>;
13
+ };
14
+ type AgUiBaseEvent = {
15
+ type: string;
16
+ timestamp: string;
17
+ };
18
+ export type AgUiEvent = (AgUiBaseEvent & {
19
+ type: "RUN_STARTED";
20
+ threadId: string;
21
+ runId: string;
22
+ input?: MessageContent;
23
+ }) | (AgUiBaseEvent & {
24
+ type: "TEXT_MESSAGE_START";
25
+ messageId: string;
26
+ role: "assistant";
27
+ }) | (AgUiBaseEvent & {
28
+ type: "TEXT_MESSAGE_CONTENT";
29
+ messageId: string;
30
+ delta: string;
31
+ }) | (AgUiBaseEvent & {
32
+ type: "TEXT_MESSAGE_END";
33
+ messageId: string;
34
+ }) | (AgUiBaseEvent & {
35
+ type: "RUN_FINISHED";
36
+ threadId: string;
37
+ runId: string;
38
+ state?: string;
39
+ approvalId?: string;
40
+ pendingActionId?: string;
41
+ }) | (AgUiBaseEvent & {
42
+ type: "RUN_ERROR";
43
+ message: string;
44
+ runId?: string;
45
+ threadId?: string;
46
+ });
47
+ export type AgUiHttpServer = {
48
+ hostname: string;
49
+ port: number;
50
+ runPath: string;
51
+ runUrl: string;
52
+ completed: Promise<void>;
53
+ close: () => Promise<void>;
54
+ };
55
+ export declare function serveAgUiOverHttp(runtime: AgentHarnessRuntime, options?: AgUiHttpServerOptions): Promise<AgUiHttpServer>;
56
+ export {};
@@ -0,0 +1,194 @@
1
+ import { createServer } from "node:http";
2
+ import { createPersistentId } from "../../utils/id.js";
3
+ function normalizePath(value, fallback) {
4
+ const source = typeof value === "string" && value.trim().length > 0 ? value.trim() : fallback;
5
+ return source.startsWith("/") ? source : `/${source}`;
6
+ }
7
+ function readRequestBody(request) {
8
+ return new Promise((resolve, reject) => {
9
+ const chunks = [];
10
+ request.on("data", (chunk) => {
11
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
12
+ });
13
+ request.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
14
+ request.on("error", reject);
15
+ });
16
+ }
17
+ function writeSseEvent(response, payload) {
18
+ return new Promise((resolve, reject) => {
19
+ response.write(`data: ${JSON.stringify(payload)}\n\n`, (error) => {
20
+ if (error) {
21
+ reject(error);
22
+ return;
23
+ }
24
+ resolve();
25
+ });
26
+ });
27
+ }
28
+ function parseRunInput(payload) {
29
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
30
+ throw new Error("AG-UI run input must be an object.");
31
+ }
32
+ const typed = payload;
33
+ if (typeof typed.input !== "string"
34
+ && !(Array.isArray(typed.input))) {
35
+ throw new Error("AG-UI run input requires `input`.");
36
+ }
37
+ return {
38
+ input: typed.input,
39
+ ...(typeof typed.agentId === "string" && typed.agentId.trim().length > 0 ? { agentId: typed.agentId.trim() } : {}),
40
+ ...(typeof typed.sessionId === "string" && typed.sessionId.trim().length > 0 ? { sessionId: typed.sessionId.trim() } : {}),
41
+ ...(typed.invocation && typeof typed.invocation === "object" && !Array.isArray(typed.invocation)
42
+ ? { invocation: typed.invocation }
43
+ : {}),
44
+ };
45
+ }
46
+ function createTimestamp() {
47
+ return new Date().toISOString();
48
+ }
49
+ function toRunStarted(event, input) {
50
+ return {
51
+ type: "RUN_STARTED",
52
+ timestamp: createTimestamp(),
53
+ threadId: event.threadId,
54
+ runId: event.runId,
55
+ input,
56
+ };
57
+ }
58
+ export async function serveAgUiOverHttp(runtime, options = {}) {
59
+ const hostname = options.hostname?.trim() || "127.0.0.1";
60
+ const port = typeof options.port === "number" && Number.isFinite(options.port) ? options.port : 0;
61
+ const runPath = normalizePath(options.runPath, "/run");
62
+ const httpServer = createServer(async (request, response) => {
63
+ const url = new URL(request.url ?? "/", `http://${hostname}`);
64
+ if (request.method !== "POST" || url.pathname !== runPath) {
65
+ response.statusCode = 404;
66
+ response.setHeader("content-type", "application/json; charset=utf-8");
67
+ response.end(JSON.stringify({ error: "Not Found", runPath }));
68
+ return;
69
+ }
70
+ response.statusCode = 200;
71
+ response.setHeader("content-type", "text/event-stream; charset=utf-8");
72
+ response.setHeader("cache-control", "no-cache, no-transform");
73
+ response.setHeader("connection", "keep-alive");
74
+ let runId;
75
+ let threadId;
76
+ let messageId;
77
+ let textMessageStarted = false;
78
+ const ensureTextStart = async () => {
79
+ if (textMessageStarted) {
80
+ return;
81
+ }
82
+ messageId = messageId ?? `msg-${createPersistentId()}`;
83
+ textMessageStarted = true;
84
+ await writeSseEvent(response, {
85
+ type: "TEXT_MESSAGE_START",
86
+ timestamp: createTimestamp(),
87
+ messageId,
88
+ role: "assistant",
89
+ });
90
+ };
91
+ try {
92
+ const body = await readRequestBody(request);
93
+ const input = parseRunInput(JSON.parse(body));
94
+ const result = await runtime.run({
95
+ agentId: input.agentId,
96
+ input: input.input,
97
+ ...(input.sessionId ? { threadId: input.sessionId } : {}),
98
+ ...(input.invocation ? { invocation: input.invocation } : {}),
99
+ listeners: {
100
+ onEvent: async (event) => {
101
+ if (event.eventType === "run.created") {
102
+ runId = event.runId;
103
+ threadId = event.threadId;
104
+ await writeSseEvent(response, toRunStarted(event, input.input));
105
+ return;
106
+ }
107
+ if (event.eventType === "output.delta") {
108
+ runId = runId ?? event.runId;
109
+ threadId = threadId ?? event.threadId;
110
+ const delta = typeof event.payload.content === "string" ? event.payload.content : "";
111
+ if (!delta) {
112
+ return;
113
+ }
114
+ await ensureTextStart();
115
+ await writeSseEvent(response, {
116
+ type: "TEXT_MESSAGE_CONTENT",
117
+ timestamp: createTimestamp(),
118
+ messageId: messageId,
119
+ delta,
120
+ });
121
+ }
122
+ },
123
+ },
124
+ });
125
+ runId = runId ?? result.runId;
126
+ threadId = threadId ?? result.threadId;
127
+ if (!textMessageStarted && result.output) {
128
+ await ensureTextStart();
129
+ await writeSseEvent(response, {
130
+ type: "TEXT_MESSAGE_CONTENT",
131
+ timestamp: createTimestamp(),
132
+ messageId: messageId,
133
+ delta: result.output,
134
+ });
135
+ }
136
+ if (textMessageStarted) {
137
+ await writeSseEvent(response, {
138
+ type: "TEXT_MESSAGE_END",
139
+ timestamp: createTimestamp(),
140
+ messageId: messageId,
141
+ });
142
+ }
143
+ await writeSseEvent(response, {
144
+ type: "RUN_FINISHED",
145
+ timestamp: createTimestamp(),
146
+ threadId: threadId,
147
+ runId: runId,
148
+ state: result.state,
149
+ ...(result.approvalId ? { approvalId: result.approvalId } : {}),
150
+ ...(result.pendingActionId ? { pendingActionId: result.pendingActionId } : {}),
151
+ });
152
+ }
153
+ catch (error) {
154
+ await writeSseEvent(response, {
155
+ type: "RUN_ERROR",
156
+ timestamp: createTimestamp(),
157
+ ...(threadId ? { threadId } : {}),
158
+ ...(runId ? { runId } : {}),
159
+ message: error instanceof Error ? error.message : "AG-UI run failed.",
160
+ });
161
+ }
162
+ finally {
163
+ response.end();
164
+ }
165
+ });
166
+ const completed = new Promise((resolve, reject) => {
167
+ httpServer.once("close", resolve);
168
+ httpServer.once("error", reject);
169
+ });
170
+ await new Promise((resolve, reject) => {
171
+ httpServer.listen(port, hostname, () => resolve());
172
+ httpServer.once("error", reject);
173
+ });
174
+ const address = httpServer.address();
175
+ const resolvedPort = typeof address === "object" && address ? address.port : port;
176
+ return {
177
+ hostname,
178
+ port: resolvedPort,
179
+ runPath,
180
+ runUrl: `http://${hostname}:${resolvedPort}${runPath}`,
181
+ completed,
182
+ close: async () => {
183
+ await new Promise((resolve, reject) => {
184
+ httpServer.close((error) => {
185
+ if (error) {
186
+ reject(error);
187
+ return;
188
+ }
189
+ resolve();
190
+ });
191
+ });
192
+ },
193
+ };
194
+ }
@@ -49,7 +49,7 @@ export async function runLocalToolInvocationLoop({ binding, request, primaryTool
49
49
  throw new Error(`Tool ${toolCall.name} is not configured for this agent.`);
50
50
  }
51
51
  const compiledTool = toolCatalog.get(toolCall.name) ?? toolCatalog.get(resolvedToolName);
52
- const normalizedArgs = normalizeToolArgsForSchema(toolCall.args, activeExecutable.schema);
52
+ const normalizedArgs = normalizeToolArgsForSchema(toolCall.args, activeExecutable.schema, toolCall.rawArgsInput);
53
53
  const toolResult = await activeExecutable.invoke(normalizedArgs);
54
54
  const memoryCandidates = compiledTool ? extractMemoryCandidatesFromToolOutput(compiledTool, toolResult) : [];
55
55
  executedToolResults.push({
@@ -1,7 +1,8 @@
1
1
  export declare function stringifyToolOutput(output: unknown): string;
2
- export declare function normalizeToolArgsForSchema(args: Record<string, unknown>, schema: unknown): Record<string, unknown>;
2
+ export declare function normalizeToolArgsForSchema(args: Record<string, unknown>, schema: unknown, rawArgsInput?: unknown): Record<string, unknown>;
3
3
  export declare function extractToolCallsFromResult(result: unknown): Array<{
4
4
  id?: string;
5
5
  name: string;
6
6
  args: Record<string, unknown>;
7
+ rawArgsInput?: unknown;
7
8
  }>;
@@ -14,7 +14,29 @@ export function stringifyToolOutput(output) {
14
14
  return `${String(output)}`;
15
15
  }
16
16
  }
17
- export function normalizeToolArgsForSchema(args, schema) {
17
+ function mapSingleFieldScalarArg(args, expectedKey, rawArgsInput) {
18
+ if (expectedKey in args) {
19
+ return args;
20
+ }
21
+ if (typeof rawArgsInput === "string") {
22
+ const trimmed = rawArgsInput.trim();
23
+ if (trimmed.length === 0) {
24
+ return args;
25
+ }
26
+ return {
27
+ ...args,
28
+ [expectedKey]: trimmed,
29
+ };
30
+ }
31
+ if (typeof rawArgsInput === "number" || typeof rawArgsInput === "boolean") {
32
+ return {
33
+ ...args,
34
+ [expectedKey]: rawArgsInput,
35
+ };
36
+ }
37
+ return args;
38
+ }
39
+ export function normalizeToolArgsForSchema(args, schema, rawArgsInput) {
18
40
  const schemaDef = isObject(schema) ? schema._def : undefined;
19
41
  const shape = schemaDef
20
42
  ? isRecord(schemaDef.shape)
@@ -34,19 +56,23 @@ export function normalizeToolArgsForSchema(args, schema) {
34
56
  if (expectedKey in args) {
35
57
  return args;
36
58
  }
59
+ const scalarMappedArgs = mapSingleFieldScalarArg(args, expectedKey, rawArgsInput);
60
+ if (expectedKey in scalarMappedArgs) {
61
+ return scalarMappedArgs;
62
+ }
37
63
  const aliasesByExpected = {
38
64
  city: ["location", "locality", "place"],
39
65
  location: ["city", "city_name"],
40
66
  query: ["question", "prompt", "text", "request"],
41
67
  };
42
68
  const aliases = aliasesByExpected[expectedKey] ?? [];
43
- const aliasKey = aliases.find((candidate) => candidate in args);
44
- if (!aliasKey || !(aliasKey in args)) {
45
- return args;
69
+ const aliasKey = aliases.find((candidate) => candidate in scalarMappedArgs);
70
+ if (!aliasKey || !(aliasKey in scalarMappedArgs)) {
71
+ return scalarMappedArgs;
46
72
  }
47
73
  return {
48
- ...args,
49
- [expectedKey]: args[aliasKey],
74
+ ...scalarMappedArgs,
75
+ [expectedKey]: scalarMappedArgs[aliasKey],
50
76
  };
51
77
  }
52
78
  export function extractToolCallsFromResult(result) {
@@ -75,12 +101,13 @@ export function extractToolCallsFromResult(result) {
75
101
  if (!name) {
76
102
  return null;
77
103
  }
78
- const rawArgs = salvageToolArgs(toolCall.args ?? functionPayload?.arguments) ?? {};
104
+ const rawArgsInput = toolCall.args ?? functionPayload?.arguments;
105
+ const rawArgs = salvageToolArgs(rawArgsInput) ?? {};
79
106
  if (!isObject(rawArgs)) {
80
107
  return null;
81
108
  }
82
109
  const id = typeof toolCall.id === "string" ? toolCall.id : undefined;
83
- return { id, name, args: rawArgs };
110
+ return { id, name, args: rawArgs, rawArgsInput };
84
111
  })
85
112
  .filter((item) => item !== null);
86
113
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.164",
3
+ "version": "0.0.166",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",