@arnilo/prism-supervisor 0.0.96 → 0.1.0

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.
@@ -1,7 +1,7 @@
1
1
  import { createA2AAgentCard } from "./a2a-card.js";
2
2
  import { resolveA2ALimits } from "./a2a-parts.js";
3
+ import { A2A_PROTOCOL_VERSION, } from "./a2a-types.js";
3
4
  import { A2AError } from "./errors.js";
4
- import { A2A_PROTOCOL_VERSION } from "./a2a-types.js";
5
5
  export function createA2AClient(options) {
6
6
  const endpoint = requireAllowedHttpsUrl(options.endpoint, options.allowedOrigins);
7
7
  const cardUrl = requireAllowedHttpsUrl(options.cardUrl ?? `${endpoint.origin}/.well-known/agent-card.json`, options.allowedOrigins);
@@ -24,7 +24,12 @@ export function createA2AClient(options) {
24
24
  }
25
25
  async function getCard(call = {}) {
26
26
  return withRequest(call.signal, async (signal) => {
27
- const response = await fetcher(cardUrl, { method: "GET", signal, redirect: "error", headers: { accept: "application/a2a+json, application/json" } });
27
+ const response = await fetcher(cardUrl, {
28
+ method: "GET",
29
+ signal,
30
+ redirect: "error",
31
+ headers: { accept: "application/a2a+json, application/json" },
32
+ });
28
33
  if (!response.ok)
29
34
  throw new A2AError("A2A card request failed", response.status, "ERR_PRISM_A2A_REMOTE");
30
35
  const value = await readBoundedJson(response, limits.maxCardBytes, signal);
@@ -45,7 +50,18 @@ export function createA2AClient(options) {
45
50
  if (new TextEncoder().encode(body).byteLength > limits.maxRequestBytes)
46
51
  throw new A2AError("A2A request exceeds max bytes", 413, "ERR_PRISM_A2A_REQUEST_LIMIT");
47
52
  const authHeaders = await abortable(Promise.resolve(options.authorize?.({ endpoint: endpoint.href, signal }) ?? {}), signal);
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 });
53
+ const response = await fetcher(endpoint, {
54
+ method: "POST",
55
+ signal,
56
+ redirect: "error",
57
+ headers: {
58
+ ...headersObject(authHeaders),
59
+ "content-type": "application/a2a+json",
60
+ accept: "application/a2a+json",
61
+ "a2a-version": "1.0",
62
+ },
63
+ body,
64
+ });
49
65
  if (!response.ok)
50
66
  throw new A2AError("A2A remote request failed", response.status, "ERR_PRISM_A2A_REMOTE");
51
67
  const rpc = parseRpcResponse(await readBoundedJson(response, limits.maxResponseBytes, signal), id);
@@ -54,25 +70,37 @@ export function createA2AClient(options) {
54
70
  return taskResult(parseTaskResult(rpc.result), options);
55
71
  });
56
72
  }
57
- async function* stream(input, call = {}) {
73
+ async function* streamMessage(message, call = {}) {
58
74
  if (active >= limits.maxConcurrentRequests)
59
75
  throw new A2AError("A2A client concurrency exceeded", 429, "ERR_PRISM_A2A_CONCURRENCY");
60
76
  active += 1;
61
77
  const owned = ownedSignal(call.signal, limits.timeoutMs);
62
78
  let reader;
63
79
  try {
64
- assertInput(input, limits.maxRequestBytes);
80
+ assertMessage(message, limits.maxRequestBytes);
65
81
  await getCardWithin(owned.signal);
66
82
  const id = ++requestId;
67
- const body = JSON.stringify(requestBody(id, "SendStreamingMessage", input));
83
+ const body = JSON.stringify({ jsonrpc: "2.0", id, method: "SendStreamingMessage", params: { message } });
68
84
  if (new TextEncoder().encode(body).byteLength > limits.maxRequestBytes)
69
85
  throw new A2AError("A2A request exceeds max bytes", 413, "ERR_PRISM_A2A_REQUEST_LIMIT");
70
86
  const authHeaders = await abortable(Promise.resolve(options.authorize?.({ endpoint: endpoint.href, signal: owned.signal }) ?? {}), owned.signal);
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 });
87
+ const response = await fetcher(endpoint, {
88
+ method: "POST",
89
+ signal: owned.signal,
90
+ redirect: "error",
91
+ headers: {
92
+ ...headersObject(authHeaders),
93
+ "content-type": "application/a2a+json",
94
+ accept: "text/event-stream",
95
+ "a2a-version": "1.0",
96
+ },
97
+ body,
98
+ });
72
99
  if (!response.ok || !response.body || !response.headers.get("content-type")?.startsWith("text/event-stream"))
73
100
  throw new A2AError("A2A stream request failed", response.status, "ERR_PRISM_A2A_REMOTE");
74
101
  reader = response.body.getReader();
75
102
  let terminal = false;
103
+ let count = 0;
76
104
  for await (const data of readA2AStreamData(reader, limits, owned.signal)) {
77
105
  if (terminal)
78
106
  throw new A2AError("A2A stream continued after terminal task state", 502, "ERR_PRISM_A2A_REMOTE");
@@ -88,17 +116,17 @@ export function createA2AClient(options) {
88
116
  const rpc = parseRpcResponse(parsed, id);
89
117
  if (rpc.error)
90
118
  throw new A2AError(safeRemote(rpc.error.message, options), 502, "ERR_PRISM_A2A_REMOTE");
91
- const task = parseTaskResult(rpc.result);
92
- if (task.status.state === "TASK_STATE_FAILED" || task.status.state === "TASK_STATE_CANCELED" || task.status.state === "TASK_STATE_REJECTED")
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");
96
- if (task.status.state === "TASK_STATE_COMPLETED")
119
+ const event = parseA2AStreamEvent(rpc.result, `stream-${++count}`);
120
+ const status = eventStatus(event);
121
+ if (status === "TASK_STATE_FAILED" ||
122
+ status === "TASK_STATE_CANCELED" ||
123
+ status === "TASK_STATE_REJECTED" ||
124
+ status === "TASK_STATE_INPUT_REQUIRED" ||
125
+ status === "TASK_STATE_AUTH_REQUIRED")
126
+ terminal = true;
127
+ if (status === "TASK_STATE_COMPLETED")
97
128
  terminal = true;
98
- for (const artifact of task.artifacts ?? [])
99
- for (const part of artifact.parts)
100
- if (typeof part.text === "string")
101
- yield options.redactor?.redact(part.text) ?? part.text;
129
+ yield event;
102
130
  }
103
131
  if (!terminal)
104
132
  throw new A2AError("A2A stream ended before terminal task state", 502, "ERR_PRISM_A2A_REMOTE");
@@ -109,8 +137,25 @@ export function createA2AClient(options) {
109
137
  active -= 1;
110
138
  }
111
139
  }
140
+ async function* stream(input, call = {}) {
141
+ assertInput(input, limits.maxRequestBytes);
142
+ for await (const event of streamMessage({ role: "user", messageId: "stream-input", parts: [{ text: input }] }, call)) {
143
+ const status = eventStatus(event);
144
+ if (status === "TASK_STATE_FAILED" || status === "TASK_STATE_CANCELED" || status === "TASK_STATE_REJECTED")
145
+ throw new A2AError("Remote A2A stream task failed", 502, "ERR_PRISM_A2A_REMOTE");
146
+ if (status === "TASK_STATE_INPUT_REQUIRED" || status === "TASK_STATE_AUTH_REQUIRED")
147
+ throw new A2AError(`Remote A2A task interrupted: ${status}`, 409, "ERR_PRISM_A2A_INTERRUPTED");
148
+ for (const text of streamEventText(event))
149
+ yield options.redactor?.redact(text) ?? text;
150
+ }
151
+ }
112
152
  async function getCardWithin(signal) {
113
- const response = await fetcher(cardUrl, { method: "GET", signal, redirect: "error", headers: { accept: "application/a2a+json, application/json" } });
153
+ const response = await fetcher(cardUrl, {
154
+ method: "GET",
155
+ signal,
156
+ redirect: "error",
157
+ headers: { accept: "application/a2a+json, application/json" },
158
+ });
114
159
  if (!response.ok)
115
160
  throw new A2AError("A2A card request failed", response.status, "ERR_PRISM_A2A_REMOTE");
116
161
  const card = parseCard(await readBoundedJson(response, limits.maxCardBytes, signal));
@@ -128,7 +173,18 @@ export function createA2AClient(options) {
128
173
  if (Buffer.byteLength(body) > limits.maxRequestBytes)
129
174
  throw new A2AError("A2A request exceeds max bytes", 413, "ERR_PRISM_A2A_REQUEST_LIMIT");
130
175
  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 });
176
+ const response = await fetcher(endpoint, {
177
+ method: "POST",
178
+ signal: owned,
179
+ redirect: "error",
180
+ headers: {
181
+ ...headersObject(authHeaders),
182
+ "content-type": "application/a2a+json",
183
+ accept: "application/a2a+json",
184
+ "a2a-version": "1.0",
185
+ },
186
+ body,
187
+ });
132
188
  if (!response.ok)
133
189
  throw new A2AError("A2A remote request failed", response.status, "ERR_PRISM_A2A_REMOTE");
134
190
  const rpc = parseRpcResponse(await readBoundedJson(response, limits.maxResponseBytes, owned), id);
@@ -140,14 +196,22 @@ export function createA2AClient(options) {
140
196
  async function sendMessage(message, call = {}) {
141
197
  return parseTaskResult(await invoke("SendMessage", { message, configuration: { returnImmediately: call.returnImmediately ?? false } }, call.signal));
142
198
  }
143
- async function getTask(id, call = {}) { return parseTaskResult(await invoke("GetTask", { id, historyLength: call.historyLength ?? 0 }, call.signal)); }
199
+ async function getTask(id, call = {}) {
200
+ return parseTaskResult(await invoke("GetTask", { id, historyLength: call.historyLength ?? 0 }, call.signal));
201
+ }
144
202
  async function listTasks(call = {}) {
145
203
  const value = await invoke("ListTasks", { pageSize: call.pageSize ?? 50, pageToken: call.pageToken, contextId: call.contextId }, call.signal);
146
204
  if (!isRecord(value) || !Array.isArray(value.tasks) || value.tasks.length > limits.maxPageSize)
147
205
  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 };
206
+ return {
207
+ tasks: value.tasks.map((task) => parseTaskResult(task)),
208
+ nextPageToken: typeof value.nextPageToken === "string" ? value.nextPageToken : undefined,
209
+ totalSize: typeof value.totalSize === "number" ? value.totalSize : undefined,
210
+ };
211
+ }
212
+ async function cancelTask(id, call = {}) {
213
+ return parseTaskResult(await invoke("CancelTask", { id }, call.signal));
149
214
  }
150
- async function cancelTask(id, call = {}) { return parseTaskResult(await invoke("CancelTask", { id }, call.signal)); }
151
215
  async function* subscribeToTask(id, call = {}) {
152
216
  if (active >= limits.maxConcurrentRequests)
153
217
  throw new A2AError("A2A client concurrency exceeded", 429, "ERR_PRISM_A2A_CONCURRENCY");
@@ -158,19 +222,31 @@ export function createA2AClient(options) {
158
222
  await getCardWithin(owned.signal);
159
223
  const request = ++requestId;
160
224
  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 } }) });
