@botbotgo/agent-harness 0.0.222 → 0.0.224
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 +1 -1
- package/README.zh.md +1 -1
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/protocol/a2a/http.js +255 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1024,7 +1024,7 @@ ACP transport notes:
|
|
|
1024
1024
|
- `serveAcpHttp(runtime)` exposes JSON-RPC over HTTP plus SSE runtime events so remote operator surfaces can connect without importing the runtime in-process.
|
|
1025
1025
|
- ACP transport validation now covers the reference-client core flow: capability discovery, request submit, session lookup, request lookup, invalid-JSON handling, notification calls without response ids, stdio JSON-RPC, and HTTP plus SSE runtime notifications.
|
|
1026
1026
|
- For the thinnest editor or CLI starter, begin with `agent-harness acp serve --workspace . --transport stdio` and mirror the `examples/protocol-hello-world/app/acp-stdio-hello-world.mjs` wire shape. Applications that want an in-process reference client can use `createAcpStdioClient(...)` to issue JSON-RPC requests and route runtime notifications without hand-rolling line parsing.
|
|
1027
|
-
- `serveA2aHttp(runtime)` exposes an A2A-compatible HTTP JSON-RPC bridge plus agent card discovery, mapping both existing methods such as `message/send` and A2A v1.0 PascalCase methods such as `SendMessage`, `SendStreamingMessage`, `GetTask`, `ListTasks`, `CancelTask`, `SubscribeToTask`,
|
|
1027
|
+
- `serveA2aHttp(runtime)` exposes an A2A-compatible HTTP JSON-RPC bridge plus agent card discovery, mapping both existing methods such as `message/send` and A2A v1.0 PascalCase methods such as `SendMessage`, `SendStreamingMessage`, `GetTask`, `ListTasks`, `CancelTask`, `SubscribeToTask`, `GetExtendedAgentCard`, and task push-notification config methods onto the existing session/request runtime surface. The bridge now advertises both `1.0` and `0.3` JSON-RPC interfaces, sets `capabilities.streaming = true` plus `capabilities.pushNotifications = true`, validates `A2A-Version`, records `A2A-Extensions` into runtime invocation metadata, publishes `TASK_STATE_*` statuses plus the `{ task }` `SendMessage` wrapper, streams task snapshots over SSE for streaming methods, and can send best-effort webhook task snapshots for configured push notification receivers.
|
|
1028
1028
|
- `serveAgUiHttp(runtime)` exposes an AG-UI-compatible HTTP SSE bridge that projects runtime lifecycle, text output, upstream thinking, step progress, and tool calls onto `RUN_*`, `TEXT_MESSAGE_*`, `THINKING_TEXT_MESSAGE_*`, `STEP_*`, and `TOOL_CALL_*` events for UI clients.
|
|
1029
1029
|
- `createRuntimeMcpServer(runtime)` and `serveRuntimeMcpOverStdio(runtime)` expose the persisted runtime control surface itself as MCP tools, including sessions, requests, approvals, artifacts, events, and package export helpers.
|
|
1030
1030
|
- `listRequestEvents(...)` and `exportRequestPackage(...)` are the request-first inspection helpers.
|
package/README.zh.md
CHANGED
|
@@ -982,7 +982,7 @@ ACP transport 说明:
|
|
|
982
982
|
- `serveAcpHttp(runtime)` 提供基于 HTTP 的 JSON-RPC 与 SSE runtime events,适合远程界面或独立控制面接入。
|
|
983
983
|
- ACP transport 现已覆盖核心参考客户端流程验证:capability discovery、request submit、session lookup、request lookup、invalid JSON 处理、无 id notification 不返回响应,以及 stdio JSON-RPC 与 HTTP + SSE runtime notifications。
|
|
984
984
|
- 如果要从最薄的一层 editor / CLI starter 开始,优先用 `agent-harness acp serve --workspace . --transport stdio`,并直接参考 `examples/protocol-hello-world/app/acp-stdio-hello-world.mjs` 的 wire shape。需要在应用内使用 reference client 时,可直接用 `createAcpStdioClient(...)` 发起 JSON-RPC 请求并分流 runtime notifications,避免每个 sidecar 自己重写 line parsing。
|
|
985
|
-
- `serveA2aHttp(runtime)` 提供 A2A HTTP JSON-RPC bridge 与 agent card discovery,同时兼容 `message/send` 这类旧方法,以及 `SendMessage`、`SendStreamingMessage`、`GetTask`、`ListTasks`、`CancelTask`、`SubscribeToTask`、`GetExtendedAgentCard` 这类 A2A v1.0
|
|
985
|
+
- `serveA2aHttp(runtime)` 提供 A2A HTTP JSON-RPC bridge 与 agent card discovery,同时兼容 `message/send` 这类旧方法,以及 `SendMessage`、`SendStreamingMessage`、`GetTask`、`ListTasks`、`CancelTask`、`SubscribeToTask`、`GetExtendedAgentCard` 与 task push-notification config 这类 A2A v1.0 方法,并统一映射到现有 session/request 运行记录。bridge 现在会同时声明 `1.0` 与 `0.3` 两个 JSON-RPC interface、把 `capabilities.streaming` 和 `capabilities.pushNotifications` 都设为 `true`、校验 `A2A-Version`、把 `A2A-Extensions` 记录进 runtime invocation metadata、发布 `TASK_STATE_*` 状态与 `SendMessage` 的 `{ task }` wrapper、在 streaming 方法上通过 SSE 输出 task snapshots,并可向已配置的 webhook receiver 发送 best-effort task push notifications。
|
|
986
986
|
- `serveAgUiHttp(runtime)` 提供 AG-UI HTTP SSE bridge,把 runtime 生命周期、文本输出、upstream thinking、step 进度与 tool call 投影成 `RUN_*`、`TEXT_MESSAGE_*`、`THINKING_TEXT_MESSAGE_*`、`STEP_*` 与 `TOOL_CALL_*` 事件,便于 UI 客户端直接接入。
|
|
987
987
|
- `createRuntimeMcpServer(runtime)` 与 `serveRuntimeMcpOverStdio(runtime)` 会把持久化 runtime 控制面本身暴露成 MCP tools,包括 sessions、requests、approvals、artifacts、events 与 package export helpers。
|
|
988
988
|
- `listRequestEvents(...)` 与 `exportRequestPackage(...)` 是 request-first 的检查 helper。
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export declare const AGENT_HARNESS_VERSION = "0.0.223";
|
package/dist/package-version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export const AGENT_HARNESS_VERSION = "0.0.223";
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
|
+
import { AGENT_HARNESS_VERSION } from "../../package-version.js";
|
|
3
|
+
import { createPersistentId } from "../../utils/id.js";
|
|
2
4
|
const SUPPORTED_A2A_VERSIONS = ["1.0", "0.3"];
|
|
3
5
|
function normalizePath(value, fallback) {
|
|
4
6
|
const source = typeof value === "string" && value.trim().length > 0 ? value.trim() : fallback;
|
|
@@ -133,11 +135,59 @@ function parseMessageSendParams(params) {
|
|
|
133
135
|
const invocation = metadata && typeof metadata === "object" && !Array.isArray(metadata)
|
|
134
136
|
? metadata
|
|
135
137
|
: undefined;
|
|
138
|
+
const configuration = typed.configuration;
|
|
139
|
+
const nestedPushNotificationConfig = configuration && typeof configuration === "object" && !Array.isArray(configuration)
|
|
140
|
+
? configuration.pushNotificationConfig
|
|
141
|
+
?? configuration.taskPushNotificationConfig
|
|
142
|
+
: undefined;
|
|
143
|
+
const pushNotificationConfig = parsePendingPushNotificationConfig(typed.pushNotificationConfig ?? nestedPushNotificationConfig);
|
|
136
144
|
return {
|
|
137
145
|
input: textParts.join("\n\n"),
|
|
138
146
|
...(agentId ? { agentId } : {}),
|
|
139
147
|
...(sessionId ? { sessionId } : {}),
|
|
140
148
|
...(invocation ? { invocation } : {}),
|
|
149
|
+
...(pushNotificationConfig ? { pushNotificationConfig } : {}),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function parsePushNotificationAuthenticationInfo(value) {
|
|
153
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
const typed = value;
|
|
157
|
+
const schemes = Array.isArray(typed.schemes)
|
|
158
|
+
? typed.schemes
|
|
159
|
+
.filter((entry) => typeof entry === "string" && entry.trim().length > 0)
|
|
160
|
+
.map((entry) => entry.trim())
|
|
161
|
+
: [];
|
|
162
|
+
if (schemes.length === 0) {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
schemes,
|
|
167
|
+
...(typeof typed.credentials === "string" && typed.credentials.trim().length > 0
|
|
168
|
+
? { credentials: typed.credentials.trim() }
|
|
169
|
+
: {}),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function parsePendingPushNotificationConfig(value) {
|
|
173
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
const typed = value;
|
|
177
|
+
const rawUrl = typeof typed.url === "string" ? typed.url.trim() : "";
|
|
178
|
+
if (rawUrl.length === 0) {
|
|
179
|
+
throw new Error("A2A push notification config requires `url`.");
|
|
180
|
+
}
|
|
181
|
+
const parsedUrl = new URL(rawUrl);
|
|
182
|
+
if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
|
|
183
|
+
throw new Error("A2A push notification config requires an http or https URL.");
|
|
184
|
+
}
|
|
185
|
+
const authentication = parsePushNotificationAuthenticationInfo(typed.authentication);
|
|
186
|
+
return {
|
|
187
|
+
url: parsedUrl.toString(),
|
|
188
|
+
...(typeof typed.id === "string" && typed.id.trim().length > 0 ? { id: typed.id.trim() } : {}),
|
|
189
|
+
...(typeof typed.token === "string" && typed.token.trim().length > 0 ? { token: typed.token.trim() } : {}),
|
|
190
|
+
...(authentication ? { authentication } : {}),
|
|
141
191
|
};
|
|
142
192
|
}
|
|
143
193
|
function attachServiceParameters(invocation, serviceParameters) {
|
|
@@ -198,6 +248,54 @@ function parseTaskListParams(params) {
|
|
|
198
248
|
limit: limitValue,
|
|
199
249
|
};
|
|
200
250
|
}
|
|
251
|
+
function parsePushNotificationLocatorParams(params) {
|
|
252
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
253
|
+
throw new Error("A2A push notification lookup requires params object.");
|
|
254
|
+
}
|
|
255
|
+
const typed = params;
|
|
256
|
+
const taskId = typeof typed.taskId === "string" && typed.taskId.trim().length > 0 ? typed.taskId.trim() : "";
|
|
257
|
+
const configId = typeof typed.id === "string" && typed.id.trim().length > 0 ? typed.id.trim() : "";
|
|
258
|
+
if (!taskId) {
|
|
259
|
+
throw new Error("A2A push notification lookup requires `taskId`.");
|
|
260
|
+
}
|
|
261
|
+
if (!configId) {
|
|
262
|
+
throw new Error("A2A push notification lookup requires `id`.");
|
|
263
|
+
}
|
|
264
|
+
return { taskId, configId };
|
|
265
|
+
}
|
|
266
|
+
function parsePushNotificationListParams(params) {
|
|
267
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
268
|
+
throw new Error("A2A push notification list requires params object.");
|
|
269
|
+
}
|
|
270
|
+
const typed = params;
|
|
271
|
+
const taskId = typeof typed.taskId === "string" && typed.taskId.trim().length > 0 ? typed.taskId.trim() : "";
|
|
272
|
+
if (!taskId) {
|
|
273
|
+
throw new Error("A2A push notification list requires `taskId`.");
|
|
274
|
+
}
|
|
275
|
+
const pageSize = typeof typed.pageSize === "number" && Number.isFinite(typed.pageSize)
|
|
276
|
+
? Math.max(1, Math.min(100, Math.trunc(typed.pageSize)))
|
|
277
|
+
: 20;
|
|
278
|
+
return {
|
|
279
|
+
taskId,
|
|
280
|
+
pageSize,
|
|
281
|
+
...(typeof typed.pageToken === "string" && typed.pageToken.trim().length > 0 ? { pageToken: typed.pageToken.trim() } : {}),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function parseCreatePushNotificationConfigParams(params) {
|
|
285
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
286
|
+
throw new Error("A2A push notification create requires params object.");
|
|
287
|
+
}
|
|
288
|
+
const typed = params;
|
|
289
|
+
const taskId = typeof typed.taskId === "string" && typed.taskId.trim().length > 0 ? typed.taskId.trim() : "";
|
|
290
|
+
if (!taskId) {
|
|
291
|
+
throw new Error("A2A push notification create requires `taskId`.");
|
|
292
|
+
}
|
|
293
|
+
const config = parsePendingPushNotificationConfig(typed.pushNotificationConfig ?? typed.config ?? typed);
|
|
294
|
+
if (!config) {
|
|
295
|
+
throw new Error("A2A push notification create requires a config object.");
|
|
296
|
+
}
|
|
297
|
+
return { taskId, config };
|
|
298
|
+
}
|
|
201
299
|
function mapRunState(state) {
|
|
202
300
|
switch (state) {
|
|
203
301
|
case "queued":
|
|
@@ -429,7 +527,7 @@ function buildAgentCard(runtime, options) {
|
|
|
429
527
|
return {
|
|
430
528
|
name: options.agentName,
|
|
431
529
|
description: options.agentDescription,
|
|
432
|
-
version:
|
|
530
|
+
version: AGENT_HARNESS_VERSION,
|
|
433
531
|
// Older JSON-RPC clients may still read these top-level fields; A2A v1.0 discovery uses `supportedInterfaces`.
|
|
434
532
|
protocolVersion: "1.0",
|
|
435
533
|
url: options.rpcUrl,
|
|
@@ -448,7 +546,7 @@ function buildAgentCard(runtime, options) {
|
|
|
448
546
|
],
|
|
449
547
|
capabilities: {
|
|
450
548
|
streaming: true,
|
|
451
|
-
pushNotifications:
|
|
549
|
+
pushNotifications: true,
|
|
452
550
|
extendedAgentCard: true,
|
|
453
551
|
},
|
|
454
552
|
defaultInputModes: ["text/plain"],
|
|
@@ -540,6 +638,42 @@ function withRunResult(task, result, streamedText) {
|
|
|
540
638
|
function isTerminalTaskState(state) {
|
|
541
639
|
return state === "TASK_STATE_COMPLETED" || state === "TASK_STATE_FAILED" || state === "TASK_STATE_CANCELED";
|
|
542
640
|
}
|
|
641
|
+
function clonePushNotificationConfig(config) {
|
|
642
|
+
return {
|
|
643
|
+
id: config.id,
|
|
644
|
+
taskId: config.taskId,
|
|
645
|
+
url: config.url,
|
|
646
|
+
...(config.token ? { token: config.token } : {}),
|
|
647
|
+
...(config.authentication ? { authentication: config.authentication } : {}),
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
function fingerprintTaskForPush(task) {
|
|
651
|
+
return JSON.stringify({
|
|
652
|
+
state: task.status.state,
|
|
653
|
+
timestamp: task.status.timestamp,
|
|
654
|
+
message: task.status.message,
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
async function sendPushNotification(config, task) {
|
|
658
|
+
const headers = {
|
|
659
|
+
"content-type": "application/json",
|
|
660
|
+
};
|
|
661
|
+
if (config.token) {
|
|
662
|
+
headers["x-a2a-push-token"] = config.token;
|
|
663
|
+
}
|
|
664
|
+
const scheme = config.authentication?.schemes[0]?.toLowerCase();
|
|
665
|
+
if (scheme === "bearer" && config.authentication?.credentials) {
|
|
666
|
+
headers.authorization = `Bearer ${config.authentication.credentials}`;
|
|
667
|
+
}
|
|
668
|
+
else if (scheme === "basic" && config.authentication?.credentials) {
|
|
669
|
+
headers.authorization = `Basic ${config.authentication.credentials}`;
|
|
670
|
+
}
|
|
671
|
+
await fetch(config.url, {
|
|
672
|
+
method: "POST",
|
|
673
|
+
headers,
|
|
674
|
+
body: JSON.stringify({ task }),
|
|
675
|
+
});
|
|
676
|
+
}
|
|
543
677
|
async function streamTaskUpdates(response, id, method, iterator) {
|
|
544
678
|
writeSseHeaders(response);
|
|
545
679
|
for await (const item of iterator) {
|
|
@@ -676,6 +810,63 @@ export async function serveA2aOverHttp(runtime, options = {}) {
|
|
|
676
810
|
...(options.provider ? { provider: options.provider } : {}),
|
|
677
811
|
...(options.defaultAgentId ? { defaultAgentId: options.defaultAgentId } : {}),
|
|
678
812
|
});
|
|
813
|
+
const pushConfigsByTask = new Map();
|
|
814
|
+
const registerPushNotificationConfig = (taskId, config) => {
|
|
815
|
+
const entry = {
|
|
816
|
+
id: config.id ?? `push-${createPersistentId()}`,
|
|
817
|
+
taskId,
|
|
818
|
+
url: config.url,
|
|
819
|
+
...(config.token ? { token: config.token } : {}),
|
|
820
|
+
...(config.authentication ? { authentication: config.authentication } : {}),
|
|
821
|
+
};
|
|
822
|
+
let taskConfigs = pushConfigsByTask.get(taskId);
|
|
823
|
+
if (!taskConfigs) {
|
|
824
|
+
taskConfigs = new Map();
|
|
825
|
+
pushConfigsByTask.set(taskId, taskConfigs);
|
|
826
|
+
}
|
|
827
|
+
taskConfigs.set(entry.id, entry);
|
|
828
|
+
return entry;
|
|
829
|
+
};
|
|
830
|
+
const listPushNotificationConfigs = (taskId) => [...(pushConfigsByTask.get(taskId)?.values() ?? [])].sort((left, right) => left.id.localeCompare(right.id));
|
|
831
|
+
const getPushNotificationConfig = (taskId, configId) => pushConfigsByTask.get(taskId)?.get(configId);
|
|
832
|
+
const deletePushNotificationConfig = (taskId, configId) => {
|
|
833
|
+
const taskConfigs = pushConfigsByTask.get(taskId);
|
|
834
|
+
if (!taskConfigs) {
|
|
835
|
+
return false;
|
|
836
|
+
}
|
|
837
|
+
const deleted = taskConfigs.delete(configId);
|
|
838
|
+
if (taskConfigs.size === 0) {
|
|
839
|
+
pushConfigsByTask.delete(taskId);
|
|
840
|
+
}
|
|
841
|
+
return deleted;
|
|
842
|
+
};
|
|
843
|
+
const dispatchPushNotificationsForTask = async (taskId) => {
|
|
844
|
+
const task = await hydrateTaskStateFromEvents(runtime, await buildTaskFromRuntime(runtime, taskId));
|
|
845
|
+
if (!task) {
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
const fingerprint = fingerprintTaskForPush(task);
|
|
849
|
+
const configs = listPushNotificationConfigs(taskId);
|
|
850
|
+
await Promise.all(configs.map(async (config) => {
|
|
851
|
+
if (config.lastFingerprint === fingerprint) {
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
try {
|
|
855
|
+
await sendPushNotification(config, task);
|
|
856
|
+
config.lastFingerprint = fingerprint;
|
|
857
|
+
}
|
|
858
|
+
catch {
|
|
859
|
+
// Push delivery is best-effort and should not fail the transport request.
|
|
860
|
+
}
|
|
861
|
+
}));
|
|
862
|
+
};
|
|
863
|
+
const pushRelevantEventTypes = new Set(["run.state.changed", "approval.requested", "approval.resolved"]);
|
|
864
|
+
const unsubscribePushNotifications = runtime.subscribe((event) => {
|
|
865
|
+
if (!pushRelevantEventTypes.has(event.eventType) || !pushConfigsByTask.has(event.runId)) {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
void dispatchPushNotificationsForTask(event.runId);
|
|
869
|
+
});
|
|
679
870
|
const httpServer = createServer(async (request, response) => {
|
|
680
871
|
try {
|
|
681
872
|
const requestUrl = new URL(request.url ?? "/", `http://${hostname}`);
|
|
@@ -719,6 +910,10 @@ export async function serveA2aOverHttp(runtime, options = {}) {
|
|
|
719
910
|
const requestRecord = await runtime.getRun(result.runId);
|
|
720
911
|
const approvals = await runtime.listApprovals({ threadId: result.threadId, runId: result.runId });
|
|
721
912
|
const task = buildTaskFromSessionAndRequest(toSessionRecord(session), toRequestRecord(requestRecord), approvals, result.output);
|
|
913
|
+
if (task && parsed.pushNotificationConfig) {
|
|
914
|
+
registerPushNotificationConfig(task.id, parsed.pushNotificationConfig);
|
|
915
|
+
await dispatchPushNotificationsForTask(task.id);
|
|
916
|
+
}
|
|
722
917
|
writeJson(response, 200, toSuccess(payload.id ?? null, toSendMessageResult(payload.method, task)));
|
|
723
918
|
return;
|
|
724
919
|
}
|
|
@@ -728,12 +923,24 @@ export async function serveA2aOverHttp(runtime, options = {}) {
|
|
|
728
923
|
return;
|
|
729
924
|
}
|
|
730
925
|
const parsed = parseMessageSendParams(payload.params);
|
|
731
|
-
|
|
926
|
+
let registeredPushTaskId;
|
|
927
|
+
const sourceIterator = streamSendMessageTaskUpdates(runtime, {
|
|
732
928
|
agentId: parsed.agentId ?? options.defaultAgentId,
|
|
733
929
|
input: parsed.input,
|
|
734
930
|
...(parsed.sessionId ? { threadId: parsed.sessionId } : {}),
|
|
735
931
|
invocation: attachServiceParameters(parsed.invocation, serviceParameters),
|
|
736
|
-
}, payload.method)
|
|
932
|
+
}, payload.method);
|
|
933
|
+
const iterator = (async function* () {
|
|
934
|
+
for await (const item of sourceIterator) {
|
|
935
|
+
if (item.task && parsed.pushNotificationConfig && !registeredPushTaskId) {
|
|
936
|
+
registeredPushTaskId = item.task.id;
|
|
937
|
+
registerPushNotificationConfig(item.task.id, parsed.pushNotificationConfig);
|
|
938
|
+
await dispatchPushNotificationsForTask(item.task.id);
|
|
939
|
+
}
|
|
940
|
+
yield item;
|
|
941
|
+
}
|
|
942
|
+
}());
|
|
943
|
+
await streamTaskUpdates(response, payload.id ?? null, payload.method, iterator);
|
|
737
944
|
return;
|
|
738
945
|
}
|
|
739
946
|
if (payload.method === "tasks/get" || payload.method === "GetTask") {
|
|
@@ -776,18 +983,55 @@ export async function serveA2aOverHttp(runtime, options = {}) {
|
|
|
776
983
|
writeJson(response, 200, toSuccess(payload.id ?? null, {
|
|
777
984
|
task,
|
|
778
985
|
streamable: false,
|
|
779
|
-
pushNotifications:
|
|
986
|
+
pushNotifications: true,
|
|
780
987
|
nextPollAfterMs: task.status.state === "TASK_STATE_COMPLETED" || task.status.state === "TASK_STATE_FAILED" || task.status.state === "TASK_STATE_CANCELED"
|
|
781
988
|
? 0
|
|
782
989
|
: 1_000,
|
|
783
990
|
}));
|
|
784
991
|
return;
|
|
785
992
|
}
|
|
786
|
-
if (payload.method === "CreateTaskPushNotificationConfig"
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
993
|
+
if (payload.method === "CreateTaskPushNotificationConfig") {
|
|
994
|
+
const { taskId, config } = parseCreatePushNotificationConfigParams(payload.params);
|
|
995
|
+
if (!await buildTaskFromRuntime(runtime, taskId)) {
|
|
996
|
+
writeJson(response, 200, toError(payload.id ?? null, -32001, "Task not found."));
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
const created = registerPushNotificationConfig(taskId, config);
|
|
1000
|
+
await dispatchPushNotificationsForTask(taskId);
|
|
1001
|
+
writeJson(response, 200, toSuccess(payload.id ?? null, clonePushNotificationConfig(created)));
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
if (payload.method === "GetTaskPushNotificationConfig") {
|
|
1005
|
+
const { taskId, configId } = parsePushNotificationLocatorParams(payload.params);
|
|
1006
|
+
const config = getPushNotificationConfig(taskId, configId);
|
|
1007
|
+
if (!config) {
|
|
1008
|
+
writeJson(response, 200, toError(payload.id ?? null, -32001, "Push notification config not found."));
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
writeJson(response, 200, toSuccess(payload.id ?? null, clonePushNotificationConfig(config)));
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
if (payload.method === "ListTaskPushNotificationConfigs") {
|
|
1015
|
+
const { taskId, pageSize, pageToken } = parsePushNotificationListParams(payload.params);
|
|
1016
|
+
const configs = listPushNotificationConfigs(taskId);
|
|
1017
|
+
const startIndex = pageToken ? Number.parseInt(Buffer.from(pageToken, "base64url").toString("utf8"), 10) || 0 : 0;
|
|
1018
|
+
const page = configs.slice(startIndex, startIndex + pageSize).map(clonePushNotificationConfig);
|
|
1019
|
+
const nextIndex = startIndex + page.length;
|
|
1020
|
+
const nextPageToken = nextIndex < configs.length ? Buffer.from(String(nextIndex), "utf8").toString("base64url") : "";
|
|
1021
|
+
const result = {
|
|
1022
|
+
configs: page,
|
|
1023
|
+
nextPageToken,
|
|
1024
|
+
};
|
|
1025
|
+
writeJson(response, 200, toSuccess(payload.id ?? null, result));
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
if (payload.method === "DeleteTaskPushNotificationConfig") {
|
|
1029
|
+
const { taskId, configId } = parsePushNotificationLocatorParams(payload.params);
|
|
1030
|
+
if (!deletePushNotificationConfig(taskId, configId)) {
|
|
1031
|
+
writeJson(response, 200, toError(payload.id ?? null, -32001, "Push notification config not found."));
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
writeJson(response, 200, toSuccess(payload.id ?? null, { deleted: true, id: configId, taskId }));
|
|
791
1035
|
return;
|
|
792
1036
|
}
|
|
793
1037
|
if (payload.method === "GetExtendedAgentCard") {
|
|
@@ -832,6 +1076,7 @@ export async function serveA2aOverHttp(runtime, options = {}) {
|
|
|
832
1076
|
agentCardUrl: `http://${hostname}:${resolvedPort}${agentCardPath}`,
|
|
833
1077
|
completed,
|
|
834
1078
|
close: async () => {
|
|
1079
|
+
unsubscribePushNotifications();
|
|
835
1080
|
await new Promise((resolve, reject) => {
|
|
836
1081
|
httpServer.close((error) => {
|
|
837
1082
|
if (error) {
|