@arnilo/prism-supervisor 0.0.96 → 0.1.1

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.
@@ -0,0 +1,40 @@
1
+ import { A2AError } from "./errors.js";
2
+ /** Host-selected durable task stream over AgentEventSource; owns no task database or worker. */
3
+ export function createA2AAgentEventSource(options) {
4
+ return {
5
+ subscribe(input) {
6
+ return {
7
+ async *[Symbol.asyncIterator]() {
8
+ input.signal.throwIfAborted();
9
+ const resolved = await options.resolveTask(input);
10
+ if (!resolved?.run.sessionId || resolved.task.id !== input.id) {
11
+ throw new A2AError("Task unavailable", 404, "ERR_PRISM_A2A_TASK");
12
+ }
13
+ let first = true;
14
+ for await (const item of options.source.subscribe({
15
+ ownership: input.authorization.ownership,
16
+ sessionId: resolved.run.sessionId,
17
+ runId: resolved.run.runId,
18
+ after: input.afterEventId,
19
+ signal: input.signal,
20
+ })) {
21
+ if (!item.record.redacted)
22
+ throw new A2AError("Task event unavailable", 500, "ERR_PRISM_A2A_TASK");
23
+ const payload = await options.map({ record: item.record, task: resolved.task, authorization: input.authorization });
24
+ if (!payload)
25
+ continue;
26
+ if (first && input.afterEventId === undefined && !("task" in payload)) {
27
+ throw new A2AError("Initial task event required", 500, "ERR_PRISM_A2A_TASK");
28
+ }
29
+ first = false;
30
+ yield { eventId: item.cursor, ...payload };
31
+ }
32
+ if (first && input.afterEventId === undefined) {
33
+ throw new A2AError("Initial task event required", 500, "ERR_PRISM_A2A_TASK");
34
+ }
35
+ },
36
+ };
37
+ },
38
+ };
39
+ }
40
+ //# sourceMappingURL=a2a-event-source.js.map
package/dist/a2a-parts.js CHANGED
@@ -1,6 +1,46 @@
1
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 };
2
+ export const A2A_DEFAULT_LIMITS = {
3
+ maxRequestBytes: 64 * 1024,
4
+ maxResponseBytes: 1024 * 1024,
5
+ maxEventBytes: 64 * 1024,
6
+ maxStreamBytes: 10 * 1024 * 1024,
7
+ maxStreamEvents: 10_000,
8
+ maxConcurrentRequests: 16,
9
+ timeoutMs: 120_000,
10
+ maxCardBytes: 64 * 1024,
11
+ maxIdBytes: 256,
12
+ maxParts: 32,
13
+ maxPartBytes: 1024 * 1024,
14
+ maxRawBytes: 1024 * 1024,
15
+ maxDataBytes: 256 * 1024,
16
+ maxArtifacts: 32,
17
+ maxHistory: 100,
18
+ maxPageSize: 100,
19
+ maxCursorBytes: 4096,
20
+ maxReplayEvents: 1000,
21
+ maxPushConfigs: 10,
22
+ };
23
+ export const A2A_HARD_LIMITS = {
24
+ maxRequestBytes: 1024 * 1024,
25
+ maxResponseBytes: 8 * 1024 * 1024,
26
+ maxEventBytes: 1024 * 1024,
27
+ maxStreamBytes: 64 * 1024 * 1024,
28
+ maxStreamEvents: 100_000,
29
+ maxConcurrentRequests: 256,
30
+ timeoutMs: 30 * 60_000,
31
+ maxCardBytes: 1024 * 1024,
32
+ maxIdBytes: 4096,
33
+ maxParts: 256,
34
+ maxPartBytes: 8 * 1024 * 1024,
35
+ maxRawBytes: 8 * 1024 * 1024,
36
+ maxDataBytes: 4 * 1024 * 1024,
37
+ maxArtifacts: 256,
38
+ maxHistory: 1000,
39
+ maxPageSize: 1000,
40
+ maxCursorBytes: 16 * 1024,
41
+ maxReplayEvents: 10_000,
42
+ maxPushConfigs: 100,
43
+ };
4
44
  export function resolveA2ALimits(input = {}) {
5
45
  const output = {};
6
46
  for (const key of Object.keys(A2A_DEFAULT_LIMITS)) {
@@ -12,12 +52,24 @@ export function resolveA2ALimits(input = {}) {
12
52
  return output;
13
53
  }
14
54
  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)
55
+ if (!record(value) ||
56
+ (value.role !== "user" && value.role !== "ROLE_USER" && value.role !== "agent" && value.role !== "ROLE_AGENT") ||
57
+ !id(value.messageId, limits) ||
58
+ !Array.isArray(value.parts) ||
59
+ value.parts.length < 1 ||
60
+ value.parts.length > limits.maxParts)
16
61
  throw new A2AError("Invalid A2A message", 400, "ERR_PRISM_A2A_MESSAGE");
17
62
  const parts = [];
18
63
  for (const part of value.parts)
19
64
  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 };