225
+ const response = await fetcher(endpoint, {
226
+ method: "POST",
227
+ signal: owned.signal,
228
+ redirect: "error",
229
+ headers: {
230
+ ...headersObject(authHeaders),
231
+ "content-type": "application/a2a+json",
232
+ accept: "text/event-stream",
233
+ "a2a-version": "1.0",
234
+ },
235
+ body: JSON.stringify({ jsonrpc: "2.0", id: request, method: "SubscribeToTask", params: { id, afterEventId: call.afterEventId } }),
236
+ });
162
237
  if (!response.ok || !response.body || !response.headers.get("content-type")?.startsWith("text/event-stream"))
163
238
  throw new A2AError("A2A subscribe request failed", response.status, "ERR_PRISM_A2A_REMOTE");
164
239
  reader = response.body.getReader();
165
- let previous = "", count = 0;
240
+ const seen = new Set();
241
+ let count = 0;
166
242
  for await (const data of readA2AStreamData(reader, limits, owned.signal)) {
167
243
  const rpc = parseRpcResponse(JSON.parse(data), request);
168
244
  if (rpc.error)
169
245
  throw remoteProtocolError(rpc.error.code, rpc.error.message, options);
170
246
  const event = parseTaskEvent(rpc.result);
171
- if (event.eventId === previous)
247
+ if (seen.has(event.eventId))
172
248
  continue;
173
- previous = event.eventId;
249
+ seen.add(event.eventId);
174
250
  if (++count > limits.maxReplayEvents)
175
251
  throw new A2AError("A2A replay exceeds event limit", 507, "ERR_PRISM_A2A_STREAM_LIMIT");
176
252
  yield event;
@@ -182,12 +258,39 @@ export function createA2AClient(options) {
182
258
  active -= 1;
183
259
  }
184
260
  }
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 };
261
+ async function createPushConfig(config, call = {}) {
262
+ return parsePushConfig(await invoke("CreateTaskPushNotificationConfig", { ...config }, call.signal));
263
+ }
264
+ async function getPushConfig(taskId, id, call = {}) {
265
+ return parsePushConfig(await invoke("GetTaskPushNotificationConfig", { taskId, id }, call.signal));
266
+ }
267
+ async function listPushConfigs(taskId, call = {}) {
268
+ const value = await invoke("ListTaskPushNotificationConfigs", { taskId, pageSize: call.pageSize, pageToken: call.pageToken }, call.signal);
269
+ if (!isRecord(value) || !Array.isArray(value.configs))
270
+ throw new A2AError("Malformed A2A push config page", 502, "ERR_PRISM_A2A_REMOTE");
271
+ return {
272
+ configs: value.configs.map(parsePushConfig),
273
+ nextPageToken: typeof value.nextPageToken === "string" ? value.nextPageToken : undefined,
274
+ };
275
+ }
276
+ async function deletePushConfig(taskId, id, call = {}) {
277
+ await invoke("DeleteTaskPushNotificationConfig", { taskId, id }, call.signal);
278
+ }
279
+ return {
280
+ getCard,
281
+ send,
282
+ sendMessage,
283
+ stream,
284
+ streamMessage,
285
+ getTask,
286
+ listTasks,
287
+ cancelTask,
288
+ subscribeToTask,
289
+ createPushConfig,
290
+ getPushConfig,
291
+ listPushConfigs,
292
+ deletePushConfig,
293
+ };
191
294
  }
