@botbotgo/agent-harness 0.0.186 → 0.0.187

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
@@ -954,7 +954,7 @@ ACP transport notes:
954
954
  - `serveAcpStdio(runtime)` exposes newline-delimited JSON-RPC over stdio for local IDE, CLI, or subprocess clients.
955
955
  - `serveAcpHttp(runtime)` exposes JSON-RPC over HTTP plus SSE runtime events so remote operator surfaces can connect without importing the runtime in-process.
956
956
  - `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.
957
- - `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.
957
+ - `serveAgUiHttp(runtime)` exposes an AG-UI-compatible HTTP SSE bridge that projects runtime lifecycle, text output, upstream thinking, step progress, and tool calls onto `RUN_*`, `TEXT_MESSAGE_*`, `THINKING_TEXT_MESSAGE_*`, `STEP_*`, and `TOOL_CALL_*` events for UI clients.
958
958
  - `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.
959
959
  - `listRequestEvents(...)` and `exportRequestPackage(...)` are the request-first inspection helpers.
960
960
  - `exportRequestPackage(...)` and `exportSessionPackage(...)` package stable runtime records, transcript, approvals, events, and artifacts for operator tooling without reaching into persistence internals.
package/README.zh.md CHANGED
@@ -913,7 +913,7 @@ ACP transport 说明:
913
913
  - `serveAcpStdio(runtime)` 提供基于 stdio 的 newline-delimited JSON-RPC,适合本地 IDE、CLI 或子进程客户端。
914
914
  - `serveAcpHttp(runtime)` 提供基于 HTTP 的 JSON-RPC 与 SSE runtime events,适合远程 operator surface 或独立控制面接入。
915
915
  - `serveA2aHttp(runtime)` 提供最小可用的 A2A HTTP JSON-RPC bridge 与 agent card discovery,把 `message/send`、`tasks/get`、`tasks/cancel` 映射到现有 session/request runtime surface。
916
- - `serveAgUiHttp(runtime)` 提供最小可用的 AG-UI HTTP SSE bridge,把现有 `run + output.delta + final result` 投影成 `RUN_STARTED`、`TEXT_MESSAGE_*` 与 `RUN_FINISHED` 事件,便于 UI 客户端直接接入。
916
+ - `serveAgUiHttp(runtime)` 提供 AG-UI HTTP SSE bridge,把 runtime 生命周期、文本输出、upstream thinking、step 进度与 tool call 投影成 `RUN_*`、`TEXT_MESSAGE_*`、`THINKING_TEXT_MESSAGE_*`、`STEP_*` 与 `TOOL_CALL_*` 事件,便于 UI 客户端直接接入。
917
917
  - `createRuntimeMcpServer(runtime)` 与 `serveRuntimeMcpOverStdio(runtime)` 会把持久化 runtime 控制面本身暴露成 MCP tools,包括 sessions、requests、approvals、artifacts、events 与 package export helpers。
918
918
  - `listRequestEvents(...)` 与 `exportRequestPackage(...)` 是 request-first 的检查 helper。
919
919
  - `exportRequestPackage(...)` 与 `exportSessionPackage(...)` 可把稳定 runtime 记录、transcript、approvals、events 和 artifacts 打包给 operator tooling,而不必直接访问 persistence 内部实现。
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.185";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.186";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.185";
1
+ export const AGENT_HARNESS_VERSION = "0.0.186";
@@ -20,6 +20,28 @@ export type AgUiEvent = (AgUiBaseEvent & {
20
20
  threadId: string;
21
21
  runId: string;
22
22
  input?: MessageContent;
23
+ }) | (AgUiBaseEvent & {
24
+ type: "STEP_STARTED";
25
+ stepId: string;
26
+ title: string;
27
+ category: "llm" | "tool" | "skill" | "memory" | "chain" | "approval";
28
+ }) | (AgUiBaseEvent & {
29
+ type: "STEP_FINISHED";
30
+ stepId: string;
31
+ title: string;
32
+ category: "llm" | "tool" | "skill" | "memory" | "chain" | "approval";
33
+ status: "completed" | "failed";
34
+ }) | (AgUiBaseEvent & {
35
+ type: "THINKING_TEXT_MESSAGE_START";
36
+ messageId: string;
37
+ role: "assistant";
38
+ }) | (AgUiBaseEvent & {
39
+ type: "THINKING_TEXT_MESSAGE_CONTENT";
40
+ messageId: string;
41
+ delta: string;
42
+ }) | (AgUiBaseEvent & {
43
+ type: "THINKING_TEXT_MESSAGE_END";
44
+ messageId: string;
23
45
  }) | (AgUiBaseEvent & {
24
46
  type: "TEXT_MESSAGE_START";
25
47
  messageId: string;
@@ -31,6 +53,25 @@ export type AgUiEvent = (AgUiBaseEvent & {
31
53
  }) | (AgUiBaseEvent & {
32
54
  type: "TEXT_MESSAGE_END";
33
55
  messageId: string;
56
+ }) | (AgUiBaseEvent & {
57
+ type: "TOOL_CALL_START";
58
+ toolCallId: string;
59
+ toolName: string;
60
+ }) | (AgUiBaseEvent & {
61
+ type: "TOOL_CALL_ARGS";
62
+ toolCallId: string;
63
+ toolName: string;
64
+ args: unknown;
65
+ }) | (AgUiBaseEvent & {
66
+ type: "TOOL_CALL_RESULT";
67
+ toolCallId: string;
68
+ toolName: string;
69
+ result: unknown;
70
+ isError?: boolean;
71
+ }) | (AgUiBaseEvent & {
72
+ type: "TOOL_CALL_END";
73
+ toolCallId: string;
74
+ toolName: string;
34
75
  }) | (AgUiBaseEvent & {
35
76
  type: "RUN_FINISHED";
36
77
  threadId: string;
@@ -1,5 +1,6 @@
1
1
  import { createServer } from "node:http";
2
2
  import { createPersistentId } from "../../utils/id.js";
3
+ import { createUpstreamTimelineReducer } from "../../upstream-events.js";
3
4
  function normalizePath(value, fallback) {
4
5
  const source = typeof value === "string" && value.trim().length > 0 ? value.trim() : fallback;
5
6
  return source.startsWith("/") ? source : `/${source}`;
@@ -55,6 +56,63 @@ function toRunStarted(event, input) {
55
56
  input,
56
57
  };
57
58
  }
59
+ function asObject(value) {
60
+ return typeof value === "object" && value !== null ? value : null;
61
+ }
62
+ function readUpstreamEventName(event) {
63
+ return typeof asObject(event)?.event === "string" ? String(asObject(event)?.event) : "";
64
+ }
65
+ function readUpstreamToolName(event) {
66
+ return typeof asObject(event)?.name === "string" ? String(asObject(event)?.name) : "";
67
+ }
68
+ function readUpstreamRunType(event) {
69
+ return typeof asObject(event)?.run_type === "string" ? String(asObject(event)?.run_type) : "";
70
+ }
71
+ function isToolStartEvent(event) {
72
+ const eventName = readUpstreamEventName(event);
73
+ return eventName === "on_tool_start" || (eventName === "on_chain_start" && readUpstreamRunType(event) === "tool");
74
+ }
75
+ function isToolTerminalEvent(event) {
76
+ const eventName = readUpstreamEventName(event);
77
+ return eventName === "on_tool_end"
78
+ || eventName === "on_tool_error"
79
+ || ((eventName === "on_chain_end" || eventName === "on_chain_error") && readUpstreamRunType(event) === "tool");
80
+ }
81
+ function readToolArgs(event) {
82
+ const typed = asObject(event);
83
+ const data = asObject(typed?.data);
84
+ if (!data || !("input" in data)) {
85
+ return undefined;
86
+ }
87
+ return data.input;
88
+ }
89
+ function createToolCallState() {
90
+ const activeToolCallIds = new Map();
91
+ return {
92
+ start(toolName) {
93
+ const toolCallId = `tool-${createPersistentId()}`;
94
+ const activeIds = activeToolCallIds.get(toolName) ?? [];
95
+ activeIds.push(toolCallId);
96
+ activeToolCallIds.set(toolName, activeIds);
97
+ return toolCallId;
98
+ },
99
+ peek(toolName) {
100
+ const activeIds = activeToolCallIds.get(toolName);
101
+ return activeIds?.at(-1);
102
+ },
103
+ finish(toolName) {
104
+ const activeIds = activeToolCallIds.get(toolName);
105
+ if (!activeIds || activeIds.length === 0) {
106
+ return undefined;
107
+ }
108
+ const toolCallId = activeIds.pop();
109
+ if (!activeIds.length) {
110
+ activeToolCallIds.delete(toolName);
111
+ }
112
+ return toolCallId;
113
+ },
114
+ };
115
+ }
58
116
  export async function serveAgUiOverHttp(runtime, options = {}) {
59
117
  const hostname = options.hostname?.trim() || "127.0.0.1";
60
118
  const port = typeof options.port === "number" && Number.isFinite(options.port) ? options.port : 0;
@@ -74,7 +132,11 @@ export async function serveAgUiOverHttp(runtime, options = {}) {
74
132
  let runId;
75
133
  let threadId;
76
134
  let messageId;
135
+ let thinkingMessageId;
77
136
  let textMessageStarted = false;
137
+ let thinkingMessageStarted = false;
138
+ const toolCalls = createToolCallState();
139
+ const upstreamReducer = createUpstreamTimelineReducer();
78
140
  const ensureTextStart = async () => {
79
141
  if (textMessageStarted) {
80
142
  return;
@@ -88,6 +150,37 @@ export async function serveAgUiOverHttp(runtime, options = {}) {
88
150
  role: "assistant",
89
151
  });
90
152
  };
153
+ const ensureThinkingTextStart = async () => {
154
+ if (thinkingMessageStarted) {
155
+ return;
156
+ }
157
+ thinkingMessageId = thinkingMessageId ?? `thinking-${createPersistentId()}`;
158
+ thinkingMessageStarted = true;
159
+ await writeSseEvent(response, {
160
+ type: "THINKING_TEXT_MESSAGE_START",
161
+ timestamp: createTimestamp(),
162
+ messageId: thinkingMessageId,
163
+ role: "assistant",
164
+ });
165
+ };
166
+ const closeOpenMessages = async () => {
167
+ if (thinkingMessageStarted) {
168
+ await writeSseEvent(response, {
169
+ type: "THINKING_TEXT_MESSAGE_END",
170
+ timestamp: createTimestamp(),
171
+ messageId: thinkingMessageId,
172
+ });
173
+ thinkingMessageStarted = false;
174
+ }
175
+ if (textMessageStarted) {
176
+ await writeSseEvent(response, {
177
+ type: "TEXT_MESSAGE_END",
178
+ timestamp: createTimestamp(),
179
+ messageId: messageId,
180
+ });
181
+ textMessageStarted = false;
182
+ }
183
+ };
91
184
  try {
92
185
  const body = await readRequestBody(request);
93
186
  const input = parseRunInput(JSON.parse(body));
@@ -120,6 +213,92 @@ export async function serveAgUiOverHttp(runtime, options = {}) {
120
213
  });
121
214
  }
122
215
  },
216
+ onUpstreamEvent: async (event) => {
217
+ const eventName = readUpstreamEventName(event);
218
+ const toolName = readUpstreamToolName(event);
219
+ if (isToolStartEvent(event) && toolName) {
220
+ const toolCallId = toolCalls.start(toolName);
221
+ await writeSseEvent(response, {
222
+ type: "TOOL_CALL_START",
223
+ timestamp: createTimestamp(),
224
+ toolCallId,
225
+ toolName,
226
+ });
227
+ const args = readToolArgs(event);
228
+ if (args !== undefined) {
229
+ await writeSseEvent(response, {
230
+ type: "TOOL_CALL_ARGS",
231
+ timestamp: createTimestamp(),
232
+ toolCallId,
233
+ toolName,
234
+ args,
235
+ });
236
+ }
237
+ }
238
+ const projections = upstreamReducer.consume(event);
239
+ for (const projection of projections) {
240
+ if (projection.type === "thinking") {
241
+ if (!projection.text) {
242
+ continue;
243
+ }
244
+ await ensureThinkingTextStart();
245
+ await writeSseEvent(response, {
246
+ type: "THINKING_TEXT_MESSAGE_CONTENT",
247
+ timestamp: createTimestamp(),
248
+ messageId: thinkingMessageId,
249
+ delta: projection.text,
250
+ });
251
+ continue;
252
+ }
253
+ if (projection.type === "step") {
254
+ if (projection.status === "started") {
255
+ await writeSseEvent(response, {
256
+ type: "STEP_STARTED",
257
+ timestamp: createTimestamp(),
258
+ stepId: projection.key,
259
+ title: projection.step,
260
+ category: projection.category,
261
+ });
262
+ continue;
263
+ }
264
+ await writeSseEvent(response, {
265
+ type: "STEP_FINISHED",
266
+ timestamp: createTimestamp(),
267
+ stepId: projection.key,
268
+ title: projection.step,
269
+ category: projection.category,
270
+ status: projection.status,
271
+ });
272
+ continue;
273
+ }
274
+ const toolCallId = toolCalls.peek(projection.toolName) ?? toolCalls.start(projection.toolName);
275
+ await writeSseEvent(response, {
276
+ type: "TOOL_CALL_RESULT",
277
+ timestamp: createTimestamp(),
278
+ toolCallId,
279
+ toolName: projection.toolName,
280
+ result: projection.output,
281
+ ...(projection.isError !== undefined ? { isError: projection.isError } : {}),
282
+ });
283
+ }
284
+ if (isToolTerminalEvent(event) && toolName) {
285
+ const toolCallId = toolCalls.finish(toolName) ?? `tool-${createPersistentId()}`;
286
+ await writeSseEvent(response, {
287
+ type: "TOOL_CALL_END",
288
+ timestamp: createTimestamp(),
289
+ toolCallId,
290
+ toolName,
291
+ });
292
+ }
293
+ if (eventName === "on_chat_model_end" && thinkingMessageStarted) {
294
+ await writeSseEvent(response, {
295
+ type: "THINKING_TEXT_MESSAGE_END",
296
+ timestamp: createTimestamp(),
297
+ messageId: thinkingMessageId,
298
+ });
299
+ thinkingMessageStarted = false;
300
+ }
301
+ },
123
302
  },
124
303
  });
125
304
  runId = runId ?? result.runId;
@@ -133,13 +312,7 @@ export async function serveAgUiOverHttp(runtime, options = {}) {
133
312
  delta: result.output,
134
313
  });
135
314
  }
136
- if (textMessageStarted) {
137
- await writeSseEvent(response, {
138
- type: "TEXT_MESSAGE_END",
139
- timestamp: createTimestamp(),
140
- messageId: messageId,
141
- });
142
- }
315
+ await closeOpenMessages();
143
316
  await writeSseEvent(response, {
144
317
  type: "RUN_FINISHED",
145
318
  timestamp: createTimestamp(),
@@ -151,6 +324,7 @@ export async function serveAgUiOverHttp(runtime, options = {}) {
151
324
  });
152
325
  }
153
326
  catch (error) {
327
+ await closeOpenMessages();
154
328
  await writeSseEvent(response, {
155
329
  type: "RUN_ERROR",
156
330
  timestamp: createTimestamp(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.186",
3
+ "version": "0.0.187",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",