@arnilo/prism-supervisor 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ## [0.0.5] - 2026-07-16
6
+
7
+ - Added optional bounded child delegation and A2A 1.0 card/server/client interoperability.
8
+
9
+
10
+ ## [0.0.4] - 2026-07-14
11
+
12
+ - Bounded explicit local child delegation with narrowing-only policy composition, derived memory scope IDs, hooks, nested delegation, cancellation, and event subscription.
13
+ - A2A protocol 1.0 cards, ES256 JWS signing/verification, authorized web-standard JSON-RPC/SSE handler, and explicit allow-listed remote client.
package/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Arnilo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # @arnilo/prism-supervisor
2
+
3
+ Optional bounded local child delegation and A2A 1.0 interoperability for Prism.
4
+
5
+ ```bash
6
+ npm install @arnilo/prism-supervisor @arnilo/prism
7
+ ```
8
+
9
+ ```ts
10
+ import { createAgent, createMockProvider, providerDone, providerTextDelta } from "@arnilo/prism";
11
+ import { createSupervisor } from "@arnilo/prism-supervisor";
12
+
13
+ const supervisor = createSupervisor({
14
+ ownership: { tenantId: "tenant", userId: "user" },
15
+ children: {
16
+ research: {
17
+ createAgent: ({ resourceId, threadId, permission }) => createAgent({
18
+ model: { provider: "mock", model: "research" },
19
+ provider: createMockProvider([providerTextDelta(`${resourceId}:${threadId}`), providerDone()]),
20
+ permission,
21
+ }),
22
+ },
23
+ },
24
+ });
25
+
26
+ console.log((await supervisor.delegate({ childId: "research", input: "Check sources" })).text);
27
+ ```
28
+
29
+ Also exports A2A 1.0 `createA2AAgentCard`, `signA2AAgentCard`, `verifyA2AAgentCard`, `createA2AHandler`, and `createA2AClient`. Only text parts and JSON-RPC `SendMessage`, `SendStreamingMessage`, and `GetExtendedAgentCard` are supported. Hosts own authentication, TLS, endpoint allow-lists, child credential resolution, and memory construction from package-derived resource/thread IDs.
30
+
31
+ See [Supervisors](../../docs/supervisors.md) and [A2A interoperability](../../docs/a2a.md).
@@ -0,0 +1,17 @@
1
+ import type { A2AAgentCard } from "./a2a-types.js";
2
+ export interface SignA2AAgentCardOptions {
3
+ readonly privateKey: CryptoKey;
4
+ readonly keyId: string;
5
+ readonly expiresAt: string;
6
+ readonly issuedAt?: string;
7
+ }
8
+ export interface VerifyA2AAgentCardOptions {
9
+ readonly publicKey: CryptoKey;
10
+ readonly keyId?: string;
11
+ readonly now?: Date;
12
+ readonly maxAgeMs?: number;
13
+ }
14
+ export declare function createA2AAgentCard(card: A2AAgentCard): A2AAgentCard;
15
+ export declare function signA2AAgentCard(card: A2AAgentCard, options: SignA2AAgentCardOptions): Promise<A2AAgentCard>;
16
+ export declare function verifyA2AAgentCard(card: A2AAgentCard, options: VerifyA2AAgentCardOptions): Promise<void>;
17
+ export declare function canonicalizeA2AAgentCard(card: A2AAgentCard): string;
@@ -0,0 +1,129 @@
1
+ import { A2AError } from "./errors.js";
2
+ export function createA2AAgentCard(card) {
3
+ validateCard(card);
4
+ return deepFreeze(structuredClone(card));
5
+ }
6
+ export async function signA2AAgentCard(card, options) {
7
+ validateCard(card);
8
+ if (!options.keyId.trim())
9
+ throw new A2AError("keyId is required", 400, "ERR_PRISM_A2A_CARD");
10
+ const issuedAt = options.issuedAt ?? new Date().toISOString();
11
+ const issued = Date.parse(issuedAt);
12
+ const expires = Date.parse(options.expiresAt);
13
+ if (!Number.isFinite(issued) || !Number.isFinite(expires) || expires <= issued)
14
+ throw new A2AError("Card signature expiry is invalid", 400, "ERR_PRISM_A2A_CARD");
15
+ const protectedHeader = base64url(new TextEncoder().encode(canonicalJson({ alg: "ES256", typ: "JOSE", kid: options.keyId, iat: issuedAt, exp: options.expiresAt })));
16
+ const payload = base64url(new TextEncoder().encode(canonicalCard(card)));
17
+ const signature = await crypto.subtle.sign({ name: "ECDSA", hash: "SHA-256" }, options.privateKey, new TextEncoder().encode(`${protectedHeader}.${payload}`));
18
+ const signed = { protected: protectedHeader, signature: base64url(new Uint8Array(signature)) };
19
+ return deepFreeze({ ...structuredClone(card), signatures: [...(card.signatures ?? []), signed] });
20
+ }
21
+ export async function verifyA2AAgentCard(card, options) {
22
+ validateCard(card);
23
+ if (!card.signatures?.length)
24
+ throw new A2AError("Agent card is unsigned", 403, "ERR_PRISM_A2A_CARD_SIGNATURE");
25
+ const payload = base64url(new TextEncoder().encode(canonicalCard(card)));
26
+ let matched = false;
27
+ for (const candidate of card.signatures) {
28
+ try {
29
+ const header = parseProtected(candidate.protected);
30
+ if (header.alg !== "ES256" || header.typ !== "JOSE")
31
+ continue;
32
+ if (options.keyId !== undefined && header.kid !== options.keyId)
33
+ continue;
34
+ const now = (options.now ?? new Date()).getTime();
35
+ const issued = Date.parse(header.iat);
36
+ const expires = Date.parse(header.exp);
37
+ if (!Number.isFinite(issued) || !Number.isFinite(expires) || now < issued || now >= expires)
38
+ continue;
39
+ if (options.maxAgeMs !== undefined && (options.maxAgeMs < 1 || now - issued > options.maxAgeMs))
40
+ continue;
41
+ const valid = await crypto.subtle.verify({ name: "ECDSA", hash: "SHA-256" }, options.publicKey, fromBase64url(candidate.signature).buffer, new TextEncoder().encode(`${candidate.protected}.${payload}`));
42
+ if (valid) {
43
+ matched = true;
44
+ break;
45
+ }
46
+ }
47
+ catch {
48
+ continue;
49
+ }
50
+ }
51
+ if (!matched)
52
+ throw new A2AError("Agent card signature is invalid or expired", 403, "ERR_PRISM_A2A_CARD_SIGNATURE");
53
+ }
54
+ export function canonicalizeA2AAgentCard(card) {
55
+ validateCard(card);
56
+ return canonicalCard(card);
57
+ }
58
+ function canonicalCard(card) {
59
+ const { signatures: _signatures, ...unsigned } = card;
60
+ return canonicalJson(unsigned);
61
+ }
62
+ function validateCard(card) {
63
+ if (!card.name?.trim() || !card.description?.trim() || !card.version?.trim())
64
+ throw new A2AError("Agent card identity is incomplete", 400, "ERR_PRISM_A2A_CARD");
65
+ if (!card.supportedInterfaces.length || !card.supportedInterfaces.every((item) => item.protocolBinding === "JSONRPC" && item.protocolVersion === "1.0" && isHttpsUrl(item.url)))
66
+ throw new A2AError("Agent card requires an HTTPS JSONRPC 1.0 interface", 400, "ERR_PRISM_A2A_CARD");
67
+ if (!card.defaultInputModes.includes("text/plain") || !card.defaultOutputModes.includes("text/plain"))
68
+ throw new A2AError("Agent card must support text/plain", 400, "ERR_PRISM_A2A_CARD");
69
+ const ids = new Set();
70
+ for (const skill of card.skills) {
71
+ if (!skill.id.trim() || !skill.name.trim() || !skill.description.trim() || ids.has(skill.id))
72
+ throw new A2AError("Agent card skill is invalid", 400, "ERR_PRISM_A2A_CARD");
73
+ ids.add(skill.id);
74
+ }
75
+ }
76
+ function parseProtected(value) {
77
+ const parsed = JSON.parse(new TextDecoder().decode(fromBase64url(value)));
78
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
79
+ throw new Error("invalid protected header");
80
+ const record = parsed;
81
+ if (typeof record.alg !== "string" || typeof record.typ !== "string" || typeof record.kid !== "string" || typeof record.iat !== "string" || typeof record.exp !== "string")
82
+ throw new Error("invalid protected header");
83
+ return { alg: record.alg, typ: record.typ, kid: record.kid, iat: record.iat, exp: record.exp };
84
+ }
85
+ function canonicalJson(value) {
86
+ if (value === null || typeof value === "boolean" || typeof value === "string")
87
+ return JSON.stringify(value);
88
+ if (typeof value === "number") {
89
+ if (!Number.isFinite(value))
90
+ throw new A2AError("Non-finite card number", 400, "ERR_PRISM_A2A_CARD");
91
+ return JSON.stringify(value);
92
+ }
93
+ if (Array.isArray(value))
94
+ return `[${value.map(canonicalJson).join(",")}]`;
95
+ if (value && typeof value === "object") {
96
+ const record = value;
97
+ return `{${Object.keys(record).filter((key) => record[key] !== undefined).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
98
+ }
99
+ throw new A2AError("Agent card is not canonical JSON", 400, "ERR_PRISM_A2A_CARD");
100
+ }
101
+ function base64url(bytes) {
102
+ let binary = "";
103
+ for (const byte of bytes)
104
+ binary += String.fromCharCode(byte);
105
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/u, "");
106
+ }
107
+ function fromBase64url(value) {
108
+ if (!/^[A-Za-z0-9_-]+$/u.test(value))
109
+ throw new Error("invalid base64url");
110
+ const binary = atob(value.replace(/-/g, "+").replace(/_/g, "/"));
111
+ return Uint8Array.from(binary, (char) => char.charCodeAt(0));
112
+ }
113
+ function isHttpsUrl(value) {
114
+ try {
115
+ return new URL(value).protocol === "https:";
116
+ }
117
+ catch {
118
+ return false;
119
+ }
120
+ }
121
+ function deepFreeze(value) {
122
+ if (value && typeof value === "object" && !Object.isFrozen(value)) {
123
+ for (const child of Object.values(value))
124
+ deepFreeze(child);
125
+ Object.freeze(value);
126
+ }
127
+ return value;
128
+ }
129
+ //# sourceMappingURL=a2a-card.js.map
@@ -0,0 +1,2 @@
1
+ import { type A2AClient, type A2AClientOptions } from "./a2a-types.js";
2
+ export declare function createA2AClient(options: A2AClientOptions): A2AClient;
@@ -0,0 +1,312 @@
1
+ import { createA2AAgentCard } from "./a2a-card.js";
2
+ import { A2AError } from "./errors.js";
3
+ import { A2A_PROTOCOL_VERSION } from "./a2a-types.js";
4
+ const DEFAULTS = { maxRequestBytes: 64 * 1024, maxResponseBytes: 1024 * 1024, maxEventBytes: 64 * 1024, maxStreamBytes: 10 * 1024 * 1024, maxStreamEvents: 10_000, maxConcurrentRequests: 16, timeoutMs: 120_000, maxCardBytes: 64 * 1024 };
5
+ const HARD = { maxRequestBytes: 1024 * 1024, maxResponseBytes: 8 * 1024 * 1024, maxEventBytes: 1024 * 1024, maxStreamBytes: 64 * 1024 * 1024, maxStreamEvents: 100_000, maxConcurrentRequests: 256, timeoutMs: 30 * 60_000, maxCardBytes: 1024 * 1024 };
6
+ export function createA2AClient(options) {
7
+ const endpoint = requireAllowedHttpsUrl(options.endpoint, options.allowedOrigins);
8
+ const cardUrl = requireAllowedHttpsUrl(options.cardUrl ?? `${endpoint.origin}/.well-known/agent-card.json`, options.allowedOrigins);
9
+ const fetcher = options.fetch ?? globalThis.fetch;
10
+ const limits = clientLimits(options.limits);
11
+ let active = 0;
12
+ let requestId = 0;
13
+ async function withRequest(signal, operation) {
14
+ if (active >= limits.maxConcurrentRequests)
15
+ throw new A2AError("A2A client concurrency exceeded", 429, "ERR_PRISM_A2A_CONCURRENCY");
16
+ active += 1;
17
+ const owned = ownedSignal(signal, limits.timeoutMs);
18
+ try {
19
+ return await operation(owned.signal);
20
+ }
21
+ finally {
22
+ owned.dispose();
23
+ active -= 1;
24
+ }
25
+ }
26
+ async function getCard(call = {}) {
27
+ return withRequest(call.signal, async (signal) => {
28
+ const response = await fetcher(cardUrl, { method: "GET", signal, redirect: "error", headers: { accept: "application/a2a+json, application/json" } });
29
+ if (!response.ok)
30
+ throw new A2AError("A2A card request failed", response.status, "ERR_PRISM_A2A_REMOTE");
31
+ const value = await readBoundedJson(response, limits.maxCardBytes, signal);
32
+ const card = parseCard(value);
33
+ if (!card.supportedInterfaces.some((item) => item.protocolBinding === "JSONRPC" && item.protocolVersion === A2A_PROTOCOL_VERSION && item.url === endpoint.href))
34
+ throw new A2AError("Agent card does not declare the selected endpoint", 403, "ERR_PRISM_A2A_CARD");
35
+ if (options.verifyCard)
36
+ await abortable(Promise.resolve(options.verifyCard(card)), signal);
37
+ return card;
38
+ });
39
+ }
40
+ async function send(input, call = {}) {
41
+ return withRequest(call.signal, async (signal) => {
42
+ assertInput(input, limits.maxRequestBytes);
43
+ await getCardWithin(signal);
44
+ const id = ++requestId;
45
+ const body = JSON.stringify(requestBody(id, "SendMessage", input));
46
+ if (new TextEncoder().encode(body).byteLength > limits.maxRequestBytes)
47
+ throw new A2AError("A2A request exceeds max bytes", 413, "ERR_PRISM_A2A_REQUEST_LIMIT");
48
+ const authHeaders = await abortable(Promise.resolve(options.authorize?.({ endpoint: endpoint.href, signal }) ?? {}), signal);
49
+ const response = await fetcher(endpoint, { method: "POST", signal, redirect: "error", headers: { ...headersObject(authHeaders), "content-type": "application/a2a+json", accept: "application/a2a+json" }, body });
50
+ if (!response.ok)
51
+ throw new A2AError("A2A remote request failed", response.status, "ERR_PRISM_A2A_REMOTE");
52
+ const rpc = parseRpcResponse(await readBoundedJson(response, limits.maxResponseBytes, signal), id);
53
+ if (rpc.error)
54
+ throw new A2AError(safeRemote(rpc.error.message, options), 502, "ERR_PRISM_A2A_REMOTE");
55
+ return taskResult(parseTaskResult(rpc.result), options);
56
+ });
57
+ }
58
+ async function* stream(input, call = {}) {
59
+ if (active >= limits.maxConcurrentRequests)
60
+ throw new A2AError("A2A client concurrency exceeded", 429, "ERR_PRISM_A2A_CONCURRENCY");
61
+ active += 1;
62
+ const owned = ownedSignal(call.signal, limits.timeoutMs);
63
+ let reader;
64
+ try {
65
+ assertInput(input, limits.maxRequestBytes);
66
+ await getCardWithin(owned.signal);
67
+ const id = ++requestId;
68
+ const body = JSON.stringify(requestBody(id, "SendStreamingMessage", input));
69
+ if (new TextEncoder().encode(body).byteLength > limits.maxRequestBytes)
70
+ throw new A2AError("A2A request exceeds max bytes", 413, "ERR_PRISM_A2A_REQUEST_LIMIT");
71
+ const authHeaders = await abortable(Promise.resolve(options.authorize?.({ endpoint: endpoint.href, signal: owned.signal }) ?? {}), owned.signal);
72
+ const response = await fetcher(endpoint, { method: "POST", signal: owned.signal, redirect: "error", headers: { ...headersObject(authHeaders), "content-type": "application/a2a+json", accept: "text/event-stream" }, body });
73
+ if (!response.ok || !response.body || !response.headers.get("content-type")?.startsWith("text/event-stream"))
74
+ throw new A2AError("A2A stream request failed", response.status, "ERR_PRISM_A2A_REMOTE");
75
+ reader = response.body.getReader();
76
+ let buffered = "";
77
+ let totalBytes = 0;
78
+ let eventCount = 0;
79
+ let terminal = false;
80
+ while (true) {
81
+ owned.signal.throwIfAborted();
82
+ const next = await reader.read();
83
+ if (next.done)
84
+ break;
85
+ totalBytes += next.value.byteLength;
86
+ if (totalBytes > limits.maxStreamBytes)
87
+ throw new A2AError("A2A stream exceeds max bytes", 507, "ERR_PRISM_A2A_STREAM_LIMIT");
88
+ buffered += new TextDecoder().decode(next.value, { stream: true });
89
+ while (buffered.includes("\n\n")) {
90
+ const split = buffered.indexOf("\n\n");
91
+ const frame = buffered.slice(0, split);
92
+ buffered = buffered.slice(split + 2);
93
+ if (new TextEncoder().encode(frame).byteLength > limits.maxEventBytes)
94
+ throw new A2AError("A2A event exceeds max bytes", 507, "ERR_PRISM_A2A_STREAM_LIMIT");
95
+ eventCount += 1;
96
+ if (eventCount > limits.maxStreamEvents)
97
+ throw new A2AError("A2A stream exceeds max events", 507, "ERR_PRISM_A2A_STREAM_LIMIT");
98
+ const data = frame.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
99
+ if (!data)
100
+ continue;
101
+ let parsed;
102
+ try {
103
+ parsed = JSON.parse(data);
104
+ }
105
+ catch {
106
+ throw new A2AError("Malformed A2A stream event", 502, "ERR_PRISM_A2A_REMOTE");
107
+ }
108
+ const rpc = parseRpcResponse(parsed, id);
109
+ if (rpc.error)
110
+ throw new A2AError(safeRemote(rpc.error.message, options), 502, "ERR_PRISM_A2A_REMOTE");
111
+ const task = parseTaskResult(rpc.result);
112
+ if (task.status.state === "TASK_STATE_FAILED" || task.status.state === "TASK_STATE_CANCELED")
113
+ throw new A2AError("Remote A2A stream task failed", 502, "ERR_PRISM_A2A_REMOTE");
114
+ if (task.status.state === "TASK_STATE_COMPLETED")
115
+ terminal = true;
116
+ for (const artifact of task.artifacts ?? [])
117
+ for (const part of artifact.parts)
118
+ yield options.redactor?.redact(part.text) ?? part.text;
119
+ }
120
+ }
121
+ if (!terminal)
122
+ throw new A2AError("A2A stream ended before terminal task state", 502, "ERR_PRISM_A2A_REMOTE");
123
+ if (buffered.trim())
124
+ throw new A2AError("Truncated A2A stream", 502, "ERR_PRISM_A2A_REMOTE");
125
+ }
126
+ finally {
127
+ await reader?.cancel().catch(() => undefined);
128
+ owned.dispose();
129
+ active -= 1;
130
+ }
131
+ }
132
+ async function getCardWithin(signal) {
133
+ const response = await fetcher(cardUrl, { method: "GET", signal, redirect: "error", headers: { accept: "application/a2a+json, application/json" } });
134
+ if (!response.ok)
135
+ throw new A2AError("A2A card request failed", response.status, "ERR_PRISM_A2A_REMOTE");
136
+ const card = parseCard(await readBoundedJson(response, limits.maxCardBytes, signal));
137
+ if (!card.supportedInterfaces.some((item) => item.protocolBinding === "JSONRPC" && item.protocolVersion === A2A_PROTOCOL_VERSION && item.url === endpoint.href))
138
+ throw new A2AError("Agent card does not declare the selected endpoint", 403, "ERR_PRISM_A2A_CARD");
139
+ if (options.verifyCard)
140
+ await abortable(Promise.resolve(options.verifyCard(card)), signal);
141
+ return card;
142
+ }
143
+ return { getCard, send, stream };
144
+ }
145
+ function requestBody(id, method, input) {
146
+ return { jsonrpc: "2.0", id, method, params: { message: { role: "user", messageId: `message-${id}`, parts: [{ text: input }] } } };
147
+ }
148
+ function taskResult(task, options) {
149
+ if (task.status.state === "TASK_STATE_SUBMITTED" || task.status.state === "TASK_STATE_WORKING")
150
+ throw new A2AError("A2A response task is not terminal", 502, "ERR_PRISM_A2A_REMOTE");
151
+ const text = (task.artifacts ?? []).flatMap((artifact) => artifact.parts.map((part) => part.text)).join("");
152
+ const safeText = options.redactor?.redact(text) ?? text;
153
+ const status = task.status.state === "TASK_STATE_COMPLETED" ? "succeeded" : task.status.state === "TASK_STATE_CANCELED" ? "aborted" : "failed";
154
+ const content = safeText ? [{ type: "text", text: safeText }] : [];
155
+ const message = safeText ? { role: "assistant", content } : undefined;
156
+ 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 });
157
+ }
158
+ function parseTaskResult(value) {
159
+ if (!isRecord(value) || !isRecord(value.task))
160
+ throw new A2AError("Malformed A2A task result", 502, "ERR_PRISM_A2A_REMOTE");
161
+ const task = value.task;
162
+ if (typeof task.id !== "string" || typeof task.contextId !== "string" || !isRecord(task.status) || typeof task.status.state !== "string")
163
+ throw new A2AError("Malformed A2A task", 502, "ERR_PRISM_A2A_REMOTE");
164
+ const states = new Set(["TASK_STATE_SUBMITTED", "TASK_STATE_WORKING", "TASK_STATE_COMPLETED", "TASK_STATE_FAILED", "TASK_STATE_CANCELED"]);
165
+ if (!states.has(task.status.state))
166
+ throw new A2AError("Unknown A2A task state", 502, "ERR_PRISM_A2A_REMOTE");
167
+ const artifacts = task.artifacts === undefined ? undefined : parseArtifacts(task.artifacts);
168
+ 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 };
169
+ }
170
+ function parseArtifacts(value) {
171
+ if (!Array.isArray(value) || value.length > 32)
172
+ throw new A2AError("Malformed A2A artifacts", 502, "ERR_PRISM_A2A_REMOTE");
173
+ return value.map((artifact) => {
174
+ if (!isRecord(artifact) || typeof artifact.artifactId !== "string" || !Array.isArray(artifact.parts) || artifact.parts.length > 32)
175
+ throw new A2AError("Malformed A2A artifact", 502, "ERR_PRISM_A2A_REMOTE");
176
+ return { artifactId: artifact.artifactId, parts: artifact.parts.map((part) => {
177
+ if (!isRecord(part) || typeof part.text !== "string")
178
+ throw new A2AError("Unsupported A2A artifact part", 502, "ERR_PRISM_A2A_REMOTE");
179
+ return { text: part.text };
180
+ }) };
181
+ });
182
+ }
183
+ function parseRpcResponse(value, id) {
184
+ if (!isRecord(value) || value.jsonrpc !== "2.0" || value.id !== id)
185
+ throw new A2AError("Malformed A2A JSON-RPC response", 502, "ERR_PRISM_A2A_REMOTE");
186
+ const error = value.error;
187
+ if (error !== undefined && (!isRecord(error) || typeof error.code !== "number" || typeof error.message !== "string"))
188
+ throw new A2AError("Malformed A2A JSON-RPC error", 502, "ERR_PRISM_A2A_REMOTE");
189
+ return { jsonrpc: "2.0", id, result: value.result, error: error };
190
+ }
191
+ function parseCard(value) {
192
+ 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")
193
+ throw new A2AError("Malformed A2A agent card", 502, "ERR_PRISM_A2A_CARD");
194
+ const supportedInterfaces = value.supportedInterfaces.map((item) => {
195
+ if (!isRecord(item) || typeof item.url !== "string" || item.protocolBinding !== "JSONRPC" || item.protocolVersion !== "1.0")
196
+ throw new A2AError("Malformed A2A agent interface", 502, "ERR_PRISM_A2A_CARD");
197
+ return { url: item.url, protocolBinding: "JSONRPC", protocolVersion: "1.0" };
198
+ });
199
+ const skills = value.skills.map((skill) => {
200
+ if (!isRecord(skill) || typeof skill.id !== "string" || typeof skill.name !== "string" || typeof skill.description !== "string" || !stringArray(skill.tags))
201
+ throw new A2AError("Malformed A2A agent skill", 502, "ERR_PRISM_A2A_CARD");
202
+ 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 };
203
+ });
204
+ const signatures = value.signatures === undefined ? undefined : Array.isArray(value.signatures) ? value.signatures.map((signature) => {
205
+ if (!isRecord(signature) || typeof signature.protected !== "string" || typeof signature.signature !== "string")
206
+ throw new A2AError("Malformed A2A card signature", 502, "ERR_PRISM_A2A_CARD");
207
+ return { protected: signature.protected, signature: signature.signature, header: isRecord(signature.header) ? signature.header : undefined };
208
+ }) : (() => { throw new A2AError("Malformed A2A card signatures", 502, "ERR_PRISM_A2A_CARD"); })();
209
+ return createA2AAgentCard({
210
+ name: value.name,
211
+ description: value.description,
212
+ version: value.version,
213
+ supportedInterfaces,
214
+ 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 },
215
+ defaultInputModes: value.defaultInputModes,
216
+ defaultOutputModes: value.defaultOutputModes,
217
+ skills,
218
+ securitySchemes: isRecord(value.securitySchemes) ? value.securitySchemes : undefined,
219
+ security: parseSecurity(value.security),
220
+ signatures,
221
+ });
222
+ }
223
+ async function readBoundedJson(response, maxBytes, signal) {
224
+ const contentType = response.headers.get("content-type");
225
+ if (contentType && !contentType.includes("json"))
226
+ throw new A2AError("Unexpected A2A response content type", 502, "ERR_PRISM_A2A_REMOTE");
227
+ if (!response.body)
228
+ throw new A2AError("A2A response body is missing", 502, "ERR_PRISM_A2A_REMOTE");
229
+ const reader = response.body.getReader();
230
+ const chunks = [];
231
+ let size = 0;
232
+ try {
233
+ while (true) {
234
+ signal.throwIfAborted();
235
+ const next = await reader.read();
236
+ if (next.done)
237
+ break;
238
+ size += next.value.byteLength;
239
+ if (size > maxBytes)
240
+ throw new A2AError("A2A response exceeds max bytes", 507, "ERR_PRISM_A2A_RESPONSE_LIMIT");
241
+ chunks.push(next.value);
242
+ }
243
+ }
244
+ finally {
245
+ reader.releaseLock();
246
+ }
247
+ const bytes = new Uint8Array(size);
248
+ let offset = 0;
249
+ for (const chunk of chunks) {
250
+ bytes.set(chunk, offset);
251
+ offset += chunk.byteLength;
252
+ }
253
+ try {
254
+ return JSON.parse(new TextDecoder().decode(bytes));
255
+ }
256
+ catch {
257
+ throw new A2AError("Malformed A2A JSON response", 502, "ERR_PRISM_A2A_REMOTE");
258
+ }
259
+ }
260
+ function requireAllowedHttpsUrl(value, origins) {
261
+ const url = new URL(value);
262
+ if (url.protocol !== "https:" || !origins.includes(url.origin))
263
+ throw new A2AError("A2A endpoint origin is not allow-listed HTTPS", 403, "ERR_PRISM_A2A_ORIGIN");
264
+ return url;
265
+ }
266
+ function clientLimits(input = {}) {
267
+ const output = {};
268
+ for (const key of Object.keys(DEFAULTS)) {
269
+ const value = input[key] ?? DEFAULTS[key];
270
+ if (!Number.isSafeInteger(value) || value < 1 || value > HARD[key])
271
+ throw new A2AError(`${key} is invalid`, 400, "ERR_PRISM_A2A_CONFIG");
272
+ output[key] = value;
273
+ }
274
+ return output;
275
+ }
276
+ function ownedSignal(parent, timeoutMs) {
277
+ const controller = new AbortController();
278
+ const abort = () => controller.abort(parent?.reason);
279
+ if (parent?.aborted)
280
+ abort();
281
+ else
282
+ parent?.addEventListener("abort", abort, { once: true });
283
+ const timer = setTimeout(() => controller.abort(new DOMException("A2A request timed out", "AbortError")), timeoutMs);
284
+ return { signal: controller.signal, dispose: () => { clearTimeout(timer); parent?.removeEventListener("abort", abort); } };
285
+ }
286
+ function abortable(promise, signal) {
287
+ if (signal.aborted)
288
+ return Promise.reject(signal.reason);
289
+ return new Promise((resolve, reject) => {
290
+ const abort = () => reject(signal.reason);
291
+ signal.addEventListener("abort", abort, { once: true });
292
+ promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
293
+ });
294
+ }
295
+ function assertInput(input, maxBytes) { if (new TextEncoder().encode(input).byteLength > maxBytes)
296
+ throw new A2AError("A2A input exceeds max bytes", 413, "ERR_PRISM_A2A_REQUEST_LIMIT"); }
297
+ function headersObject(headers) { return Object.fromEntries(new Headers(headers).entries()); }
298
+ function isRecord(value) { return !!value && typeof value === "object" && !Array.isArray(value); }
299
+ function stringArray(value) { return Array.isArray(value) && value.every((item) => typeof item === "string"); }
300
+ function parseSecurity(value) {
301
+ if (value === undefined)
302
+ return undefined;
303
+ if (!Array.isArray(value))
304
+ throw new A2AError("Malformed A2A card security", 502, "ERR_PRISM_A2A_CARD");
305
+ return value.map((entry) => {
306
+ if (!isRecord(entry) || !Object.values(entry).every(stringArray))
307
+ throw new A2AError("Malformed A2A card security", 502, "ERR_PRISM_A2A_CARD");
308
+ return entry;
309
+ });
310
+ }
311
+ function safeRemote(message, options) { return options.redactor?.redact(message.slice(0, 1024)) ?? message.slice(0, 1024); }
312
+ //# sourceMappingURL=a2a-client.js.map
@@ -0,0 +1,2 @@
1
+ import type { CreateA2AHandlerOptions } from "./a2a-types.js";
2
+ export declare function createA2AHandler(options: CreateA2AHandlerOptions): (request: Request) => Promise<Response>;