192
295
  async function* readA2AStreamData(reader, limits, signal) {
193
296
  const decoder = new TextDecoder("utf-8", { fatal: true });
@@ -295,12 +398,21 @@ function taskResult(task, options) {
295
398
  throw new A2AError("A2A response task is not terminal", 502, "ERR_PRISM_A2A_REMOTE");
296
399
  if (task.status.state === "TASK_STATE_INPUT_REQUIRED" || task.status.state === "TASK_STATE_AUTH_REQUIRED")
297
400
  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("");
401
+ const text = (task.artifacts ?? []).flatMap((artifact) => artifact.parts.flatMap((part) => ("text" in part ? [part.text] : []))).join("");
299
402
  const safeText = options.redactor?.redact(text) ?? text;
300
403
  const status = task.status.state === "TASK_STATE_COMPLETED" ? "succeeded" : task.status.state === "TASK_STATE_CANCELED" ? "aborted" : "failed";
301
404
  const content = safeText ? [{ type: "text", text: safeText }] : [];
302
405
  const message = safeText ? { role: "assistant", content } : undefined;
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 });
406
+ return Object.freeze({
407
+ sessionId: task.contextId,
408
+ runId: task.id,
409
+ status,
410
+ text: safeText,
411
+ content,
412
+ message,
413
+ error: status === "failed" ? { message: "Remote A2A task failed" } : undefined,
414
+ abortReason: status === "aborted" ? "Remote A2A task canceled" : undefined,
415
+ });
304
416
  }
