@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.
@@ -1,29 +1,31 @@
1
1
  import { createA2AAgentCard } from "./a2a-card.js";
2
+ import { bounded, optionalCursor, parseA2AMessage, record, requireId, resolveA2ALimits, validateA2ATask } from "./a2a-parts.js";
2
3
  import { A2AError } from "./errors.js";
3
- const JSON_HEADERS = { "content-type": "application/a2a+json; charset=utf-8" };
4
- const SSE_HEADERS = { "content-type": "text/event-stream; charset=utf-8", "cache-control": "no-cache, no-transform" };
5
- 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 };
6
- 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 };
4
+ const JSON_HEADERS = { "content-type": "application/a2a+json; charset=utf-8", "a2a-version": "1.0" };
5
+ const SSE_HEADERS = { "content-type": "text/event-stream; charset=utf-8", "cache-control": "no-cache, no-transform", "a2a-version": "1.0" };
7
6
  export function createA2AHandler(options) {
8
7
  const limits = resolveA2ALimits(options.limits);
9
8
  const card = createA2AAgentCard(options.card);
9
+ if (Boolean(card.capabilities.pushNotifications) !== Boolean(options.push))
10
+ throw new A2AError("Agent card push capability must match push adapter", 400, "ERR_PRISM_A2A_CONFIG");
10
11
  const endpointPath = options.endpointPath ?? new URL(card.supportedInterfaces[0].url).pathname;
11
12
  if (!endpointPath.startsWith("/"))
12
13
  throw new A2AError("endpointPath must be absolute", 400, "ERR_PRISM_A2A_CONFIG");
13
14
  const cardJson = JSON.stringify(card);
14
- if (new TextEncoder().encode(cardJson).byteLength > limits.maxCardBytes)
15
+ if (Buffer.byteLength(cardJson) > limits.maxCardBytes)
15
16
  throw new A2AError("Agent card exceeds max bytes", 400, "ERR_PRISM_A2A_CARD");
16
17
  let active = 0;
17
- let sequence = 0;
18
18
  return async (request) => {
19
- let acquired = false;
20
- let transferred = false;
19
+ let acquired = false, transferred = false;
21
20
  try {
22
21
  const path = new URL(request.url).pathname;
23
22
  if (request.method === "GET" && path === "/.well-known/agent-card.json")
24
- return new Response(cardJson, { status: 200, headers: JSON_HEADERS });
23
+ return new Response(cardJson, { headers: JSON_HEADERS });
25
24
  if (request.method !== "POST" || path !== endpointPath)
26
25
  return errorResponse(404, "Not found", null);
26
+ const version = request.headers.get("a2a-version");
27
+ if (version && version !== "1.0")
28
+ return rpcError(null, -32009, "Unsupported A2A version");
27
29
  if (active >= limits.maxConcurrentRequests)
28
30
  return errorResponse(429, "Too many requests", null);
29
31
  active += 1;
@@ -33,31 +35,67 @@ export function createA2AHandler(options) {
33
35
  const contentType = request.headers.get("content-type")?.split(";", 1)[0]?.trim();
34
36
  if (contentType !== "application/json" && contentType !== "application/a2a+json")
35
37
  return errorResponse(415, "Unsupported media type", null);
36
- const body = await readJson(request, limits.maxRequestBytes, owned.signal);
37
- const rpc = parseRpc(body);
38
- const authorized = await abortable(Promise.resolve(options.authorize({ request, method: rpc.method, signal: owned.signal })), owned.signal);
39
- if (!authorized)
38
+ const rpc = parseRpc(await readJson(request, limits.maxRequestBytes, owned.signal));
39
+ const authorization = await abortable(Promise.resolve(options.authorize({ request, method: rpc.method, signal: owned.signal })), owned.signal);
40
+ if (!authorization)
40
41
  return errorResponse(403, "Forbidden", rpc.id);
41
42
  if (rpc.method === "GetExtendedAgentCard")
42
- return boundedJson({ jsonrpc: "2.0", id: rpc.id, result: card }, limits.maxResponseBytes, options);
43
- if (rpc.method !== "SendMessage" && rpc.method !== "SendStreamingMessage")
44
- return boundedJson({ jsonrpc: "2.0", id: rpc.id, error: { code: -32601, message: "Method not found" } }, limits.maxResponseBytes, options);
45
- const message = parseMessage(rpc.params?.message, limits.maxRequestBytes);
46
- const input = message.parts.map((part) => part.text).join("\n");
47
- sequence += 1;
48
- const taskId = `task-${crypto.randomUUID()}`;
49
- const contextId = message.contextId ?? `context-${sequence}-${crypto.randomUUID()}`;
50
- const session = await abortable(Promise.resolve(options.exposure.sessionFactory(authorized)), owned.signal);
51
- if (rpc.method === "SendMessage") {
52
- const result = await abortable(session.run(input, { ownership: authorized.ownership, metadata: authorized.metadata, signal: owned.signal, redactor: options.redactor }), owned.signal);
53
- return boundedJson({ jsonrpc: "2.0", id: rpc.id, result: { task: toTask(taskId, contextId, result, options) } }, limits.maxResponseBytes, options);
43
+ return card.capabilities.extendedAgentCard ? json(rpc.id, card, limits, options) : rpcError(rpc.id, -32004, "Extended Agent Card unavailable");
44
+ if (rpc.method === "SendMessage" || rpc.method === "SendStreamingMessage") {
45
+ const message = await parseA2AMessage(rpc.params?.message, limits, options.parts);
46
+ 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 });
48
+ await validateA2ATask(task, limits, options.parts);
49
+ if (rpc.method === "SendMessage")
50
+ return json(rpc.id, { task }, limits, options);
51
+ 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; });
54
+ }
55
+ if (message.parts.some((part) => !("text" in part)))
56
+ return rpcError(rpc.id, -32004, "Durable task lifecycle required for rich parts");
57
+ const input = message.parts.map((part) => part.text).join("\n");
58
+ const session = await abortable(Promise.resolve(options.exposure.sessionFactory(authorization)), owned.signal);
59
+ const taskId = `task-${crypto.randomUUID()}`, contextId = message.contextId ?? `context-${crypto.randomUUID()}`;
60
+ 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);
62
+ 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; });
54
65
  }
