@arnilo/prism-supervisor 0.0.6 → 0.0.8
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/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/dist/a2a-card.js +10 -1
- package/dist/a2a-client.js +126 -28
- package/dist/a2a-parts.d.ts +54 -0
- package/dist/a2a-parts.js +121 -0
- package/dist/a2a-push.d.ts +20 -0
- package/dist/a2a-push.js +38 -0
- package/dist/a2a-server.js +190 -169
- package/dist/a2a-types.d.ts +196 -9
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/supervisor.js +25 -9
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## [0.0.8] - 2026-07-20
|
|
6
|
+
|
|
7
|
+
- Added host-owned durable A2A task start/get/list/cancel/subscribe with bounded cursor replay, interrupted states, ordered rich task events, and non-disclosing task errors.
|
|
8
|
+
- Added opt-in bounded text/raw/URL/data parts; URL policy validates without dereferencing.
|
|
9
|
+
- Added capability-gated push config CRUD/client APIs and explicit bounded `deliverA2APushEvent()` retry/timeout/idempotency-key wrapper; webhook transport/credentials remain host-owned and secrets are omitted from responses.
|
|
10
|
+
|
|
11
|
+
## [0.0.7] - 2026-07-19
|
|
12
|
+
|
|
13
|
+
- Released with the exact 0.0.7 first-party package graph.
|
|
14
|
+
|
|
3
15
|
## [0.0.6] - 2026-07-19
|
|
4
16
|
|
|
5
17
|
- Fixed A2A streaming UTF-8 corruption across chunk boundaries with one fatal streaming decoder and incremental LF/CRLF/multiline SSE parsing; truncated or post-terminal streams fail without changing existing limits.
|
package/README.md
CHANGED
|
@@ -26,6 +26,6 @@ const supervisor = createSupervisor({
|
|
|
26
26
|
console.log((await supervisor.delegate({ childId: "research", input: "Check sources" })).text);
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
Also exports A2A 1.0
|
|
29
|
+
Also exports bounded A2A 1.0 cards, handler/client, rich one-of parts, host-owned `A2ATaskLifecycle`, reconnect subscriptions, and push-config CRUD. Direct text invocation remains compatible; durable get/list/cancel/subscribe and rich raw/data/URL parts require explicit adapters/policy. URL parts are validated but never fetched. Push persistence/network/credentials and exact-owner checks remain host-owned; explicit `deliverA2APushEvent()` only bounds attempts/time and forwards stable event IDs for host idempotency. Returned configs omit secrets. JSON-RPC/HTTPS is the only binding.
|
|
30
30
|
|
|
31
31
|
See [Supervisors](../../docs/supervisors.md) and [A2A interoperability](../../docs/a2a.md).
|
package/dist/a2a-card.js
CHANGED
|
@@ -60,6 +60,15 @@ function canonicalCard(card) {
|
|
|
60
60
|
return canonicalJson(unsigned);
|
|
61
61
|
}
|
|
62
62
|
function validateCard(card) {
|
|
63
|
+
let serialized;
|
|
64
|
+
try {
|
|
65
|
+
serialized = JSON.stringify(card);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
throw new A2AError("Agent card must be JSON", 400, "ERR_PRISM_A2A_CARD");
|
|
69
|
+
}
|
|
70
|
+
if (Buffer.byteLength(serialized) > 1024 * 1024 || card.supportedInterfaces.length > 16 || card.skills.length > 256)
|
|
71
|
+
throw new A2AError("Agent card exceeds collection/byte limits", 400, "ERR_PRISM_A2A_CARD");
|
|
63
72
|
if (!card.name?.trim() || !card.description?.trim() || !card.version?.trim())
|
|
64
73
|
throw new A2AError("Agent card identity is incomplete", 400, "ERR_PRISM_A2A_CARD");
|
|
65
74
|
if (!card.supportedInterfaces.length || !card.supportedInterfaces.every((item) => item.protocolBinding === "JSONRPC" && item.protocolVersion === "1.0" && isHttpsUrl(item.url)))
|
|
@@ -68,7 +77,7 @@ function validateCard(card) {
|
|
|
68
77
|
throw new A2AError("Agent card must support text/plain", 400, "ERR_PRISM_A2A_CARD");
|
|
69
78
|
const ids = new Set();
|
|
70
79
|
for (const skill of card.skills) {
|
|
71
|
-
if (!skill.id.trim() || !skill.name.trim() || !skill.description.trim() || ids.has(skill.id))
|
|
80
|
+
if (!skill.id.trim() || !skill.name.trim() || !skill.description.trim() || ids.has(skill.id) || skill.tags.length > 64 || [skill.id, skill.name, skill.description, ...skill.tags].some((value) => Buffer.byteLength(value) > 16 * 1024))
|
|
72
81
|
throw new A2AError("Agent card skill is invalid", 400, "ERR_PRISM_A2A_CARD");
|
|
73
82
|
ids.add(skill.id);
|
|
74
83
|
}
|
package/dist/a2a-client.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { createA2AAgentCard } from "./a2a-card.js";
|
|
2
|
+
import { resolveA2ALimits } from "./a2a-parts.js";
|
|
2
3
|
import { A2AError } from "./errors.js";
|
|
3
4
|
import { A2A_PROTOCOL_VERSION } from "./a2a-types.js";
|
|
4
|
-
const DEFAULTS = { maxRequestBytes: 64 * 1024, maxResponseBytes: 1024 * 1024, maxEventBytes: 64 * 1024, maxStreamBytes: 10 * 1024 * 1024, maxStreamEvents: 10_000, maxConcurrentRequests: 16, timeoutMs: 120_000, maxCardBytes: 64 * 1024 };
|
|
5
|
-
const HARD = { maxRequestBytes: 1024 * 1024, maxResponseBytes: 8 * 1024 * 1024, maxEventBytes: 1024 * 1024, maxStreamBytes: 64 * 1024 * 1024, maxStreamEvents: 100_000, maxConcurrentRequests: 256, timeoutMs: 30 * 60_000, maxCardBytes: 1024 * 1024 };
|
|
6
5
|
export function createA2AClient(options) {
|
|
7
6
|
const endpoint = requireAllowedHttpsUrl(options.endpoint, options.allowedOrigins);
|
|
8
7
|
const cardUrl = requireAllowedHttpsUrl(options.cardUrl ?? `${endpoint.origin}/.well-known/agent-card.json`, options.allowedOrigins);
|
|
9
8
|
const fetcher = options.fetch ?? globalThis.fetch;
|
|
10
|
-
const limits =
|
|
9
|
+
const limits = resolveA2ALimits(options.limits);
|
|
11
10
|
let active = 0;
|
|
12
11
|
let requestId = 0;
|
|
13
12
|
async function withRequest(signal, operation) {
|
|
@@ -46,7 +45,7 @@ export function createA2AClient(options) {
|
|
|
46
45
|
if (new TextEncoder().encode(body).byteLength > limits.maxRequestBytes)
|
|
47
46
|
throw new A2AError("A2A request exceeds max bytes", 413, "ERR_PRISM_A2A_REQUEST_LIMIT");
|
|
48
47
|
const authHeaders = await abortable(Promise.resolve(options.authorize?.({ endpoint: endpoint.href, signal }) ?? {}), signal);
|
|
49
|
-
const response = await fetcher(endpoint, { method: "POST", signal, redirect: "error", headers: { ...headersObject(authHeaders), "content-type": "application/a2a+json", accept: "application/a2a+json" }, body });
|
|
48
|
+
const response = await fetcher(endpoint, { method: "POST", signal, redirect: "error", headers: { ...headersObject(authHeaders), "content-type": "application/a2a+json", accept: "application/a2a+json", "a2a-version": "1.0" }, body });
|
|
50
49
|
if (!response.ok)
|
|
51
50
|
throw new A2AError("A2A remote request failed", response.status, "ERR_PRISM_A2A_REMOTE");
|
|
52
51
|
const rpc = parseRpcResponse(await readBoundedJson(response, limits.maxResponseBytes, signal), id);
|
|
@@ -69,7 +68,7 @@ export function createA2AClient(options) {
|
|
|
69
68
|
if (new TextEncoder().encode(body).byteLength > limits.maxRequestBytes)
|
|
70
69
|
throw new A2AError("A2A request exceeds max bytes", 413, "ERR_PRISM_A2A_REQUEST_LIMIT");
|
|
71
70
|
const authHeaders = await abortable(Promise.resolve(options.authorize?.({ endpoint: endpoint.href, signal: owned.signal }) ?? {}), owned.signal);
|
|
72
|
-
const response = await fetcher(endpoint, { method: "POST", signal: owned.signal, redirect: "error", headers: { ...headersObject(authHeaders), "content-type": "application/a2a+json", accept: "text/event-stream" }, body });
|
|
71
|
+
const response = await fetcher(endpoint, { method: "POST", signal: owned.signal, redirect: "error", headers: { ...headersObject(authHeaders), "content-type": "application/a2a+json", accept: "text/event-stream", "a2a-version": "1.0" }, body });
|
|
73
72
|
if (!response.ok || !response.body || !response.headers.get("content-type")?.startsWith("text/event-stream"))
|
|
74
73
|
throw new A2AError("A2A stream request failed", response.status, "ERR_PRISM_A2A_REMOTE");
|
|
75
74
|
reader = response.body.getReader();
|
|
@@ -90,13 +89,16 @@ export function createA2AClient(options) {
|
|
|
90
89
|
if (rpc.error)
|
|
91
90
|
throw new A2AError(safeRemote(rpc.error.message, options), 502, "ERR_PRISM_A2A_REMOTE");
|
|
92
91
|
const task = parseTaskResult(rpc.result);
|
|
93
|
-
if (task.status.state === "TASK_STATE_FAILED" || task.status.state === "TASK_STATE_CANCELED")
|
|
92
|
+
if (task.status.state === "TASK_STATE_FAILED" || task.status.state === "TASK_STATE_CANCELED" || task.status.state === "TASK_STATE_REJECTED")
|
|
94
93
|
throw new A2AError("Remote A2A stream task failed", 502, "ERR_PRISM_A2A_REMOTE");
|
|
94
|
+
if (task.status.state === "TASK_STATE_INPUT_REQUIRED" || task.status.state === "TASK_STATE_AUTH_REQUIRED")
|
|
95
|
+
throw new A2AError(`Remote A2A task interrupted: ${task.status.state}`, 409, "ERR_PRISM_A2A_INTERRUPTED");
|
|
95
96
|
if (task.status.state === "TASK_STATE_COMPLETED")
|
|
96
97
|
terminal = true;
|
|
97
98
|
for (const artifact of task.artifacts ?? [])
|
|
98
99
|
for (const part of artifact.parts)
|
|
99
|
-
|
|
100
|
+
if (typeof part.text === "string")
|
|
101
|
+
yield options.redactor?.redact(part.text) ?? part.text;
|
|
100
102
|
}
|
|
101
103
|
if (!terminal)
|
|
102
104
|
throw new A2AError("A2A stream ended before terminal task state", 502, "ERR_PRISM_A2A_REMOTE");
|
|
@@ -118,7 +120,74 @@ export function createA2AClient(options) {
|
|
|
118
120
|
await abortable(Promise.resolve(options.verifyCard(card)), signal);
|
|
119
121
|
return card;
|
|
120
122
|
}
|
|
121
|
-
|
|
123
|
+
async function invoke(method, params, signal) {
|
|
124
|
+
return withRequest(signal, async (owned) => {
|
|
125
|
+
await getCardWithin(owned);
|
|
126
|
+
const id = ++requestId;
|
|
127
|
+
const body = JSON.stringify({ jsonrpc: "2.0", id, method, params });
|
|
128
|
+
if (Buffer.byteLength(body) > limits.maxRequestBytes)
|
|
129
|
+
throw new A2AError("A2A request exceeds max bytes", 413, "ERR_PRISM_A2A_REQUEST_LIMIT");
|
|
130
|
+
const authHeaders = await abortable(Promise.resolve(options.authorize?.({ endpoint: endpoint.href, signal: owned }) ?? {}), owned);
|
|
131
|
+
const response = await fetcher(endpoint, { method: "POST", signal: owned, redirect: "error", headers: { ...headersObject(authHeaders), "content-type": "application/a2a+json", accept: "application/a2a+json", "a2a-version": "1.0" }, body });
|
|
132
|
+
if (!response.ok)
|
|
133
|
+
throw new A2AError("A2A remote request failed", response.status, "ERR_PRISM_A2A_REMOTE");
|
|
134
|
+
const rpc = parseRpcResponse(await readBoundedJson(response, limits.maxResponseBytes, owned), id);
|
|
135
|
+
if (rpc.error)
|
|
136
|
+
throw remoteProtocolError(rpc.error.code, rpc.error.message, options);
|
|
137
|
+
return rpc.result;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
async function sendMessage(message, call = {}) {
|
|
141
|
+
return parseTaskResult(await invoke("SendMessage", { message, configuration: { returnImmediately: call.returnImmediately ?? false } }, call.signal));
|
|
142
|
+
}
|
|
143
|
+
async function getTask(id, call = {}) { return parseTaskResult(await invoke("GetTask", { id, historyLength: call.historyLength ?? 0 }, call.signal)); }
|
|
144
|
+
async function listTasks(call = {}) {
|
|
145
|
+
const value = await invoke("ListTasks", { pageSize: call.pageSize ?? 50, pageToken: call.pageToken, contextId: call.contextId }, call.signal);
|
|
146
|
+
if (!isRecord(value) || !Array.isArray(value.tasks) || value.tasks.length > limits.maxPageSize)
|
|
147
|
+
throw new A2AError("Malformed A2A task page", 502, "ERR_PRISM_A2A_REMOTE");
|
|
148
|
+
return { tasks: value.tasks.map((task) => parseTaskResult(task)), nextPageToken: typeof value.nextPageToken === "string" ? value.nextPageToken : undefined, totalSize: typeof value.totalSize === "number" ? value.totalSize : undefined };
|
|
149
|
+
}
|
|
150
|
+
async function cancelTask(id, call = {}) { return parseTaskResult(await invoke("CancelTask", { id }, call.signal)); }
|
|
151
|
+
async function* subscribeToTask(id, call = {}) {
|
|
152
|
+
if (active >= limits.maxConcurrentRequests)
|
|
153
|
+
throw new A2AError("A2A client concurrency exceeded", 429, "ERR_PRISM_A2A_CONCURRENCY");
|
|
154
|
+
active += 1;
|
|
155
|
+
const owned = ownedSignal(call.signal, limits.timeoutMs);
|
|
156
|
+
let reader;
|
|
157
|
+
try {
|
|
158
|
+
await getCardWithin(owned.signal);
|
|
159
|
+
const request = ++requestId;
|
|
160
|
+
const authHeaders = await abortable(Promise.resolve(options.authorize?.({ endpoint: endpoint.href, signal: owned.signal }) ?? {}), owned.signal);
|
|
161
|
+
const response = await fetcher(endpoint, { method: "POST", signal: owned.signal, redirect: "error", headers: { ...headersObject(authHeaders), "content-type": "application/a2a+json", accept: "text/event-stream", "a2a-version": "1.0" }, body: JSON.stringify({ jsonrpc: "2.0", id: request, method: "SubscribeToTask", params: { id, afterEventId: call.afterEventId } }) });
|
|
162
|
+
if (!response.ok || !response.body || !response.headers.get("content-type")?.startsWith("text/event-stream"))
|
|
163
|
+
throw new A2AError("A2A subscribe request failed", response.status, "ERR_PRISM_A2A_REMOTE");
|
|
164
|
+
reader = response.body.getReader();
|
|
165
|
+
let previous = "", count = 0;
|
|
166
|
+
for await (const data of readA2AStreamData(reader, limits, owned.signal)) {
|
|
167
|
+
const rpc = parseRpcResponse(JSON.parse(data), request);
|
|
168
|
+
if (rpc.error)
|
|
169
|
+
throw remoteProtocolError(rpc.error.code, rpc.error.message, options);
|
|
170
|
+
const event = parseTaskEvent(rpc.result);
|
|
171
|
+
if (event.eventId === previous)
|
|
172
|
+
continue;
|
|
173
|
+
previous = event.eventId;
|
|
174
|
+
if (++count > limits.maxReplayEvents)
|
|
175
|
+
throw new A2AError("A2A replay exceeds event limit", 507, "ERR_PRISM_A2A_STREAM_LIMIT");
|
|
176
|
+
yield event;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
await reader?.cancel().catch(() => undefined);
|
|
181
|
+
owned.dispose();
|
|
182
|
+
active -= 1;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async function createPushConfig(config, call = {}) { return parsePushConfig(await invoke("CreateTaskPushNotificationConfig", { ...config }, call.signal)); }
|
|
186
|
+
async function getPushConfig(taskId, id, call = {}) { return parsePushConfig(await invoke("GetTaskPushNotificationConfig", { taskId, id }, call.signal)); }
|
|
187
|
+
async function listPushConfigs(taskId, call = {}) { const value = await invoke("ListTaskPushNotificationConfigs", { taskId, pageSize: call.pageSize, pageToken: call.pageToken }, call.signal); if (!isRecord(value) || !Array.isArray(value.configs))
|
|
188
|
+
throw new A2AError("Malformed A2A push config page", 502, "ERR_PRISM_A2A_REMOTE"); return { configs: value.configs.map(parsePushConfig), nextPageToken: typeof value.nextPageToken === "string" ? value.nextPageToken : undefined }; }
|
|
189
|
+
async function deletePushConfig(taskId, id, call = {}) { await invoke("DeleteTaskPushNotificationConfig", { taskId, id }, call.signal); }
|
|
190
|
+
return { getCard, send, sendMessage, stream, getTask, listTasks, cancelTask, subscribeToTask, createPushConfig, getPushConfig, listPushConfigs, deletePushConfig };
|
|
122
191
|
}
|
|
123
192
|
async function* readA2AStreamData(reader, limits, signal) {
|
|
124
193
|
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
@@ -224,7 +293,9 @@ function requestBody(id, method, input) {
|
|
|
224
293
|
function taskResult(task, options) {
|
|
225
294
|
if (task.status.state === "TASK_STATE_SUBMITTED" || task.status.state === "TASK_STATE_WORKING")
|
|
226
295
|
throw new A2AError("A2A response task is not terminal", 502, "ERR_PRISM_A2A_REMOTE");
|
|
227
|
-
|
|
296
|
+
if (task.status.state === "TASK_STATE_INPUT_REQUIRED" || task.status.state === "TASK_STATE_AUTH_REQUIRED")
|
|
297
|
+
throw new A2AError(`Remote A2A task interrupted: ${task.status.state}`, 409, "ERR_PRISM_A2A_INTERRUPTED");
|
|
298
|
+
const text = (task.artifacts ?? []).flatMap((artifact) => artifact.parts.flatMap((part) => "text" in part ? [part.text] : [])).join("");
|
|
228
299
|
const safeText = options.redactor?.redact(text) ?? text;
|
|
229
300
|
const status = task.status.state === "TASK_STATE_COMPLETED" ? "succeeded" : task.status.state === "TASK_STATE_CANCELED" ? "aborted" : "failed";
|
|
230
301
|
const content = safeText ? [{ type: "text", text: safeText }] : [];
|
|
@@ -232,16 +303,17 @@ function taskResult(task, options) {
|
|
|
232
303
|
return Object.freeze({ sessionId: task.contextId, runId: task.id, status, text: safeText, content, message, error: status === "failed" ? { message: "Remote A2A task failed" } : undefined, abortReason: status === "aborted" ? "Remote A2A task canceled" : undefined });
|
|
233
304
|
}
|
|
234
305
|
function parseTaskResult(value) {
|
|
235
|
-
if (!isRecord(value)
|
|
306
|
+
if (!isRecord(value))
|
|
236
307
|
throw new A2AError("Malformed A2A task result", 502, "ERR_PRISM_A2A_REMOTE");
|
|
237
|
-
const task = value.task;
|
|
308
|
+
const task = isRecord(value.task) ? value.task : value;
|
|
238
309
|
if (typeof task.id !== "string" || typeof task.contextId !== "string" || !isRecord(task.status) || typeof task.status.state !== "string")
|
|
239
310
|
throw new A2AError("Malformed A2A task", 502, "ERR_PRISM_A2A_REMOTE");
|
|
240
|
-
const states = new Set(["TASK_STATE_SUBMITTED", "TASK_STATE_WORKING", "TASK_STATE_COMPLETED", "TASK_STATE_FAILED", "TASK_STATE_CANCELED"]);
|
|
311
|
+
const states = new Set(["TASK_STATE_SUBMITTED", "TASK_STATE_WORKING", "TASK_STATE_COMPLETED", "TASK_STATE_FAILED", "TASK_STATE_CANCELED", "TASK_STATE_INPUT_REQUIRED", "TASK_STATE_REJECTED", "TASK_STATE_AUTH_REQUIRED"]);
|
|
241
312
|
if (!states.has(task.status.state))
|
|
242
313
|
throw new A2AError("Unknown A2A task state", 502, "ERR_PRISM_A2A_REMOTE");
|
|
243
314
|
const artifacts = task.artifacts === undefined ? undefined : parseArtifacts(task.artifacts);
|
|
244
|
-
|
|
315
|
+
const history = task.history === undefined ? undefined : Array.isArray(task.history) ? task.history.map(parseRemoteMessage) : (() => { throw new A2AError("Malformed A2A task history", 502, "ERR_PRISM_A2A_REMOTE"); })();
|
|
316
|
+
return { id: task.id, contextId: task.contextId, status: { state: task.status.state, timestamp: typeof task.status.timestamp === "string" ? task.status.timestamp : new Date(0).toISOString() }, artifacts, history };
|
|
245
317
|
}
|
|
246
318
|
function parseArtifacts(value) {
|
|
247
319
|
if (!Array.isArray(value) || value.length > 32)
|
|
@@ -249,13 +321,49 @@ function parseArtifacts(value) {
|
|
|
249
321
|
return value.map((artifact) => {
|
|
250
322
|
if (!isRecord(artifact) || typeof artifact.artifactId !== "string" || !Array.isArray(artifact.parts) || artifact.parts.length > 32)
|
|
251
323
|
throw new A2AError("Malformed A2A artifact", 502, "ERR_PRISM_A2A_REMOTE");
|
|
252
|
-
return { artifactId: artifact.artifactId, parts: artifact.parts.map(
|
|
253
|
-
if (!isRecord(part) || typeof part.text !== "string")
|
|
254
|
-
throw new A2AError("Unsupported A2A artifact part", 502, "ERR_PRISM_A2A_REMOTE");
|
|
255
|
-
return { text: part.text };
|
|
256
|
-
}) };
|
|
324
|
+
return { artifactId: artifact.artifactId, parts: artifact.parts.map(parseRemotePart) };
|
|
257
325
|
});
|
|
258
326
|
}
|
|
327
|
+
function parseRemotePart(value) {
|
|
328
|
+
if (!isRecord(value))
|
|
329
|
+
throw new A2AError("Malformed A2A part", 502, "ERR_PRISM_A2A_REMOTE");
|
|
330
|
+
const keys = ["text", "raw", "url", "data"].filter((key) => Object.hasOwn(value, key));
|
|
331
|
+
if (keys.length !== 1)
|
|
332
|
+
throw new A2AError("Malformed A2A part union", 502, "ERR_PRISM_A2A_REMOTE");
|
|
333
|
+
const base = { mediaType: typeof value.mediaType === "string" ? value.mediaType : undefined, filename: typeof value.filename === "string" ? value.filename : undefined };
|
|
334
|
+
if (keys[0] === "text" && typeof value.text === "string")
|
|
335
|
+
return { ...base, text: value.text };
|
|
336
|
+
if (keys[0] === "raw" && typeof value.raw === "string" && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(value.raw))
|
|
337
|
+
return { ...base, raw: value.raw };
|
|
338
|
+
if (keys[0] === "url" && typeof value.url === "string") {
|
|
339
|
+
const url = new URL(value.url);
|
|
340
|
+
if (url.protocol !== "https:" || url.username || url.password || url.hash)
|
|
341
|
+
throw new A2AError("Unsafe remote A2A URL part", 502, "ERR_PRISM_A2A_REMOTE");
|
|
342
|
+
return { ...base, url: url.href };
|
|
343
|
+
}
|
|
344
|
+
if (keys[0] === "data")
|
|
345
|
+
return { ...base, data: structuredClone(value.data) };
|
|
346
|
+
throw new A2AError("Malformed A2A part", 502, "ERR_PRISM_A2A_REMOTE");
|
|
347
|
+
}
|
|
348
|
+
function parseRemoteMessage(value) { if (!isRecord(value) || typeof value.messageId !== "string" || !Array.isArray(value.parts) || (value.role !== "ROLE_USER" && value.role !== "ROLE_AGENT" && value.role !== "user" && value.role !== "agent"))
|
|
349
|
+
throw new A2AError("Malformed A2A message", 502, "ERR_PRISM_A2A_REMOTE"); return { role: value.role, messageId: value.messageId, parts: value.parts.map(parseRemotePart), contextId: typeof value.contextId === "string" ? value.contextId : undefined, taskId: typeof value.taskId === "string" ? value.taskId : undefined }; }
|
|
350
|
+
function parseTaskEvent(value) {
|
|
351
|
+
if (!isRecord(value) || typeof value.eventId !== "string" || !value.eventId)
|
|
352
|
+
throw new A2AError("Malformed A2A task event", 502, "ERR_PRISM_A2A_REMOTE");
|
|
353
|
+
if (isRecord(value.task))
|
|
354
|
+
return { eventId: value.eventId, task: parseTaskResult(value.task) };
|
|
355
|
+
if (isRecord(value.statusUpdate) && typeof value.statusUpdate.taskId === "string" && typeof value.statusUpdate.contextId === "string" && isRecord(value.statusUpdate.status)) {
|
|
356
|
+
const parsed = parseTaskResult({ id: value.statusUpdate.taskId, contextId: value.statusUpdate.contextId, status: value.statusUpdate.status });
|
|
357
|
+
return { eventId: value.eventId, statusUpdate: { taskId: parsed.id, contextId: parsed.contextId, status: parsed.status } };
|
|
358
|
+
}
|
|
359
|
+
if (isRecord(value.artifactUpdate) && typeof value.artifactUpdate.taskId === "string" && typeof value.artifactUpdate.contextId === "string" && isRecord(value.artifactUpdate.artifact))
|
|
360
|
+
return { eventId: value.eventId, artifactUpdate: { taskId: value.artifactUpdate.taskId, contextId: value.artifactUpdate.contextId, artifact: parseArtifacts([value.artifactUpdate.artifact])[0], append: value.artifactUpdate.append === true, lastChunk: value.artifactUpdate.lastChunk === true } };
|
|
361
|
+
throw new A2AError("Malformed A2A task event", 502, "ERR_PRISM_A2A_REMOTE");
|
|
362
|
+
}
|
|
363
|
+
function parsePushConfig(value) { if (!isRecord(value) || typeof value.id !== "string" || typeof value.taskId !== "string" || typeof value.url !== "string")
|
|
364
|
+
throw new A2AError("Malformed A2A push config", 502, "ERR_PRISM_A2A_REMOTE"); const url = new URL(value.url); if (url.protocol !== "https:" || url.username || url.password || url.hash)
|
|
365
|
+
throw new A2AError("Unsafe A2A push URL", 502, "ERR_PRISM_A2A_REMOTE"); return { id: value.id, taskId: value.taskId, url: url.href, token: typeof value.token === "string" ? value.token : undefined, authentication: isRecord(value.authentication) && typeof value.authentication.scheme === "string" ? { scheme: value.authentication.scheme, credentials: typeof value.authentication.credentials === "string" ? value.authentication.credentials : undefined } : undefined }; }
|
|
366
|
+
function remoteProtocolError(code, message, options) { return new A2AError(safeRemote(message, options), code === -32001 ? 404 : code === -32004 ? 501 : 502, code === -32001 ? "ERR_PRISM_A2A_TASK_NOT_FOUND" : code === -32004 ? "ERR_PRISM_A2A_UNSUPPORTED" : "ERR_PRISM_A2A_REMOTE"); }
|
|
259
367
|
function parseRpcResponse(value, id) {
|
|
260
368
|
if (!isRecord(value) || value.jsonrpc !== "2.0" || value.id !== id)
|
|
261
369
|
throw new A2AError("Malformed A2A JSON-RPC response", 502, "ERR_PRISM_A2A_REMOTE");
|
|
@@ -339,16 +447,6 @@ function requireAllowedHttpsUrl(value, origins) {
|
|
|
339
447
|
throw new A2AError("A2A endpoint origin is not allow-listed HTTPS", 403, "ERR_PRISM_A2A_ORIGIN");
|
|
340
448
|
return url;
|
|
341
449
|
}
|
|
342
|
-
function clientLimits(input = {}) {
|
|
343
|
-
const output = {};
|
|
344
|
-
for (const key of Object.keys(DEFAULTS)) {
|
|
345
|
-
const value = input[key] ?? DEFAULTS[key];
|
|
346
|
-
if (!Number.isSafeInteger(value) || value < 1 || value > HARD[key])
|
|
347
|
-
throw new A2AError(`${key} is invalid`, 400, "ERR_PRISM_A2A_CONFIG");
|
|
348
|
-
output[key] = value;
|
|
349
|
-
}
|
|
350
|
-
return output;
|
|
351
|
-
}
|
|
352
450
|
function ownedSignal(parent, timeoutMs) {
|
|
353
451
|
const controller = new AbortController();
|
|
354
452
|
const abort = () => controller.abort(parent?.reason);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { A2ALimits, A2AMessage, A2APart, A2APartPolicy, A2ATask } from "./a2a-types.js";
|
|
2
|
+
export declare const A2A_DEFAULT_LIMITS: {
|
|
3
|
+
readonly maxRequestBytes: number;
|
|
4
|
+
readonly maxResponseBytes: number;
|
|
5
|
+
readonly maxEventBytes: number;
|
|
6
|
+
readonly maxStreamBytes: number;
|
|
7
|
+
readonly maxStreamEvents: 10000;
|
|
8
|
+
readonly maxConcurrentRequests: 16;
|
|
9
|
+
readonly timeoutMs: 120000;
|
|
10
|
+
readonly maxCardBytes: number;
|
|
11
|
+
readonly maxIdBytes: 256;
|
|
12
|
+
readonly maxParts: 32;
|
|
13
|
+
readonly maxPartBytes: number;
|
|
14
|
+
readonly maxRawBytes: number;
|
|
15
|
+
readonly maxDataBytes: number;
|
|
16
|
+
readonly maxArtifacts: 32;
|
|
17
|
+
readonly maxHistory: 100;
|
|
18
|
+
readonly maxPageSize: 100;
|
|
19
|
+
readonly maxCursorBytes: 4096;
|
|
20
|
+
readonly maxReplayEvents: 1000;
|
|
21
|
+
readonly maxPushConfigs: 10;
|
|
22
|
+
};
|
|
23
|
+
export declare const A2A_HARD_LIMITS: {
|
|
24
|
+
readonly maxRequestBytes: number;
|
|
25
|
+
readonly maxResponseBytes: number;
|
|
26
|
+
readonly maxEventBytes: number;
|
|
27
|
+
readonly maxStreamBytes: number;
|
|
28
|
+
readonly maxStreamEvents: 100000;
|
|
29
|
+
readonly maxConcurrentRequests: 256;
|
|
30
|
+
readonly timeoutMs: number;
|
|
31
|
+
readonly maxCardBytes: number;
|
|
32
|
+
readonly maxIdBytes: 4096;
|
|
33
|
+
readonly maxParts: 256;
|
|
34
|
+
readonly maxPartBytes: number;
|
|
35
|
+
readonly maxRawBytes: number;
|
|
36
|
+
readonly maxDataBytes: number;
|
|
37
|
+
readonly maxArtifacts: 256;
|
|
38
|
+
readonly maxHistory: 1000;
|
|
39
|
+
readonly maxPageSize: 1000;
|
|
40
|
+
readonly maxCursorBytes: number;
|
|
41
|
+
readonly maxReplayEvents: 10000;
|
|
42
|
+
readonly maxPushConfigs: 100;
|
|
43
|
+
};
|
|
44
|
+
export type ResolvedA2ALimits = {
|
|
45
|
+
readonly [K in keyof typeof A2A_DEFAULT_LIMITS]: number;
|
|
46
|
+
};
|
|
47
|
+
export declare function resolveA2ALimits(input?: A2ALimits): ResolvedA2ALimits;
|
|
48
|
+
export declare function parseA2AMessage(value: unknown, limits: ResolvedA2ALimits, policy?: A2APartPolicy): Promise<A2AMessage>;
|
|
49
|
+
export declare function parseA2APart(value: unknown, limits: ResolvedA2ALimits, policy?: A2APartPolicy): Promise<A2APart>;
|
|
50
|
+
export declare function validateA2ATask(task: A2ATask, limits: ResolvedA2ALimits, policy?: A2APartPolicy): Promise<A2ATask>;
|
|
51
|
+
export declare function requireId(value: unknown, limits: ResolvedA2ALimits, label?: string): string;
|
|
52
|
+
export declare function optionalCursor(value: unknown, limits: ResolvedA2ALimits): string | undefined;
|
|
53
|
+
export declare function bounded<T>(value: T, maxBytes: number, label: string): T;
|
|
54
|
+
export declare function record(value: unknown): value is Record<string, unknown>;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { A2AError } from "./errors.js";
|
|
2
|
+
export const A2A_DEFAULT_LIMITS = { maxRequestBytes: 64 * 1024, maxResponseBytes: 1024 * 1024, maxEventBytes: 64 * 1024, maxStreamBytes: 10 * 1024 * 1024, maxStreamEvents: 10_000, maxConcurrentRequests: 16, timeoutMs: 120_000, maxCardBytes: 64 * 1024, maxIdBytes: 256, maxParts: 32, maxPartBytes: 1024 * 1024, maxRawBytes: 1024 * 1024, maxDataBytes: 256 * 1024, maxArtifacts: 32, maxHistory: 100, maxPageSize: 100, maxCursorBytes: 4096, maxReplayEvents: 1000, maxPushConfigs: 10 };
|
|
3
|
+
export const A2A_HARD_LIMITS = { maxRequestBytes: 1024 * 1024, maxResponseBytes: 8 * 1024 * 1024, maxEventBytes: 1024 * 1024, maxStreamBytes: 64 * 1024 * 1024, maxStreamEvents: 100_000, maxConcurrentRequests: 256, timeoutMs: 30 * 60_000, maxCardBytes: 1024 * 1024, maxIdBytes: 4096, maxParts: 256, maxPartBytes: 8 * 1024 * 1024, maxRawBytes: 8 * 1024 * 1024, maxDataBytes: 4 * 1024 * 1024, maxArtifacts: 256, maxHistory: 1000, maxPageSize: 1000, maxCursorBytes: 16 * 1024, maxReplayEvents: 10_000, maxPushConfigs: 100 };
|
|
4
|
+
export function resolveA2ALimits(input = {}) {
|
|
5
|
+
const output = {};
|
|
6
|
+
for (const key of Object.keys(A2A_DEFAULT_LIMITS)) {
|
|
7
|
+
const value = input[key] ?? A2A_DEFAULT_LIMITS[key];
|
|
8
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > A2A_HARD_LIMITS[key])
|
|
9
|
+
throw new A2AError(`${key} is invalid`, 400, "ERR_PRISM_A2A_CONFIG");
|
|
10
|
+
output[key] = value;
|
|
11
|
+
}
|
|
12
|
+
return output;
|
|
13
|
+
}
|
|
14
|
+
export async function parseA2AMessage(value, limits, policy = {}) {
|
|
15
|
+
if (!record(value) || (value.role !== "user" && value.role !== "ROLE_USER" && value.role !== "agent" && value.role !== "ROLE_AGENT") || !id(value.messageId, limits) || !Array.isArray(value.parts) || value.parts.length < 1 || value.parts.length > limits.maxParts)
|
|
16
|
+
throw new A2AError("Invalid A2A message", 400, "ERR_PRISM_A2A_MESSAGE");
|
|
17
|
+
const parts = [];
|
|
18
|
+
for (const part of value.parts)
|
|
19
|
+
parts.push(await parseA2APart(part, limits, policy));
|
|
20
|
+
const message = { role: value.role, messageId: value.messageId, parts, contextId: optionalId(value.contextId, limits), taskId: optionalId(value.taskId, limits), metadata: record(value.metadata) ? value.metadata : undefined };
|
|
21
|
+
bounded(message, limits.maxRequestBytes, "A2A message");
|
|
22
|
+
return message;
|
|
23
|
+
}
|
|
24
|
+
export async function parseA2APart(value, limits, policy = {}) {
|
|
25
|
+
if (!record(value))
|
|
26
|
+
throw new A2AError("Invalid A2A part", 400, "ERR_PRISM_A2A_PART");
|
|
27
|
+
const variants = ["text", "raw", "url", "data"].filter((key) => Object.hasOwn(value, key));
|
|
28
|
+
if (Object.keys(value).some((key) => !["text", "raw", "url", "data", "mediaType", "filename", "metadata"].includes(key)))
|
|
29
|
+
throw new A2AError("Unknown A2A part field", 400, "ERR_PRISM_A2A_PART");
|
|
30
|
+
if (variants.length !== 1)
|
|
31
|
+
throw new A2AError("A2A part requires exactly one content field", 400, "ERR_PRISM_A2A_PART");
|
|
32
|
+
const base = { mediaType: optionalString(value.mediaType, 256), filename: optionalString(value.filename, 1024), metadata: record(value.metadata) ? value.metadata : undefined };
|
|
33
|
+
let part;
|
|
34
|
+
if (variants[0] === "text" && typeof value.text === "string")
|
|
35
|
+
part = { ...base, text: value.text };
|
|
36
|
+
else if (variants[0] === "raw" && policy.allowRaw && typeof value.raw === "string" && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(value.raw) && Buffer.from(value.raw, "base64").byteLength <= limits.maxRawBytes)
|
|
37
|
+
part = { ...base, raw: value.raw };
|
|
38
|
+
else if (variants[0] === "url" && policy.allowUrl && policy.validateUrl && typeof value.url === "string") {
|
|
39
|
+
let url;
|
|
40
|
+
try {
|
|
41
|
+
url = new URL(value.url);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
throw new A2AError("Invalid A2A file URL", 400, "ERR_PRISM_A2A_PART");
|
|
45
|
+
}
|
|
46
|
+
if (url.protocol !== "https:" || url.username || url.password || url.hash)
|
|
47
|
+
throw new A2AError("A2A file URL requires credential-free HTTPS", 403, "ERR_PRISM_A2A_ORIGIN");
|
|
48
|
+
await policy.validateUrl?.(url); // Validation only; never dereference.
|
|
49
|
+
part = { ...base, url: url.href };
|
|
50
|
+
}
|
|
51
|
+
else if (variants[0] === "data" && policy.allowData) {
|
|
52
|
+
bounded(value.data, limits.maxDataBytes, "A2A data part");
|
|
53
|
+
part = { ...base, data: structuredClone(value.data) };
|
|
54
|
+
}
|
|
55
|
+
else
|
|
56
|
+
throw new A2AError("Unsupported A2A part", 400, "ERR_PRISM_A2A_PART");
|
|
57
|
+
bounded(part, limits.maxPartBytes, "A2A part");
|
|
58
|
+
return part;
|
|
59
|
+
}
|
|
60
|
+
export async function validateA2ATask(task, limits, policy = { allowRaw: true, allowUrl: true, allowData: true }) {
|
|
61
|
+
if (!id(task.id, limits) || !id(task.contextId, limits) || !task.status || !TASK_STATES.has(task.status.state) || !Number.isFinite(Date.parse(task.status.timestamp)))
|
|
62
|
+
throw new A2AError("Invalid A2A task", 500, "ERR_PRISM_A2A_TASK");
|
|
63
|
+
if ((task.artifacts?.length ?? 0) > limits.maxArtifacts || (task.history?.length ?? 0) > limits.maxHistory)
|
|
64
|
+
throw new A2AError("A2A task collection limit exceeded", 507, "ERR_PRISM_A2A_RESPONSE_LIMIT");
|
|
65
|
+
for (const artifact of task.artifacts ?? [])
|
|
66
|
+
await validateArtifact(artifact, limits, policy);
|
|
67
|
+
for (const message of task.history ?? [])
|
|
68
|
+
await parseA2AMessage(message, limits, policy);
|
|
69
|
+
bounded(task, limits.maxResponseBytes, "A2A task");
|
|
70
|
+
return task;
|
|
71
|
+
}
|
|
72
|
+
async function validateArtifact(value, limits, policy) {
|
|
73
|
+
if (!id(value.artifactId, limits) || !value.parts.length || value.parts.length > limits.maxParts)
|
|
74
|
+
throw new A2AError("Invalid A2A artifact", 500, "ERR_PRISM_A2A_TASK");
|
|
75
|
+
for (const part of value.parts)
|
|
76
|
+
await parseA2APart(part, limits, policy);
|
|
77
|
+
}
|
|
78
|
+
export function requireId(value, limits, label = "task id") { if (!id(value, limits))
|
|
79
|
+
throw new A2AError(`Invalid A2A ${label}`, 400, "ERR_PRISM_A2A_REQUEST"); return value; }
|
|
80
|
+
export function optionalCursor(value, limits) { if (value === undefined || value === "")
|
|
81
|
+
return undefined; if (typeof value !== "string" || Buffer.byteLength(value) > limits.maxCursorBytes)
|
|
82
|
+
throw new A2AError("Invalid A2A page/event cursor", 400, "ERR_PRISM_A2A_REQUEST"); return value; }
|
|
83
|
+
export function bounded(value, maxBytes, label) {
|
|
84
|
+
let properties = 0;
|
|
85
|
+
const stack = [{ value, depth: 0 }];
|
|
86
|
+
const seen = new Set();
|
|
87
|
+
while (stack.length) {
|
|
88
|
+
const item = stack.pop();
|
|
89
|
+
if (typeof item.value === "number" && !Number.isFinite(item.value))
|
|
90
|
+
throw new A2AError(`${label} contains non-finite number`, 400, "ERR_PRISM_A2A_REQUEST");
|
|
91
|
+
if (!item.value || typeof item.value !== "object")
|
|
92
|
+
continue;
|
|
93
|
+
if (item.depth > 64 || seen.has(item.value))
|
|
94
|
+
throw new A2AError(`${label} exceeds JSON depth or is cyclic`, 400, "ERR_PRISM_A2A_REQUEST");
|
|
95
|
+
seen.add(item.value);
|
|
96
|
+
const values = Array.isArray(item.value) ? item.value : Object.values(item.value);
|
|
97
|
+
properties += values.length;
|
|
98
|
+
if (properties > 10_000)
|
|
99
|
+
throw new A2AError(`${label} exceeds JSON property limit`, 400, "ERR_PRISM_A2A_REQUEST");
|
|
100
|
+
for (const child of values)
|
|
101
|
+
stack.push({ value: child, depth: item.depth + 1 });
|
|
102
|
+
}
|
|
103
|
+
let json;
|
|
104
|
+
try {
|
|
105
|
+
json = JSON.stringify(value);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
throw new A2AError(`${label} is not JSON`, 400, "ERR_PRISM_A2A_REQUEST");
|
|
109
|
+
}
|
|
110
|
+
if (Buffer.byteLength(json) > maxBytes)
|
|
111
|
+
throw new A2AError(`${label} exceeds max bytes`, 507, "ERR_PRISM_A2A_RESPONSE_LIMIT");
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
export function record(value) { return !!value && typeof value === "object" && !Array.isArray(value); }
|
|
115
|
+
function id(value, limits) { return typeof value === "string" && value.length > 0 && Buffer.byteLength(value) <= limits.maxIdBytes; }
|
|
116
|
+
function optionalId(value, limits) { return value === undefined ? undefined : requireId(value, limits); }
|
|
117
|
+
function optionalString(value, max) { if (value === undefined)
|
|
118
|
+
return undefined; if (typeof value !== "string" || Buffer.byteLength(value) > max)
|
|
119
|
+
throw new A2AError("Invalid A2A part metadata", 400, "ERR_PRISM_A2A_PART"); return value; }
|
|
120
|
+
const TASK_STATES = new Set(["TASK_STATE_SUBMITTED", "TASK_STATE_WORKING", "TASK_STATE_COMPLETED", "TASK_STATE_FAILED", "TASK_STATE_CANCELED", "TASK_STATE_INPUT_REQUIRED", "TASK_STATE_REJECTED", "TASK_STATE_AUTH_REQUIRED"]);
|
|
121
|
+
//# sourceMappingURL=a2a-parts.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { A2ALimits, A2APushConfig, A2ATaskEvent } from "./a2a-types.js";
|
|
2
|
+
export interface A2APushDelivery {
|
|
3
|
+
deliver(input: {
|
|
4
|
+
readonly config: A2APushConfig;
|
|
5
|
+
readonly event: A2ATaskEvent;
|
|
6
|
+
readonly idempotencyKey: string;
|
|
7
|
+
readonly attempt: number;
|
|
8
|
+
readonly signal: AbortSignal;
|
|
9
|
+
}): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export interface DeliverA2APushEventOptions {
|
|
12
|
+
readonly signal?: AbortSignal;
|
|
13
|
+
readonly maxAttempts?: number;
|
|
14
|
+
readonly timeoutMs?: number;
|
|
15
|
+
readonly limits?: A2ALimits;
|
|
16
|
+
}
|
|
17
|
+
/** Explicit host call; stores no timer/config/event and performs no network I/O itself. */
|
|
18
|
+
export declare function deliverA2APushEvent(delivery: A2APushDelivery, config: A2APushConfig, event: A2ATaskEvent, options?: DeliverA2APushEventOptions): Promise<{
|
|
19
|
+
readonly attempts: number;
|
|
20
|
+
}>;
|
package/dist/a2a-push.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { bounded, resolveA2ALimits } from "./a2a-parts.js";
|
|
2
|
+
import { A2AError } from "./errors.js";
|
|
3
|
+
/** Explicit host call; stores no timer/config/event and performs no network I/O itself. */
|
|
4
|
+
export async function deliverA2APushEvent(delivery, config, event, options = {}) {
|
|
5
|
+
const limits = resolveA2ALimits(options.limits);
|
|
6
|
+
const maxAttempts = options.maxAttempts ?? 1, timeoutMs = options.timeoutMs ?? 10_000;
|
|
7
|
+
if (!Number.isSafeInteger(maxAttempts) || maxAttempts < 1 || maxAttempts > 3 || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 60_000)
|
|
8
|
+
throw new A2AError("Invalid A2A push delivery limits", 400, "ERR_PRISM_A2A_CONFIG");
|
|
9
|
+
bounded(event, limits.maxEventBytes, "A2A push event");
|
|
10
|
+
if (!event.eventId || Buffer.byteLength(event.eventId) > limits.maxCursorBytes)
|
|
11
|
+
throw new A2AError("Invalid A2A push event id", 400, "ERR_PRISM_A2A_PUSH");
|
|
12
|
+
let last;
|
|
13
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
14
|
+
const controller = new AbortController();
|
|
15
|
+
const abort = () => controller.abort(options.signal?.reason);
|
|
16
|
+
if (options.signal?.aborted)
|
|
17
|
+
abort();
|
|
18
|
+
else
|
|
19
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
20
|
+
const timer = setTimeout(() => controller.abort(new DOMException("A2A push delivery timed out", "AbortError")), timeoutMs);
|
|
21
|
+
try {
|
|
22
|
+
controller.signal.throwIfAborted();
|
|
23
|
+
await Promise.race([delivery.deliver({ config, event, idempotencyKey: event.eventId, attempt, signal: controller.signal }), new Promise((_resolve, reject) => controller.signal.addEventListener("abort", () => reject(controller.signal.reason), { once: true }))]);
|
|
24
|
+
return { attempts: attempt };
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
last = error;
|
|
28
|
+
if (controller.signal.aborted || attempt === maxAttempts)
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
clearTimeout(timer);
|
|
33
|
+
options.signal?.removeEventListener("abort", abort);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
throw new A2AError(last instanceof DOMException && last.name === "AbortError" ? "A2A push delivery timed out" : "A2A push delivery failed", 502, "ERR_PRISM_A2A_PUSH");
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=a2a-push.js.map
|