305
417
  function parseTaskResult(value) {
306
418
  if (!isRecord(value))
@@ -308,12 +420,37 @@ function parseTaskResult(value) {
308
420
  const task = isRecord(value.task) ? value.task : value;
309
421
  if (typeof task.id !== "string" || typeof task.contextId !== "string" || !isRecord(task.status) || typeof task.status.state !== "string")
310
422
  throw new A2AError("Malformed A2A task", 502, "ERR_PRISM_A2A_REMOTE");
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"]);
423
+ const states = new Set([
424
+ "TASK_STATE_SUBMITTED",
425
+ "TASK_STATE_WORKING",
426
+ "TASK_STATE_COMPLETED",
427
+ "TASK_STATE_FAILED",
428
+ "TASK_STATE_CANCELED",
429
+ "TASK_STATE_INPUT_REQUIRED",
430
+ "TASK_STATE_REJECTED",
431
+ "TASK_STATE_AUTH_REQUIRED",
432
+ ]);
312
433
  if (!states.has(task.status.state))
313
434
  throw new A2AError("Unknown A2A task state", 502, "ERR_PRISM_A2A_REMOTE");
314
435
  const artifacts = task.artifacts === undefined ? undefined : parseArtifacts(task.artifacts);
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 };
436
+ const history = task.history === undefined
437
+ ? undefined
438
+ : Array.isArray(task.history)
439
+ ? task.history.map(parseRemoteMessage)
440
+ : (() => {
441
+ throw new A2AError("Malformed A2A task history", 502, "ERR_PRISM_A2A_REMOTE");
442
+ })();
443
+ return {
444
+ id: task.id,
445
+ contextId: task.contextId,
446
+ status: {
447
+ state: task.status.state,
448
+ timestamp: typeof task.status.timestamp === "string" ? task.status.timestamp : new Date(0).toISOString(),
449
+ ...(task.status.message === undefined ? {} : { message: parseRemoteMessage(task.status.message) }),
450
+ },
451
+ artifacts,
452
+ history,
453
+ };
317
454
  }