55
- if (rpc.method === "SendStreamingMessage") {
66
+ if (["GetTask", "ListTasks", "CancelTask", "SubscribeToTask"].includes(rpc.method)) {
67
+ if (!options.tasks)
68
+ return rpcError(rpc.id, -32004, "Task lifecycle unavailable");
69
+ 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");
72
+ }
73
+ if (rpc.method === "ListTasks") {
74
+ 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");
75
+ const page = await options.tasks.list({ pageSize, pageToken, contextId, authorization, signal: owned.signal });
76
+ if (page.tasks.length > pageSize || page.tasks.length > limits.maxPageSize)
77
+ throw new A2AError("Task page exceeds limit", 507, "ERR_PRISM_A2A_RESPONSE_LIMIT");
78
+ for (const task of page.tasks)
79
+ await validateA2ATask(task, limits, options.parts);
80
+ optionalCursor(page.nextPageToken, limits);
81
+ return json(rpc.id, page, limits, options);
82
+ }
83
+ if (rpc.method === "CancelTask") {
84
+ 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");
86
+ }
87
+ const taskId = requireId(rpc.params?.id, limits), afterEventId = optionalCursor(rpc.params?.afterEventId, limits);
88
+ const current = await options.tasks.get({ id: taskId, historyLength: 0, authorization, signal: owned.signal });
89
+ if (!current)
90
+ return rpcError(rpc.id, -32001, "Task not found");
91
+ if (finalTerminal(current.status.state))
92
+ return rpcError(rpc.id, -32004, "Terminal task cannot be subscribed");
56
93
  transferred = true;
57
- const stream = taskStream(rpc.id, taskId, contextId, () => session.run(input, { ownership: authorized.ownership, metadata: authorized.metadata, signal: owned.signal, redactor: options.redactor }), owned, limits, options, () => { active -= 1; });
58
- return new Response(stream, { status: 200, headers: SSE_HEADERS });
94
+ return streamResponse(rpc.id, options.tasks.subscribe({ id: taskId, afterEventId, authorization, signal: owned.signal }), owned, limits, options, () => { active -= 1; });
59
95
  }