65
+ const message = {
66
+ role: value.role,
67
+ messageId: value.messageId,
68
+ parts,
69
+ contextId: optionalId(value.contextId, limits),
70
+ taskId: optionalId(value.taskId, limits),
71
+ metadata: record(value.metadata) ? value.metadata : undefined,
72
+ };
21
73
  bounded(message, limits.maxRequestBytes, "A2A message");
22
74
  return message;
23
75
  }
@@ -29,11 +81,19 @@ export async function parseA2APart(value, limits, policy = {}) {
29
81
  throw new A2AError("Unknown A2A part field", 400, "ERR_PRISM_A2A_PART");
30
82
  if (variants.length !== 1)
31
83
  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 };
84
+ const base = {
85
+ mediaType: optionalString(value.mediaType, 256),
86
+ filename: optionalString(value.filename, 1024),
87
+ metadata: record(value.metadata) ? value.metadata : undefined,
88
+ };
33
89
  let part;
34
90
  if (variants[0] === "text" && typeof value.text === "string")
35
91
  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)
92
+ else if (variants[0] === "raw" &&
93
+ policy.allowRaw &&
94
+ typeof value.raw === "string" &&
95
+ /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(value.raw) &&
96
+ Buffer.from(value.raw, "base64").byteLength <= limits.maxRawBytes)
37
97
  part = { ...base, raw: value.raw };
38
98
  else if (variants[0] === "url" && policy.allowUrl && policy.validateUrl && typeof value.url === "string") {
39
99
  let url;
@@ -58,7 +118,11 @@ export async function parseA2APart(value, limits, policy = {}) {
58
118
  return part;
59
119
  }
60
120
  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)))
121
+ if (!id(task.id, limits) ||
122
+ !id(task.contextId, limits) ||
123
+ !task.status ||
124
+ !TASK_STATES.has(task.status.state) ||
125
+ !Number.isFinite(Date.parse(task.status.timestamp)))
62
126
  throw new A2AError("Invalid A2A task", 500, "ERR_PRISM_A2A_TASK");
63
127
  if ((task.artifacts?.length ?? 0) > limits.maxArtifacts || (task.history?.length ?? 0) > limits.maxHistory)
64
128
  throw new A2AError("A2A task collection limit exceeded", 507, "ERR_PRISM_A2A_RESPONSE_LIMIT");
@@ -75,11 +139,18 @@ async function validateArtifact(value, limits, policy) {
75
139
  for (const part of value.parts)
76
140
  await parseA2APart(part, limits, policy);
77
141
  }
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; }
142
+ export function requireId(value, limits, label = "task id") {
143
+ if (!id(value, limits))
144
+ throw new A2AError(`Invalid A2A ${label}`, 400, "ERR_PRISM_A2A_REQUEST");
145
+ return value;
146
+ }
147
+ export function optionalCursor(value, limits) {
148
+ if (value === undefined || value === "")
149
+ return undefined;
150
+ if (typeof value !== "string" || Buffer.byteLength(value) > limits.maxCursorBytes)
151
+ throw new A2AError("Invalid A2A page/event cursor", 400, "ERR_PRISM_A2A_REQUEST");
152
+ return value;
153
+ }
83
154
  export function bounded(value, maxBytes, label) {
84
155
  let properties = 0;
85
156
  const stack = [{ value, depth: 0 }];
@@ -111,11 +182,30 @@ export function bounded(value, maxBytes, label) {
111
182
  throw new A2AError(`${label} exceeds max bytes`, 507, "ERR_PRISM_A2A_RESPONSE_LIMIT");
112
183
  return value;
113
184
  }
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"]);
185
+ export function record(value) {
186
+ return !!value && typeof value === "object" && !Array.isArray(value);
187
+ }
188
+ function id(value, limits) {
189
+ return typeof value === "string" && value.length > 0 && Buffer.byteLength(value) <= limits.maxIdBytes;
190
+ }
191
+ function optionalId(value, limits) {
192
+ return value === undefined ? undefined : requireId(value, limits);
193
+ }
194
+ function optionalString(value, max) {
195
+ if (value === undefined)
196
+ return undefined;
197
+ if (typeof value !== "string" || Buffer.byteLength(value) > max)
198
+ throw new A2AError("Invalid A2A part metadata", 400, "ERR_PRISM_A2A_PART");
199
+ return value;
200
+ }
201
+ const TASK_STATES = new Set([
202
+ "TASK_STATE_SUBMITTED",
203
+ "TASK_STATE_WORKING",
204
+ "TASK_STATE_COMPLETED",
205
+ "TASK_STATE_FAILED",
206
+ "TASK_STATE_CANCELED",
207
+ "TASK_STATE_INPUT_REQUIRED",
208
+ "TASK_STATE_REJECTED",
209
+ "TASK_STATE_AUTH_REQUIRED",
210
+ ]);
121
211
  //# sourceMappingURL=a2a-parts.js.map
package/dist/a2a-push.js CHANGED
@@ -4,7 +4,12 @@ import { A2AError } from "./errors.js";
4
4
  export async function deliverA2APushEvent(delivery, config, event, options = {}) {
5
5
  const limits = resolveA2ALimits(options.limits);
6
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)
7
+ if (!Number.isSafeInteger(maxAttempts) ||
8
+ maxAttempts < 1 ||
9
+ maxAttempts > 3 ||
10
+ !Number.isSafeInteger(timeoutMs) ||
11
+ timeoutMs < 1 ||
12
+ timeoutMs > 60_000)
8
13
  throw new A2AError("Invalid A2A push delivery limits", 400, "ERR_PRISM_A2A_CONFIG");
9
14
  bounded(event, limits.maxEventBytes, "A2A push event");
10
15
  if (!event.eventId || Buffer.byteLength(event.eventId) > limits.maxCursorBytes)
@@ -20,7 +25,10 @@ export async function deliverA2APushEvent(delivery, config, event, options = {})
20
25
  const timer = setTimeout(() => controller.abort(new DOMException("A2A push delivery timed out", "AbortError")), timeoutMs);
21
26
  try {
22
27
  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 }))]);