318
455
  function parseArtifacts(value) {
319
456
  if (!Array.isArray(value) || value.length > 32)
@@ -330,10 +467,15 @@ function parseRemotePart(value) {
330
467
  const keys = ["text", "raw", "url", "data"].filter((key) => Object.hasOwn(value, key));
331
468
  if (keys.length !== 1)
332
469
  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 };
470
+ const base = {
471
+ mediaType: typeof value.mediaType === "string" ? value.mediaType : undefined,
472
+ filename: typeof value.filename === "string" ? value.filename : undefined,
473
+ };
334
474
  if (keys[0] === "text" && typeof value.text === "string")
335
475
  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))
476
+ if (keys[0] === "raw" &&
477
+ typeof value.raw === "string" &&
478
+ /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(value.raw))
337
479
  return { ...base, raw: value.raw };
338
480
  if (keys[0] === "url" && typeof value.url === "string") {
339
481
  const url = new URL(value.url);
@@ -345,25 +487,99 @@ function parseRemotePart(value) {
345
487
  return { ...base, data: structuredClone(value.data) };
346
488
  throw new A2AError("Malformed A2A part", 502, "ERR_PRISM_A2A_REMOTE");
347
489
  }
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 }; }
490
+ function parseRemoteMessage(value) {
491
+ if (!isRecord(value) ||
492
+ typeof value.messageId !== "string" ||
493
+ !Array.isArray(value.parts) ||
494
+ (value.role !== "ROLE_USER" && value.role !== "ROLE_AGENT" && value.role !== "user" && value.role !== "agent"))
495
+ throw new A2AError("Malformed A2A message", 502, "ERR_PRISM_A2A_REMOTE");
496
+ return {
497
+ role: value.role,
498
+ messageId: value.messageId,
499
+ parts: value.parts.map(parseRemotePart),
500
+ contextId: typeof value.contextId === "string" ? value.contextId : undefined,
501
+ taskId: typeof value.taskId === "string" ? value.taskId : undefined,
502
+ };
503
+ }
504
+ function parseA2AStreamEvent(value, fallbackEventId) {
505
+ if (isRecord(value) && isRecord(value.message))
506
+ return { eventId: fallbackEventId, message: parseRemoteMessage(value.message) };
507
+ if (isRecord(value) && !Object.hasOwn(value, "eventId") && (isRecord(value.task) || typeof value.id === "string")) {
508
+ return { eventId: fallbackEventId, task: parseTaskResult(value) };
509
+ }
510
+ return parseTaskEvent(value);
511
+ }
512
+ function eventStatus(event) {
513
+ if ("task" in event)
514
+ return event.task.status.state;
515
+ if ("statusUpdate" in event)
516
+ return event.statusUpdate.status.state;
517
+ return undefined;
518
+ }
519
+ function streamEventText(event) {
520
+ const parts = "message" in event
521
+ ? event.message.parts
522
+ : "task" in event
523
+ ? (event.task.artifacts?.flatMap((artifact) => artifact.parts) ?? [])
524
+ : "artifactUpdate" in event
525
+ ? event.artifactUpdate.artifact.parts
526
+ : [];
527
+ return parts.flatMap((part) => (typeof part.text === "string" ? [part.text] : []));
528
+ }
350
529
  function parseTaskEvent(value) {
351
530
  if (!isRecord(value) || typeof value.eventId !== "string" || !value.eventId)
352
531
  throw new A2AError("Malformed A2A task event", 502, "ERR_PRISM_A2A_REMOTE");
353
532
  if (isRecord(value.task))
354
533
  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 });