60
- throw new A2AError("Method not found", 400, "ERR_PRISM_A2A_METHOD");
96
+ if (rpc.method.includes("TaskPushNotificationConfig"))
97
+ return await pushOperation(rpc, authorization, owned.signal, limits, options);
98
+ return rpcError(rpc.id, -32601, "Method not found");
61
99
  }
62
100
  finally {
63
101
  if (!transferred)
@@ -74,157 +112,140 @@ export function createA2AHandler(options) {
74
112
  }
75
113
  };
76
114
  }
77
- function taskStream(id, taskId, contextId, run, owned, limits, options, release) {
78
- const iterator = (async function* () {
79
- yield { jsonrpc: "2.0", id, result: { task: { id: taskId, contextId, status: { state: "TASK_STATE_WORKING", timestamp: new Date().toISOString() } } } };
80
- try {
81
- const result = await run();
82
- yield { jsonrpc: "2.0", id, result: { task: toTask(taskId, contextId, result, options) } };
83
- }
84
- catch (error) {
85
- yield { jsonrpc: "2.0", id, error: { code: -32000, message: safeError(error, options) } };
86
- }
87
- })()[Symbol.asyncIterator]();
88
- let events = 0;
89
- let bytes = 0;
90
- let released = false;
115
+ async function pushOperation(rpc, authorization, signal, limits, options) {
116
+ if (!options.push)
117
+ return rpcError(rpc.id, -32004, "Push notifications unavailable");
118
+ const taskId = requireId(rpc.params?.taskId, limits), id = rpc.params?.id === undefined ? undefined : requireId(rpc.params.id, limits, "push config id");
119
+ if (rpc.method === "CreateTaskPushNotificationConfig") {
120
+ const supplied = record(rpc.params?.config) ? rpc.params.config : rpc.params;
121
+ if (!record(supplied))
122
+ throw new A2AError("Invalid push config", 400, "ERR_PRISM_A2A_PUSH");
123
+ const config = await parsePush({ ...supplied, taskId }, limits, options);
124
+ return json(rpc.id, publicPush(await options.push.create({ config, authorization, signal })), limits, options);
125
+ }
126
+ if (rpc.method === "GetTaskPushNotificationConfig") {
127
+ const value = await options.push.get({ taskId, id: id, authorization, signal });
128
+ return value ? json(rpc.id, publicPush(value), limits, options) : rpcError(rpc.id, -32001, "Task or push config not found");
129
+ }
130
+ if (rpc.method === "ListTaskPushNotificationConfigs") {
131
+ const pageSize = integer(rpc.params?.pageSize, limits.maxPushConfigs, limits.maxPushConfigs), pageToken = optionalCursor(rpc.params?.pageToken, limits);
132
+ const page = await options.push.list({ taskId, pageSize, pageToken, authorization, signal });
133
+ if (page.configs.length > pageSize)
134
+ throw new A2AError("Push config page exceeds limit", 507, "ERR_PRISM_A2A_RESPONSE_LIMIT");
135
+ return json(rpc.id, { configs: page.configs.map(publicPush), nextPageToken: optionalCursor(page.nextPageToken, limits) }, limits, options);
136
+ }
137
+ 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");
139
+ return rpcError(rpc.id, -32601, "Method not found");
140
+ }
141
+ async function parsePush(value, limits, options) {
142
+ const id = requireId(value.id, limits, "push config id"), taskId = requireId(value.taskId, limits);
143
+ if (typeof value.url !== "string" || !options.parts?.validateUrl)
144
+ throw new A2AError("Push URL policy required", 403, "ERR_PRISM_A2A_ORIGIN");
145
+ let url;
146
+ try {
147
+ url = new URL(value.url);
148
+ }
149
+ catch {
150
+ throw new A2AError("Invalid push URL", 400, "ERR_PRISM_A2A_PUSH");
151
+ }
152
+ if (url.protocol !== "https:" || url.username || url.password || url.hash)
153
+ throw new A2AError("Push URL requires credential-free HTTPS", 403, "ERR_PRISM_A2A_ORIGIN");
154
+ 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");
157
+ }
158
+ function publicPush(value) { return { id: value.id, taskId: value.taskId, url: value.url, authentication: value.authentication ? { scheme: value.authentication.scheme } : undefined }; }
159
+ function streamResponse(id, source, owned, limits, options, release) {
160
+ const iterator = source[Symbol.asyncIterator]();
161
+ let events = 0, bytes = 0, released = false, previous = "";
91
162
  const finish = () => { if (!released) {
92
163
  released = true;
93
164
  owned.dispose();
94
165
  release();
166
+ void iterator.return?.();
95
167
  } };
96
- return new ReadableStream({
97
- async pull(controller) {
98
- try {
99
- const next = await iterator.next();
100
- if (next.done) {
101
- finish();
102
- controller.close();
103
- return;
104
- }
105
- const payload = options.redactor?.redact(next.value) ?? next.value;
106
- const chunk = new TextEncoder().encode(`data: ${JSON.stringify(payload)}\n\n`);
107
- events += 1;
108
- bytes += chunk.byteLength;
109
- if (chunk.byteLength > limits.maxEventBytes || events > limits.maxStreamEvents || bytes > limits.maxStreamBytes)
110
- throw new A2AError("A2A stream limit exceeded", 507, "ERR_PRISM_A2A_STREAM_LIMIT");
111
- controller.enqueue(chunk);
112
- }
113
- catch (error) {
168
+ return new Response(new ReadableStream({
169
+ async pull(controller) { try {
170
+ const next = await iterator.next();
171
+ if (next.done) {
114
172
  finish();
115
- controller.error(error);
173
+ controller.close();
174
+ return;
116
175
  }
117
- },
118
- cancel(reason) { owned.abort(reason); finish(); void iterator.return?.(); },
119
- });
120
- }
121
- function toTask(taskId, contextId, result, options) {
122
- const state = result.status === "succeeded" ? "TASK_STATE_COMPLETED" : result.status === "aborted" ? "TASK_STATE_CANCELED" : "TASK_STATE_FAILED";
123
- const text = options.redactor?.redact(result.text) ?? result.text;
124
- return Object.freeze({
125
- id: taskId,
126
- contextId,
127
- status: { state, timestamp: new Date().toISOString() },
128
- artifacts: text ? [{ artifactId: `${taskId}-result`, parts: [{ text }] }] : undefined,
129
- });
130
- }
131
- function parseRpc(value) {
132
- if (!isRecord(value) || value.jsonrpc !== "2.0" || !(typeof value.id === "string" || typeof value.id === "number" || value.id === null) || typeof value.method !== "string")
133
- throw new A2AError("Invalid JSON-RPC request", 400, "ERR_PRISM_A2A_REQUEST");
134
- if (value.params !== undefined && !isRecord(value.params))
135
- throw new A2AError("Invalid JSON-RPC params", 400, "ERR_PRISM_A2A_REQUEST");
136
- return { jsonrpc: "2.0", id: value.id, method: value.method, params: value.params };
137
- }
138
- function parseMessage(value, maxBytes) {
139
- if (!isRecord(value) || (value.role !== "user" && value.role !== "ROLE_USER") || typeof value.messageId !== "string" || !value.messageId || !Array.isArray(value.parts) || value.parts.length < 1 || value.parts.length > 32)
140
- throw new A2AError("Invalid A2A message", 400, "ERR_PRISM_A2A_MESSAGE");
141
- const parts = value.parts.map((part) => {
142
- if (!isRecord(part) || typeof part.text !== "string" || Object.keys(part).some((key) => key !== "text" && key !== "metadata"))
143
- throw new A2AError("Only text A2A parts are supported", 400, "ERR_PRISM_A2A_MESSAGE");
144
- return { text: part.text, metadata: isRecord(part.metadata) ? part.metadata : undefined };
145
- });
146
- const message = { role: value.role, messageId: value.messageId, parts, contextId: typeof value.contextId === "string" ? value.contextId : undefined };
147
- if (encode(message).byteLength > maxBytes)
148
- throw new A2AError("A2A message exceeds max bytes", 413, "ERR_PRISM_A2A_MESSAGE_LIMIT");
149
- return message;
150
- }
151
- function resolveA2ALimits(input = {}) {
152
- const output = {};
153
- for (const key of Object.keys(DEFAULTS)) {
154
- const value = input[key] ?? DEFAULTS[key];
155
- if (!Number.isSafeInteger(value) || value < 1 || value > HARD[key])
156
- throw new A2AError(`${key} is invalid`, 400, "ERR_PRISM_A2A_CONFIG");
157
- output[key] = value;
158
- }
159
- return output;
160
- }
161
- async function readJson(request, maxBytes, signal) {
162
- if (!request.body)
163
- throw new A2AError("Request body is required", 400, "ERR_PRISM_A2A_REQUEST");
164
- const reader = request.body.getReader();
165
- const chunks = [];
166
- let size = 0;
167
- try {
168
- while (true) {
169
- signal.throwIfAborted();
170
- const next = await reader.read();
171
- if (next.done)
172
- break;
173
- size += next.value.byteLength;
174
- if (size > maxBytes)
175
- throw new A2AError("Request exceeds max bytes", 413, "ERR_PRISM_A2A_REQUEST_LIMIT");
176
- chunks.push(next.value);
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);
177
187
  }
188
+ catch (error) {
189
+ finish();
190
+ controller.error(error);
191
+ } },
192
+ cancel(reason) { owned.abort(reason); finish(); },
193
+ }), { headers: SSE_HEADERS });
194
+ }
195
+ async function validateTaskEvent(event, limits, options) {
196
+ if ("task" in event) {
197
+ await validateA2ATask(event.task, limits, options.parts);
198
+ return;
178
199
  }
179
- finally {
180
- reader.releaseLock();
181
- }
182
- const bytes = new Uint8Array(size);
183
- let offset = 0;
184
- for (const chunk of chunks) {
185
- bytes.set(chunk, offset);
186
- offset += chunk.byteLength;
187
- }
188
- try {
189
- return JSON.parse(new TextDecoder().decode(bytes));
190
- }
191
- catch {
192
- throw new A2AError("Invalid JSON", 400, "ERR_PRISM_A2A_REQUEST");
200
+ if ("statusUpdate" in event) {
201
+ await validateA2ATask({ id: event.statusUpdate.taskId, contextId: event.statusUpdate.contextId, status: event.statusUpdate.status }, limits, options.parts);
202
+ return;
193
203
  }
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);
194
205
  }
195
- function boundedJson(value, maxBytes, options) {
196
- const body = JSON.stringify(options.redactor?.redact(value) ?? value);
197
- if (new TextEncoder().encode(body).byteLength > maxBytes)
198
- throw new A2AError("Response exceeds max bytes", 507, "ERR_PRISM_A2A_RESPONSE_LIMIT");
199
- return new Response(body, { status: 200, headers: JSON_HEADERS });
200
- }
201
- function errorResponse(status, message, id) {
202
- const body = { jsonrpc: "2.0", id, error: { code: status === 404 ? -32601 : -32000, message } };
203
- return new Response(JSON.stringify(body), { status, headers: JSON_HEADERS });
204
- }
205
- function ownedSignal(parent, timeoutMs) {
206
- const controller = new AbortController();
207
- const abort = () => controller.abort(parent.reason);
208
- if (parent.aborted)
209
- abort();
210
- else
211
- parent.addEventListener("abort", abort, { once: true });
212
- const timer = setTimeout(() => controller.abort(new DOMException("A2A request timed out", "AbortError")), timeoutMs);
213
- return { signal: controller.signal, abort: (reason) => controller.abort(reason), dispose: () => { clearTimeout(timer); parent.removeEventListener("abort", abort); } };
214
- }
215
- function abortable(promise, signal) {
216
- if (signal.aborted)
217
- return Promise.reject(signal.reason);
218
- return new Promise((resolve, reject) => {
219
- const abort = () => reject(signal.reason);
220
- signal.addEventListener("abort", abort, { once: true });
221
- promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
222
- });
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);
222
+ }
223
223
  }
224
- function encode(value) { return new TextEncoder().encode(JSON.stringify(value)); }
225
- function isRecord(value) { return !!value && typeof value === "object" && !Array.isArray(value); }
226
- function safeError(error, options) {
227
- const message = (error instanceof Error ? error.message : "A2A request failed").slice(0, 1024);
228
- return options.redactor?.redact(message) ?? message;
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));
229
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; }
230
251
  //# sourceMappingURL=a2a-server.js.map