28
+ await Promise.race([
29
+ delivery.deliver({ config, event, idempotencyKey: event.eventId, attempt, signal: controller.signal }),
30
+ new Promise((_resolve, reject) => controller.signal.addEventListener("abort", () => reject(controller.signal.reason), { once: true })),
31
+ ]);
24
32
  return { attempts: attempt };
25
33
  }
26
34
  catch (error) {
@@ -1,5 +1,6 @@
1
+ import { assertIdentityActive, assertIdentityMatchesOwnership } from "@arnilo/prism";
1
2
  import { createA2AAgentCard } from "./a2a-card.js";
2
- import { bounded, optionalCursor, parseA2AMessage, record, requireId, resolveA2ALimits, validateA2ATask } from "./a2a-parts.js";
3
+ import { bounded, optionalCursor, parseA2AMessage, record, requireId, resolveA2ALimits, validateA2ATask, } from "./a2a-parts.js";
3
4
  import { A2AError } from "./errors.js";
4
5
  const JSON_HEADERS = { "content-type": "application/a2a+json; charset=utf-8", "a2a-version": "1.0" };
5
6
  const SSE_HEADERS = { "content-type": "text/event-stream; charset=utf-8", "cache-control": "no-cache, no-transform", "a2a-version": "1.0" };
@@ -39,36 +40,72 @@ export function createA2AHandler(options) {
39
40
  const authorization = await abortable(Promise.resolve(options.authorize({ request, method: rpc.method, signal: owned.signal })), owned.signal);
40
41
  if (!authorization)
41
42
  return errorResponse(403, "Forbidden", rpc.id);
43
+ if (authorization.identity) {
44
+ try {
45
+ assertIdentityActive(authorization.identity);
46
+ assertIdentityMatchesOwnership(authorization.identity, authorization.ownership);
47
+ }
48
+ catch {
49
+ return errorResponse(403, "Forbidden", rpc.id);
50
+ }
51
+ }
42
52
  if (rpc.method === "GetExtendedAgentCard")
43
- return card.capabilities.extendedAgentCard ? json(rpc.id, card, limits, options) : rpcError(rpc.id, -32004, "Extended Agent Card unavailable");
53
+ return card.capabilities.extendedAgentCard
54
+ ? json(rpc.id, card, limits, options)
55
+ : rpcError(rpc.id, -32004, "Extended Agent Card unavailable");
44
56
  if (rpc.method === "SendMessage" || rpc.method === "SendStreamingMessage") {
45
57
  const message = await parseA2AMessage(rpc.params?.message, limits, options.parts);
46
58
  if (options.tasks) {
47
- const task = await options.tasks.start({ message, authorization, signal: owned.signal, returnImmediately: record(rpc.params?.configuration) && rpc.params.configuration.returnImmediately === true });
59
+ const task = await options.tasks.start({
60
+ message,
61
+ authorization,
62
+ signal: owned.signal,
63
+ returnImmediately: record(rpc.params?.configuration) && rpc.params.configuration.returnImmediately === true,
64
+ });
48
65
  await validateA2ATask(task, limits, options.parts);
49
66
  if (rpc.method === "SendMessage")
50
67
  return json(rpc.id, { task }, limits, options);
51
68
  transferred = true;
52
- const events = terminal(task.status.state) ? oneEvent(task) : options.tasks.subscribe({ id: task.id, authorization, signal: owned.signal });
53
- return streamResponse(rpc.id, events, owned, limits, options, () => { active -= 1; });
69
+ const events = terminal(task.status.state)
70
+ ? oneEvent(task)
71
+ : options.tasks.subscribe({ id: task.id, authorization, signal: owned.signal });
72
+ return streamResponse(rpc.id, events, owned, limits, options, () => {
73
+ active -= 1;
74
+ });
54
75
  }
55
76
  if (message.parts.some((part) => !("text" in part)))
56
77
  return rpcError(rpc.id, -32004, "Durable task lifecycle required for rich parts");
57
78
  const input = message.parts.map((part) => part.text).join("\n");
58
79
  const session = await abortable(Promise.resolve(options.exposure.sessionFactory(authorization)), owned.signal);
59
80
  const taskId = `task-${crypto.randomUUID()}`, contextId = message.contextId ?? `context-${crypto.randomUUID()}`;
81
+ const runOptions = {
82
+ ownership: authorization.ownership,
83
+ identity: authorization.identity,
84
+ metadata: authorization.metadata,
85
+ signal: owned.signal,
86
+ redactor: options.redactor,
87
+ };
60
88
  if (rpc.method === "SendMessage")
61
- return json(rpc.id, { task: toTask(taskId, contextId, await abortable(session.run(input, { ownership: authorization.ownership, metadata: authorization.metadata, signal: owned.signal, redactor: options.redactor }), owned.signal), options) }, limits, options);
89
+ return json(rpc.id, { task: toTask(taskId, contextId, await abortable(session.run(input, runOptions), owned.signal), options) }, limits, options);
62
90
  transferred = true;
63
- const events = runEvents(taskId, contextId, () => session.run(input, { ownership: authorization.ownership, metadata: authorization.metadata, signal: owned.signal, redactor: options.redactor }), options);
64
- return streamResponse(rpc.id, events, owned, limits, options, () => { active -= 1; });
91
+ const events = runEvents(taskId, contextId, () => session.run(input, runOptions), options);
92
+ return streamResponse(rpc.id, events, owned, limits, options, () => {
93
+ active -= 1;
94
+ });
65
95
  }
66
96
  if (["GetTask", "ListTasks", "CancelTask", "SubscribeToTask"].includes(rpc.method)) {
67
97
  if (!options.tasks)
68
98
  return rpcError(rpc.id, -32004, "Task lifecycle unavailable");
69
99
  if (rpc.method === "GetTask") {
70
- const task = await options.tasks.get({ id: requireId(rpc.params?.id, limits), historyLength: integer(rpc.params?.historyLength, 0, limits.maxHistory), authorization, signal: owned.signal });
71
- return task ? json(rpc.id, await validateA2ATask(task, limits, options.parts), limits, options) : rpcError(rpc.id, -32001, "Task not found");
100
+ const task = await options.tasks.get({
101
+ id: requireId(rpc.params?.id, limits),
102
+ historyLength: integer(rpc.params?.historyLength, 0, limits.maxHistory),
103
+ authorization,
104
+ signal: owned.signal,
105
+ });
106
+ return task
107
+ ? json(rpc.id, await validateA2ATask(task, limits, options.parts), limits, options)
108
+ : rpcError(rpc.id, -32001, "Task not found");
72
109
  }
73
110
  if (rpc.method === "ListTasks") {
74
111
  const pageSize = integer(rpc.params?.pageSize, 50, limits.maxPageSize), pageToken = optionalCursor(rpc.params?.pageToken, limits), contextId = rpc.params?.contextId === undefined ? undefined : requireId(rpc.params.contextId, limits, "context id");
@@ -82,7 +119,9 @@ export function createA2AHandler(options) {
82
119
  }
83
120
  if (rpc.method === "CancelTask") {
84
121
  const task = await options.tasks.cancel({ id: requireId(rpc.params?.id, limits), authorization, signal: owned.signal });
85
- return task ? json(rpc.id, await validateA2ATask(task, limits, options.parts), limits, options) : rpcError(rpc.id, -32001, "Task not found");
122
+ return task
123
+ ? json(rpc.id, await validateA2ATask(task, limits, options.parts), limits, options)
124
+ : rpcError(rpc.id, -32001, "Task not found");
86
125
  }
87
126
  const taskId = requireId(rpc.params?.id, limits), afterEventId = optionalCursor(rpc.params?.afterEventId, limits);
88
127
  const current = await options.tasks.get({ id: taskId, historyLength: 0, authorization, signal: owned.signal });
@@ -91,7 +130,9 @@ export function createA2AHandler(options) {
91
130
  if (finalTerminal(current.status.state))
92
131
  return rpcError(rpc.id, -32004, "Terminal task cannot be subscribed");
93
132
  transferred = true;
94
- return streamResponse(rpc.id, options.tasks.subscribe({ id: taskId, afterEventId, authorization, signal: owned.signal }), owned, limits, options, () => { active -= 1; });
133
+ return streamResponse(rpc.id, options.tasks.subscribe({ id: taskId, afterEventId, authorization, signal: owned.signal }), owned, limits, options, () => {
134
+ active -= 1;
135
+ });
95
136
  }
96
137
  if (rpc.method.includes("TaskPushNotificationConfig"))
97
138
  return await pushOperation(rpc, authorization, owned.signal, limits, options);
@@ -135,7 +176,9 @@ async function pushOperation(rpc, authorization, signal, limits, options) {
135
176
  return json(rpc.id, { configs: page.configs.map(publicPush), nextPageToken: optionalCursor(page.nextPageToken, limits) }, limits, options);
136
177
  }
137
178
  if (rpc.method === "DeleteTaskPushNotificationConfig")
138
- return await options.push.delete({ taskId, id: id, authorization, signal }) ? json(rpc.id, {}, limits, options) : rpcError(rpc.id, -32001, "Task or push config not found");
179
+ return (await options.push.delete({ taskId, id: id, authorization, signal }))
180
+ ? json(rpc.id, {}, limits, options)
181
+ : rpcError(rpc.id, -32001, "Task or push config not found");
139
182
  return rpcError(rpc.id, -32601, "Method not found");
140
183
  }
141
184
  async function parsePush(value, limits, options) {
@@ -152,44 +195,77 @@ async function parsePush(value, limits, options) {
152
195
  if (url.protocol !== "https:" || url.username || url.password || url.hash)
153
196
  throw new A2AError("Push URL requires credential-free HTTPS", 403, "ERR_PRISM_A2A_ORIGIN");
154
197
  await options.parts.validateUrl(url);
155
- const authentication = record(value.authentication) && typeof value.authentication.scheme === "string" ? { scheme: value.authentication.scheme.slice(0, 64), credentials: typeof value.authentication.credentials === "string" ? value.authentication.credentials.slice(0, limits.maxPartBytes) : undefined } : undefined;
156
- return bounded({ id, taskId, url: url.href, token: typeof value.token === "string" ? value.token.slice(0, limits.maxPartBytes) : undefined, authentication }, limits.maxPartBytes, "Push config");
198
+ const authentication = record(value.authentication) && typeof value.authentication.scheme === "string"
199
+ ? {
200
+ scheme: value.authentication.scheme.slice(0, 64),
201
+ credentials: typeof value.authentication.credentials === "string"
202
+ ? value.authentication.credentials.slice(0, limits.maxPartBytes)
203
+ : undefined,
204
+ }
205
+ : undefined;
206
+ return bounded({
207
+ id,
208
+ taskId,
209
+ url: url.href,
210
+ token: typeof value.token === "string" ? value.token.slice(0, limits.maxPartBytes) : undefined,
211
+ authentication,
212
+ }, limits.maxPartBytes, "Push config");
213
+ }
214
+ function publicPush(value) {
215
+ return {
216
+ id: value.id,
217
+ taskId: value.taskId,
218
+ url: value.url,
219
+ authentication: value.authentication ? { scheme: value.authentication.scheme } : undefined,
220
+ };
157
221
  }
158
- function publicPush(value) { return { id: value.id, taskId: value.taskId, url: value.url, authentication: value.authentication ? { scheme: value.authentication.scheme } : undefined }; }
159
222
  function streamResponse(id, source, owned, limits, options, release) {
160
223
  const iterator = source[Symbol.asyncIterator]();
161
224
  let events = 0, bytes = 0, released = false, previous = "";
162
- const finish = () => { if (!released) {
163
- released = true;
164
- owned.dispose();
165
- release();
166
- void iterator.return?.();
167
- } };
225
+ const finish = () => {
226
+ if (!released) {
227
+ released = true;
228
+ owned.dispose();
229
+ release();
230
+ void iterator.return?.();
231
+ }
232
+ };
168
233
  return new Response(new ReadableStream({
169
- async pull(controller) { try {
170
- const next = await iterator.next();
171
- if (next.done) {
234
+ async pull(controller) {
235
+ try {
236
+ const next = await iterator.next();
237
+ if (next.done) {
238
+ finish();
239
+ controller.close();
240
+ return;
241
+ }
242
+ if (!optionalCursor(next.value.eventId, limits) || next.value.eventId === previous)
243
+ throw new A2AError("Duplicate/invalid A2A event id", 500, "ERR_PRISM_A2A_STREAM_LIMIT");
244
+ await validateTaskEvent(next.value, limits, options);
245
+ previous = next.value.eventId;
246
+ const payload = options.redactor?.redact({ jsonrpc: "2.0", id, result: next.value }) ?? {
247
+ jsonrpc: "2.0",
248
+ id,
249
+ result: next.value,
250
+ };
251
+ const chunk = new TextEncoder().encode(`id: ${next.value.eventId}\ndata: ${JSON.stringify(payload)}\n\n`);
252
+ events++;
253
+ bytes += chunk.byteLength;
254
+ if (events > Math.min(limits.maxStreamEvents, limits.maxReplayEvents) ||
255
+ chunk.byteLength > limits.maxEventBytes ||
256
+ bytes > limits.maxStreamBytes)
257
+ throw new A2AError("A2A stream limit exceeded", 507, "ERR_PRISM_A2A_STREAM_LIMIT");
258
+ controller.enqueue(chunk);
259
+ }
260
+ catch (error) {
172
261
  finish();
173
- controller.close();
174
- return;
262
+ controller.error(error);
175
263
  }
176
- if (!optionalCursor(next.value.eventId, limits) || next.value.eventId === previous)
177
- throw new A2AError("Duplicate/invalid A2A event id", 500, "ERR_PRISM_A2A_STREAM_LIMIT");
178
- await validateTaskEvent(next.value, limits, options);
179
- previous = next.value.eventId;
180
- const payload = options.redactor?.redact({ jsonrpc: "2.0", id, result: next.value }) ?? { jsonrpc: "2.0", id, result: next.value };
181
- const chunk = new TextEncoder().encode(`id: ${next.value.eventId}\ndata: ${JSON.stringify(payload)}\n\n`);
182
- events++;
183
- bytes += chunk.byteLength;
184
- if (events > Math.min(limits.maxStreamEvents, limits.maxReplayEvents) || chunk.byteLength > limits.maxEventBytes || bytes > limits.maxStreamBytes)
185
- throw new A2AError("A2A stream limit exceeded", 507, "ERR_PRISM_A2A_STREAM_LIMIT");
186
- controller.enqueue(chunk);
187
- }
188
- catch (error) {
264
+ },
265
+ cancel(reason) {
266
+ owned.abort(reason);
189
267
  finish();
190
- controller.error(error);
191
- } },
192
- cancel(reason) { owned.abort(reason); finish(); },
268
+ },
193
269
  }), { headers: SSE_HEADERS });
194
270
  }
195
271
  async function validateTaskEvent(event, limits, options) {
@@ -201,51 +277,126 @@ async function validateTaskEvent(event, limits, options) {
201
277
  await validateA2ATask({ id: event.statusUpdate.taskId, contextId: event.statusUpdate.contextId, status: event.statusUpdate.status }, limits, options.parts);
202
278
  return;
203
279
  }
204
- await validateA2ATask({ id: event.artifactUpdate.taskId, contextId: event.artifactUpdate.contextId, status: { state: "TASK_STATE_WORKING", timestamp: new Date().toISOString() }, artifacts: [event.artifactUpdate.artifact] }, limits, options.parts);
205
- }
206
- async function* oneEvent(task) { yield { eventId: `terminal-${task.id}`, task }; }
207
- async function* runEvents(taskId, contextId, run, options) { yield { eventId: "1", task: { id: taskId, contextId, status: { state: "TASK_STATE_WORKING", timestamp: new Date().toISOString() } } }; yield { eventId: "2", task: toTask(taskId, contextId, await run(), options) }; }
208
- function toTask(taskId, contextId, result, options) { const state = result.status === "succeeded" ? "TASK_STATE_COMPLETED" : result.status === "aborted" ? "TASK_STATE_CANCELED" : "TASK_STATE_FAILED"; const text = options.redactor?.redact(result.text) ?? result.text; return { id: taskId, contextId, status: { state, timestamp: new Date().toISOString() }, artifacts: text ? [{ artifactId: `${taskId}-result`, parts: [{ text }] }] : undefined }; }
209
- function parseRpc(value) { if (!record(value) || value.jsonrpc !== "2.0" || !(typeof value.id === "string" || typeof value.id === "number" || value.id === null) || typeof value.method !== "string" || (value.params !== undefined && !record(value.params)))
210
- throw new A2AError("Invalid JSON-RPC request", 400, "ERR_PRISM_A2A_REQUEST"); return { jsonrpc: "2.0", id: value.id, method: value.method, params: value.params }; }
211
- async function readJson(request, maxBytes, signal) { if (!request.body)
212
- throw new A2AError("Request body is required", 400, "ERR_PRISM_A2A_REQUEST"); const reader = request.body.getReader(), chunks = []; let size = 0; try {
213
- while (true) {
214
- signal.throwIfAborted();
215
- const n = await reader.read();
216
- if (n.done)
217
- break;
218
- size += n.value.byteLength;
219
- if (size > maxBytes)
220
- throw new A2AError("Request exceeds max bytes", 413, "ERR_PRISM_A2A_REQUEST_LIMIT");
221
- chunks.push(n.value);
280
+ await validateA2ATask({
281
+ id: event.artifactUpdate.taskId,
282
+ contextId: event.artifactUpdate.contextId,
283
+ status: { state: "TASK_STATE_WORKING", timestamp: new Date().toISOString() },
284
+ artifacts: [event.artifactUpdate.artifact],
285
+ }, limits, options.parts);
286
+ }
287
+ async function* oneEvent(task) {
288
+ yield { eventId: `terminal-${task.id}`, task };
289
+ }
290
+ async function* runEvents(taskId, contextId, run, options) {
291
+ yield { eventId: "1", task: { id: taskId, contextId, status: { state: "TASK_STATE_WORKING", timestamp: new Date().toISOString() } } };
292
+ yield { eventId: "2", task: toTask(taskId, contextId, await run(), options) };
293
+ }
294
+ function toTask(taskId, contextId, result, options) {
295
+ const state = result.status === "succeeded" ? "TASK_STATE_COMPLETED" : result.status === "aborted" ? "TASK_STATE_CANCELED" : "TASK_STATE_FAILED";
296
+ const text = options.redactor?.redact(result.text) ?? result.text;
297
+ return {
298
+ id: taskId,
299
+ contextId,
300
+ status: { state, timestamp: new Date().toISOString() },
301
+ artifacts: text ? [{ artifactId: `${taskId}-result`, parts: [{ text }] }] : undefined,
302
+ };
303
+ }
304
+ function parseRpc(value) {
305
+ if (!record(value) ||
306
+ value.jsonrpc !== "2.0" ||
307
+ !(typeof value.id === "string" || typeof value.id === "number" || value.id === null) ||
308
+ typeof value.method !== "string" ||
309
+ (value.params !== undefined && !record(value.params)))
310
+ throw new A2AError("Invalid JSON-RPC request", 400, "ERR_PRISM_A2A_REQUEST");
311
+ return { jsonrpc: "2.0", id: value.id, method: value.method, params: value.params };
312
+ }
313
+ async function readJson(request, maxBytes, signal) {
314
+ if (!request.body)
315
+ throw new A2AError("Request body is required", 400, "ERR_PRISM_A2A_REQUEST");
316
+ const reader = request.body.getReader(), chunks = [];
317
+ let size = 0;
318
+ try {
319
+ while (true) {
320
+ signal.throwIfAborted();
321
+ const n = await reader.read();
322
+ if (n.done)
323
+ break;
324
+ size += n.value.byteLength;
325
+ if (size > maxBytes)
326
+ throw new A2AError("Request exceeds max bytes", 413, "ERR_PRISM_A2A_REQUEST_LIMIT");
327
+ chunks.push(n.value);
328
+ }
329
+ }
330
+ finally {
331
+ reader.releaseLock();
222
332
  }
333
+ const bytes = new Uint8Array(size);
334
+ let offset = 0;
335
+ for (const chunk of chunks) {
336
+ bytes.set(chunk, offset);
337
+ offset += chunk.byteLength;
338
+ }
339
+ try {
340
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
341
+ }
342
+ catch {
343
+ throw new A2AError("Invalid JSON", 400, "ERR_PRISM_A2A_REQUEST");
344
+ }
345
+ }
346
+ function json(id, result, limits, options) {
347
+ const body = JSON.stringify(options.redactor?.redact({ jsonrpc: "2.0", id, result }) ?? { jsonrpc: "2.0", id, result });
348
+ if (Buffer.byteLength(body) > limits.maxResponseBytes)
349
+ throw new A2AError("Response exceeds max bytes", 507, "ERR_PRISM_A2A_RESPONSE_LIMIT");
350
+ return new Response(body, { headers: JSON_HEADERS });
351
+ }
352
+ function rpcError(id, code, message) {
353
+ return new Response(JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } }), { headers: JSON_HEADERS });
354
+ }
355
+ function errorResponse(status, message, id) {
356
+ const body = { jsonrpc: "2.0", id, error: { code: status === 404 ? -32001 : -32000, message } };
357
+ return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS });
358
+ }
359
+ function integer(value, fallback, max) {
360
+ if (value === undefined)
361
+ return fallback;
362
+ if (!Number.isSafeInteger(value) || Number(value) < 0 || Number(value) > max)
363
+ throw new A2AError("Invalid A2A integer limit", 400, "ERR_PRISM_A2A_REQUEST");
364
+ return Number(value);
365
+ }
366
+ function terminal(state) {
367
+ return finalTerminal(state) || state === "TASK_STATE_INPUT_REQUIRED" || state === "TASK_STATE_AUTH_REQUIRED";
368
+ }
369
+ function finalTerminal(state) {
370
+ return ["TASK_STATE_COMPLETED", "TASK_STATE_FAILED", "TASK_STATE_CANCELED", "TASK_STATE_REJECTED"].includes(state);
371
+ }
372
+ function ownedSignal(parent, timeoutMs) {
373
+ const controller = new AbortController();
374
+ const abort = () => controller.abort(parent.reason);
375
+ if (parent.aborted)
376
+ abort();
377
+ else
378
+ parent.addEventListener("abort", abort, { once: true });
379
+ const timer = setTimeout(() => controller.abort(new DOMException("A2A request timed out", "AbortError")), timeoutMs);
380
+ return {
381
+ signal: controller.signal,
382
+ abort: (reason) => controller.abort(reason),
383
+ dispose: () => {
384
+ clearTimeout(timer);
385
+ parent.removeEventListener("abort", abort);
386
+ },
387
+ };
388
+ }
389
+ function abortable(promise, signal) {
390
+ if (signal.aborted)
391
+ return Promise.reject(signal.reason);
392
+ return new Promise((resolve, reject) => {
393
+ const abort = () => reject(signal.reason);
394
+ signal.addEventListener("abort", abort, { once: true });
395
+ promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
396
+ });
397
+ }
398
+ function safeError(error, options) {
399
+ const message = (error instanceof Error ? error.message : "A2A request failed").slice(0, 1024);
400
+ return options.redactor?.redact(message) ?? message;
223
401
  }
224
- finally {
225
- reader.releaseLock();
226
- } const bytes = new Uint8Array(size); let offset = 0; for (const chunk of chunks) {
227
- bytes.set(chunk, offset);
228
- offset += chunk.byteLength;
229
- } try {
230
- return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
231
- }
232
- catch {
233
- throw new A2AError("Invalid JSON", 400, "ERR_PRISM_A2A_REQUEST");
234
- } }
235
- function json(id, result, limits, options) { const body = JSON.stringify(options.redactor?.redact({ jsonrpc: "2.0", id, result }) ?? { jsonrpc: "2.0", id, result }); if (Buffer.byteLength(body) > limits.maxResponseBytes)
236
- throw new A2AError("Response exceeds max bytes", 507, "ERR_PRISM_A2A_RESPONSE_LIMIT"); return new Response(body, { headers: JSON_HEADERS }); }
237
- function rpcError(id, code, message) { return new Response(JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } }), { headers: JSON_HEADERS }); }
238
- function errorResponse(status, message, id) { const body = { jsonrpc: "2.0", id, error: { code: status === 404 ? -32001 : -32000, message } }; return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS }); }
239
- function integer(value, fallback, max) { if (value === undefined)
240
- return fallback; if (!Number.isSafeInteger(value) || Number(value) < 0 || Number(value) > max)
241
- throw new A2AError("Invalid A2A integer limit", 400, "ERR_PRISM_A2A_REQUEST"); return Number(value); }
242
- function terminal(state) { return finalTerminal(state) || state === "TASK_STATE_INPUT_REQUIRED" || state === "TASK_STATE_AUTH_REQUIRED"; }
243
- function finalTerminal(state) { return ["TASK_STATE_COMPLETED", "TASK_STATE_FAILED", "TASK_STATE_CANCELED", "TASK_STATE_REJECTED"].includes(state); }
244
- function ownedSignal(parent, timeoutMs) { const controller = new AbortController(); const abort = () => controller.abort(parent.reason); if (parent.aborted)
245
- abort();
246
- else
247
- parent.addEventListener("abort", abort, { once: true }); const timer = setTimeout(() => controller.abort(new DOMException("A2A request timed out", "AbortError")), timeoutMs); return { signal: controller.signal, abort: (reason) => controller.abort(reason), dispose: () => { clearTimeout(timer); parent.removeEventListener("abort", abort); } }; }
248
- function abortable(promise, signal) { if (signal.aborted)
249
- return Promise.reject(signal.reason); return new Promise((resolve, reject) => { const abort = () => reject(signal.reason); signal.addEventListener("abort", abort, { once: true }); promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort)); }); }
250
- function safeError(error, options) { const message = (error instanceof Error ? error.message : "A2A request failed").slice(0, 1024); return options.redactor?.redact(message) ?? message; }
251
402
  //# sourceMappingURL=a2a-server.js.map