534
+ if (isRecord(value.statusUpdate) &&
535
+ typeof value.statusUpdate.taskId === "string" &&
536
+ typeof value.statusUpdate.contextId === "string" &&
537
+ isRecord(value.statusUpdate.status)) {
538
+ const parsed = parseTaskResult({
539
+ id: value.statusUpdate.taskId,
540
+ contextId: value.statusUpdate.contextId,
541
+ status: value.statusUpdate.status,
542
+ });
357
543
  return { eventId: value.eventId, statusUpdate: { taskId: parsed.id, contextId: parsed.contextId, status: parsed.status } };
358
544
  }
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 } };
545
+ if (isRecord(value.artifactUpdate) &&
546
+ typeof value.artifactUpdate.taskId === "string" &&
547
+ typeof value.artifactUpdate.contextId === "string" &&
548
+ isRecord(value.artifactUpdate.artifact))
549
+ return {
550
+ eventId: value.eventId,
551
+ artifactUpdate: {
552
+ taskId: value.artifactUpdate.taskId,
553
+ contextId: value.artifactUpdate.contextId,
554
+ artifact: parseArtifacts([value.artifactUpdate.artifact])[0],
555
+ append: value.artifactUpdate.append === true,
556
+ lastChunk: value.artifactUpdate.lastChunk === true,
557
+ },
558
+ };
361
559
  throw new A2AError("Malformed A2A task event", 502, "ERR_PRISM_A2A_REMOTE");
362
560
  }
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"); }
561
+ function parsePushConfig(value) {
562
+ if (!isRecord(value) || typeof value.id !== "string" || typeof value.taskId !== "string" || typeof value.url !== "string")
563
+ throw new A2AError("Malformed A2A push config", 502, "ERR_PRISM_A2A_REMOTE");
564
+ const url = new URL(value.url);
565
+ if (url.protocol !== "https:" || url.username || url.password || url.hash)
566
+ throw new A2AError("Unsafe A2A push URL", 502, "ERR_PRISM_A2A_REMOTE");
567
+ return {
568
+ id: value.id,
569
+ taskId: value.taskId,
570
+ url: url.href,
571
+ token: typeof value.token === "string" ? value.token : undefined,
572
+ authentication: isRecord(value.authentication) && typeof value.authentication.scheme === "string"
573
+ ? {
574
+ scheme: value.authentication.scheme,
575
+ credentials: typeof value.authentication.credentials === "string" ? value.authentication.credentials : undefined,
576
+ }
577
+ : undefined,
578
+ };
579
+ }
580
+ function remoteProtocolError(code, message, options) {
581
+ 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");
582
+ }
367
583
  function parseRpcResponse(value, id) {
368
584
  if (!isRecord(value) || value.jsonrpc !== "2.0" || value.id !== id)
369
585
  throw new A2AError("Malformed A2A JSON-RPC response", 502, "ERR_PRISM_A2A_REMOTE");
@@ -373,7 +589,16 @@ function parseRpcResponse(value, id) {
373
589
  return { jsonrpc: "2.0", id, result: value.result, error: error };
374
590
  }
375
591
  function parseCard(value) {
376
- if (!isRecord(value) || typeof value.name !== "string" || typeof value.description !== "string" || typeof value.version !== "string" || !Array.isArray(value.supportedInterfaces) || !Array.isArray(value.skills) || !stringArray(value.defaultInputModes) || !stringArray(value.defaultOutputModes) || !isRecord(value.capabilities) || typeof value.capabilities.streaming !== "boolean")
592
+ if (!isRecord(value) ||
593
+ typeof value.name !== "string" ||
594
+ typeof value.description !== "string" ||
595
+ typeof value.version !== "string" ||
596
+ !Array.isArray(value.supportedInterfaces) ||
597
+ !Array.isArray(value.skills) ||
598
+ !stringArray(value.defaultInputModes) ||
599
+ !stringArray(value.defaultOutputModes) ||
600
+ !isRecord(value.capabilities) ||
601
+ typeof value.capabilities.streaming !== "boolean")
377
602
  throw new A2AError("Malformed A2A agent card", 502, "ERR_PRISM_A2A_CARD");
378
603
  const supportedInterfaces = value.supportedInterfaces.map((item) => {
379
604
  if (!isRecord(item) || typeof item.url !== "string" || item.protocolBinding !== "JSONRPC" || item.protocolVersion !== "1.0")
@@ -381,21 +606,47 @@ function parseCard(value) {
381
606
  return { url: item.url, protocolBinding: "JSONRPC", protocolVersion: "1.0" };
382
607
  });
383
608
  const skills = value.skills.map((skill) => {
384
- if (!isRecord(skill) || typeof skill.id !== "string" || typeof skill.name !== "string" || typeof skill.description !== "string" || !stringArray(skill.tags))
609
+ if (!isRecord(skill) ||
610
+ typeof skill.id !== "string" ||
611
+ typeof skill.name !== "string" ||
612
+ typeof skill.description !== "string" ||
613
+ !stringArray(skill.tags))
385
614
  throw new A2AError("Malformed A2A agent skill", 502, "ERR_PRISM_A2A_CARD");
386
- return { id: skill.id, name: skill.name, description: skill.description, tags: skill.tags, examples: stringArray(skill.examples) ? skill.examples : undefined, inputModes: stringArray(skill.inputModes) ? skill.inputModes : undefined, outputModes: stringArray(skill.outputModes) ? skill.outputModes : undefined };
615
+ return {
616
+ id: skill.id,
617
+ name: skill.name,
618
+ description: skill.description,
619
+ tags: skill.tags,
620
+ examples: stringArray(skill.examples) ? skill.examples : undefined,
621
+ inputModes: stringArray(skill.inputModes) ? skill.inputModes : undefined,
622
+ outputModes: stringArray(skill.outputModes) ? skill.outputModes : undefined,
623
+ };
387
624
  });
388
- const signatures = value.signatures === undefined ? undefined : Array.isArray(value.signatures) ? value.signatures.map((signature) => {
389
- if (!isRecord(signature) || typeof signature.protected !== "string" || typeof signature.signature !== "string")
390
- throw new A2AError("Malformed A2A card signature", 502, "ERR_PRISM_A2A_CARD");
391
- return { protected: signature.protected, signature: signature.signature, header: isRecord(signature.header) ? signature.header : undefined };
392
- }) : (() => { throw new A2AError("Malformed A2A card signatures", 502, "ERR_PRISM_A2A_CARD"); })();
625
+ const signatures = value.signatures === undefined
626
+ ? undefined
627
+ : Array.isArray(value.signatures)
628
+ ? value.signatures.map((signature) => {
629
+ if (!isRecord(signature) || typeof signature.protected !== "string" || typeof signature.signature !== "string")
630
+ throw new A2AError("Malformed A2A card signature", 502, "ERR_PRISM_A2A_CARD");
631
+ return {
632
+ protected: signature.protected,
633
+ signature: signature.signature,
634
+ header: isRecord(signature.header) ? signature.header : undefined,
635
+ };
636
+ })
637
+ : (() => {
638
+ throw new A2AError("Malformed A2A card signatures", 502, "ERR_PRISM_A2A_CARD");
639
+ })();
393
640
  return createA2AAgentCard({
394
641
  name: value.name,
395
642
  description: value.description,
396
643
  version: value.version,
397
644
  supportedInterfaces,
398
- capabilities: { streaming: value.capabilities.streaming, pushNotifications: typeof value.capabilities.pushNotifications === "boolean" ? value.capabilities.pushNotifications : undefined, extendedAgentCard: typeof value.capabilities.extendedAgentCard === "boolean" ? value.capabilities.extendedAgentCard : undefined },
645
+ capabilities: {
646
+ streaming: value.capabilities.streaming,
647
+ pushNotifications: typeof value.capabilities.pushNotifications === "boolean" ? value.capabilities.pushNotifications : undefined,
648
+ extendedAgentCard: typeof value.capabilities.extendedAgentCard === "boolean" ? value.capabilities.extendedAgentCard : undefined,
649
+ },
399
650
  defaultInputModes: value.defaultInputModes,
400
651
  defaultOutputModes: value.defaultOutputModes,
401
652
  skills,
@@ -455,7 +706,13 @@ function ownedSignal(parent, timeoutMs) {
455
706
  else
456
707
  parent?.addEventListener("abort", abort, { once: true });
457
708
  const timer = setTimeout(() => controller.abort(new DOMException("A2A request timed out", "AbortError")), timeoutMs);
458
- return { signal: controller.signal, dispose: () => { clearTimeout(timer); parent?.removeEventListener("abort", abort); } };
709
+ return {
710
+ signal: controller.signal,
711
+ dispose: () => {
712
+ clearTimeout(timer);
713
+ parent?.removeEventListener("abort", abort);
714
+ },
715
+ };
459
716
  }
460
717
  function abortable(promise, signal) {
461
718
  if (signal.aborted)
@@ -466,11 +723,32 @@ function abortable(promise, signal) {
466
723
  promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
467
724
  });
468
725
  }
469
- function assertInput(input, maxBytes) { if (new TextEncoder().encode(input).byteLength > maxBytes)
470
- throw new A2AError("A2A input exceeds max bytes", 413, "ERR_PRISM_A2A_REQUEST_LIMIT"); }
471
- function headersObject(headers) { return Object.fromEntries(new Headers(headers).entries()); }
472
- function isRecord(value) { return !!value && typeof value === "object" && !Array.isArray(value); }
473
- function stringArray(value) { return Array.isArray(value) && value.every((item) => typeof item === "string"); }
726
+ function assertInput(input, maxBytes) {
727
+ if (new TextEncoder().encode(input).byteLength > maxBytes)
728
+ throw new A2AError("A2A input exceeds max bytes", 413, "ERR_PRISM_A2A_REQUEST_LIMIT");
729
+ }
730
+ function assertMessage(message, maxBytes) {
731
+ if (!message.messageId || !Array.isArray(message.parts) || message.parts.length === 0)
732
+ throw new A2AError("Invalid A2A outbound message", 400, "ERR_PRISM_A2A_MESSAGE");
733
+ let encoded;
734
+ try {
735
+ encoded = JSON.stringify(message);
736
+ }
737
+ catch {
738
+ throw new A2AError("Invalid A2A outbound message", 400, "ERR_PRISM_A2A_MESSAGE");
739
+ }
740
+ if (Buffer.byteLength(encoded, "utf8") > maxBytes)
741
+ throw new A2AError("A2A input exceeds max bytes", 413, "ERR_PRISM_A2A_REQUEST_LIMIT");
742
+ }
743
+ function headersObject(headers) {
744
+ return Object.fromEntries(new Headers(headers).entries());
745
+ }
746
+ function isRecord(value) {
747
+ return !!value && typeof value === "object" && !Array.isArray(value);
748
+ }
749
+ function stringArray(value) {
750
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
751
+ }
474
752
  function parseSecurity(value) {
475
753
  if (value === undefined)
476
754
  return undefined;
@@ -482,5 +760,7 @@ function parseSecurity(value) {
482
760
  return entry;
483
761
  });
484
762
  }
485
- function safeRemote(message, options) { return options.redactor?.redact(message.slice(0, 1024)) ?? message.slice(0, 1024); }
763
+ function safeRemote(message, options) {
764
+ return options.redactor?.redact(message.slice(0, 1024)) ?? message.slice(0, 1024);
765
+ }
486
766
  //# sourceMappingURL=a2a-client.js.map
@@ -0,0 +1,42 @@
1
+ import type { AgentEventSource, AgentRunRef, DurableAgentEventRecord } from "@arnilo/prism";
2
+ import type { A2AAuthorization, A2ATask, A2ATaskEvent } from "./a2a-types.js";
3
+ export type A2ATaskEventPayload = {
4
+ readonly task: A2ATask;
5
+ } | {
6
+ readonly statusUpdate: Extract<A2ATaskEvent, {
7
+ readonly statusUpdate: unknown;
8
+ }>["statusUpdate"];
9
+ } | {
10
+ readonly artifactUpdate: Extract<A2ATaskEvent, {
11
+ readonly artifactUpdate: unknown;
12
+ }>["artifactUpdate"];
13
+ };
14
+ export interface A2AAgentEventTask {
15
+ readonly task: A2ATask;
16
+ readonly run: AgentRunRef;
17
+ }
18
+ export interface A2AAgentEventSourceOptions {
19
+ readonly source: AgentEventSource;
20
+ /** Resolves only host-owned task state and its exact Prism run. */
21
+ readonly resolveTask: (input: {
22
+ readonly id: string;
23
+ readonly authorization: A2AAuthorization;
24
+ readonly signal: AbortSignal;
25
+ }) => A2AAgentEventTask | undefined | Promise<A2AAgentEventTask | undefined>;
26
+ /** Maps at most one durable Prism record to one A2A update. Event IDs stay source-owned cursors. */
27
+ readonly map: (input: {
28
+ readonly record: DurableAgentEventRecord;
29
+ readonly task: A2ATask;
30
+ readonly authorization: A2AAuthorization;
31
+ }) => A2ATaskEventPayload | undefined | Promise<A2ATaskEventPayload | undefined>;
32
+ }
33
+ export interface A2AAgentEventSource {
34
+ subscribe(input: {
35
+ readonly id: string;
36
+ readonly afterEventId?: string;
37
+ readonly authorization: A2AAuthorization;
38
+ readonly signal: AbortSignal;
39
+ }): AsyncIterable<A2ATaskEvent>;
40
+ }
41
+ /** Host-selected durable task stream over AgentEventSource; owns no task database or worker. */
42
+ export declare function createA2AAgentEventSource(options: A2AAgentEventSourceOptions): A2AAgentEventSource;