@arnilo/prism-supervisor 0.0.14 → 0.0.16
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 +11 -0
- package/dist/a2a-card.js +19 -7
- package/dist/a2a-client.js +278 -50
- package/dist/a2a-parts.js +109 -19
- package/dist/a2a-push.js +10 -2
- package/dist/a2a-server.js +218 -84
- package/dist/errors.js +12 -3
- package/dist/limits.js +4 -1
- package/dist/supervisor.js +51 -25
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
package/dist/a2a-card.js
CHANGED
|
@@ -44,9 +44,7 @@ export async function verifyA2AAgentCard(card, options) {
|
|
|
44
44
|
break;
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
-
catch {
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
47
|
+
catch { }
|
|
50
48
|
}
|
|
51
49
|
if (!matched)
|
|
52
50
|
throw new A2AError("Agent card signature is invalid or expired", 403, "ERR_PRISM_A2A_CARD_SIGNATURE");
|
|
@@ -71,13 +69,19 @@ function validateCard(card) {
|
|
|
71
69
|
throw new A2AError("Agent card exceeds collection/byte limits", 400, "ERR_PRISM_A2A_CARD");
|
|
72
70
|
if (!card.name?.trim() || !card.description?.trim() || !card.version?.trim())
|
|
73
71
|
throw new A2AError("Agent card identity is incomplete", 400, "ERR_PRISM_A2A_CARD");
|
|
74
|
-
if (!card.supportedInterfaces.length ||
|
|
72
|
+
if (!card.supportedInterfaces.length ||
|
|
73
|
+
!card.supportedInterfaces.every((item) => item.protocolBinding === "JSONRPC" && item.protocolVersion === "1.0" && isHttpsUrl(item.url)))
|
|
75
74
|
throw new A2AError("Agent card requires an HTTPS JSONRPC 1.0 interface", 400, "ERR_PRISM_A2A_CARD");
|
|
76
75
|
if (!card.defaultInputModes.includes("text/plain") || !card.defaultOutputModes.includes("text/plain"))
|
|
77
76
|
throw new A2AError("Agent card must support text/plain", 400, "ERR_PRISM_A2A_CARD");
|
|
78
77
|
const ids = new Set();
|
|
79
78
|
for (const skill of card.skills) {
|
|
80
|
-
if (!skill.id.trim() ||
|
|
79
|
+
if (!skill.id.trim() ||
|
|
80
|
+
!skill.name.trim() ||
|
|
81
|
+
!skill.description.trim() ||
|
|
82
|
+
ids.has(skill.id) ||
|
|
83
|
+
skill.tags.length > 64 ||
|
|
84
|
+
[skill.id, skill.name, skill.description, ...skill.tags].some((value) => Buffer.byteLength(value) > 16 * 1024))
|
|
81
85
|
throw new A2AError("Agent card skill is invalid", 400, "ERR_PRISM_A2A_CARD");
|
|
82
86
|
ids.add(skill.id);
|
|
83
87
|
}
|
|
@@ -87,7 +91,11 @@ function parseProtected(value) {
|
|
|
87
91
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
88
92
|
throw new Error("invalid protected header");
|
|
89
93
|
const record = parsed;
|
|
90
|
-
if (typeof record.alg !== "string" ||
|
|
94
|
+
if (typeof record.alg !== "string" ||
|
|
95
|
+
typeof record.typ !== "string" ||
|
|
96
|
+
typeof record.kid !== "string" ||
|
|
97
|
+
typeof record.iat !== "string" ||
|
|
98
|
+
typeof record.exp !== "string")
|
|
91
99
|
throw new Error("invalid protected header");
|
|
92
100
|
return { alg: record.alg, typ: record.typ, kid: record.kid, iat: record.iat, exp: record.exp };
|
|
93
101
|
}
|
|
@@ -103,7 +111,11 @@ function canonicalJson(value) {
|
|
|
103
111
|
return `[${value.map(canonicalJson).join(",")}]`;
|
|
104
112
|
if (value && typeof value === "object") {
|
|
105
113
|
const record = value;
|
|
106
|
-
return `{${Object.keys(record)
|
|
114
|
+
return `{${Object.keys(record)
|
|
115
|
+
.filter((key) => record[key] !== undefined)
|
|
116
|
+
.sort()
|
|
117
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`)
|
|
118
|
+
.join(",")}}`;
|
|
107
119
|
}
|
|
108
120
|
throw new A2AError("Agent card is not canonical JSON", 400, "ERR_PRISM_A2A_CARD");
|
|
109
121
|
}
|
package/dist/a2a-client.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createA2AAgentCard } from "./a2a-card.js";
|
|
2
2
|
import { resolveA2ALimits } from "./a2a-parts.js";
|
|
3
|
+
import { A2A_PROTOCOL_VERSION, } from "./a2a-types.js";
|
|
3
4
|
import { A2AError } from "./errors.js";
|
|
4
|
-
import { A2A_PROTOCOL_VERSION } from "./a2a-types.js";
|
|
5
5
|
export function createA2AClient(options) {
|
|
6
6
|
const endpoint = requireAllowedHttpsUrl(options.endpoint, options.allowedOrigins);
|
|
7
7
|
const cardUrl = requireAllowedHttpsUrl(options.cardUrl ?? `${endpoint.origin}/.well-known/agent-card.json`, options.allowedOrigins);
|
|
@@ -24,7 +24,12 @@ export function createA2AClient(options) {
|
|
|
24
24
|
}
|
|
25
25
|
async function getCard(call = {}) {
|
|
26
26
|
return withRequest(call.signal, async (signal) => {
|
|
27
|
-
const response = await fetcher(cardUrl, {
|
|
27
|
+
const response = await fetcher(cardUrl, {
|
|
28
|
+
method: "GET",
|
|
29
|
+
signal,
|
|
30
|
+
redirect: "error",
|
|
31
|
+
headers: { accept: "application/a2a+json, application/json" },
|
|
32
|
+
});
|
|
28
33
|
if (!response.ok)
|
|
29
34
|
throw new A2AError("A2A card request failed", response.status, "ERR_PRISM_A2A_REMOTE");
|
|
30
35
|
const value = await readBoundedJson(response, limits.maxCardBytes, signal);
|
|
@@ -45,7 +50,18 @@ export function createA2AClient(options) {
|
|
|
45
50
|
if (new TextEncoder().encode(body).byteLength > limits.maxRequestBytes)
|
|
46
51
|
throw new A2AError("A2A request exceeds max bytes", 413, "ERR_PRISM_A2A_REQUEST_LIMIT");
|
|
47
52
|
const authHeaders = await abortable(Promise.resolve(options.authorize?.({ endpoint: endpoint.href, signal }) ?? {}), signal);
|
|
48
|
-
const response = await fetcher(endpoint, {
|
|
53
|
+
const response = await fetcher(endpoint, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
signal,
|
|
56
|
+
redirect: "error",
|
|
57
|
+
headers: {
|
|
58
|
+
...headersObject(authHeaders),
|
|
59
|
+
"content-type": "application/a2a+json",
|
|
60
|
+
accept: "application/a2a+json",
|
|
61
|
+
"a2a-version": "1.0",
|
|
62
|
+
},
|
|
63
|
+
body,
|
|
64
|
+
});
|
|
49
65
|
if (!response.ok)
|
|
50
66
|
throw new A2AError("A2A remote request failed", response.status, "ERR_PRISM_A2A_REMOTE");
|
|
51
67
|
const rpc = parseRpcResponse(await readBoundedJson(response, limits.maxResponseBytes, signal), id);
|
|
@@ -68,7 +84,18 @@ export function createA2AClient(options) {
|
|
|
68
84
|
if (new TextEncoder().encode(body).byteLength > limits.maxRequestBytes)
|
|
69
85
|
throw new A2AError("A2A request exceeds max bytes", 413, "ERR_PRISM_A2A_REQUEST_LIMIT");
|
|
70
86
|
const authHeaders = await abortable(Promise.resolve(options.authorize?.({ endpoint: endpoint.href, signal: owned.signal }) ?? {}), owned.signal);
|
|
71
|
-
const response = await fetcher(endpoint, {
|
|
87
|
+
const response = await fetcher(endpoint, {
|
|
88
|
+
method: "POST",
|
|
89
|
+
signal: owned.signal,
|
|
90
|
+
redirect: "error",
|
|
91
|
+
headers: {
|
|
92
|
+
...headersObject(authHeaders),
|
|
93
|
+
"content-type": "application/a2a+json",
|
|
94
|
+
accept: "text/event-stream",
|
|
95
|
+
"a2a-version": "1.0",
|
|
96
|
+
},
|
|
97
|
+
body,
|
|
98
|
+
});
|
|
72
99
|
if (!response.ok || !response.body || !response.headers.get("content-type")?.startsWith("text/event-stream"))
|
|
73
100
|
throw new A2AError("A2A stream request failed", response.status, "ERR_PRISM_A2A_REMOTE");
|
|
74
101
|
reader = response.body.getReader();
|
|
@@ -89,7 +116,9 @@ export function createA2AClient(options) {
|
|
|
89
116
|
if (rpc.error)
|
|
90
117
|
throw new A2AError(safeRemote(rpc.error.message, options), 502, "ERR_PRISM_A2A_REMOTE");
|
|
91
118
|
const task = parseTaskResult(rpc.result);
|
|
92
|
-
if (task.status.state === "TASK_STATE_FAILED" ||
|
|
119
|
+
if (task.status.state === "TASK_STATE_FAILED" ||
|
|
120
|
+
task.status.state === "TASK_STATE_CANCELED" ||
|
|
121
|
+
task.status.state === "TASK_STATE_REJECTED")
|
|
93
122
|
throw new A2AError("Remote A2A stream task failed", 502, "ERR_PRISM_A2A_REMOTE");
|
|
94
123
|
if (task.status.state === "TASK_STATE_INPUT_REQUIRED" || task.status.state === "TASK_STATE_AUTH_REQUIRED")
|
|
95
124
|
throw new A2AError(`Remote A2A task interrupted: ${task.status.state}`, 409, "ERR_PRISM_A2A_INTERRUPTED");
|
|
@@ -110,7 +139,12 @@ export function createA2AClient(options) {
|
|
|
110
139
|
}
|
|
111
140
|
}
|
|
112
141
|
async function getCardWithin(signal) {
|
|
113
|
-
const response = await fetcher(cardUrl, {
|
|
142
|
+
const response = await fetcher(cardUrl, {
|
|
143
|
+
method: "GET",
|
|
144
|
+
signal,
|
|
145
|
+
redirect: "error",
|
|
146
|
+
headers: { accept: "application/a2a+json, application/json" },
|
|
147
|
+
});
|
|
114
148
|
if (!response.ok)
|
|
115
149
|
throw new A2AError("A2A card request failed", response.status, "ERR_PRISM_A2A_REMOTE");
|
|
116
150
|
const card = parseCard(await readBoundedJson(response, limits.maxCardBytes, signal));
|
|
@@ -128,7 +162,18 @@ export function createA2AClient(options) {
|
|
|
128
162
|
if (Buffer.byteLength(body) > limits.maxRequestBytes)
|
|
129
163
|
throw new A2AError("A2A request exceeds max bytes", 413, "ERR_PRISM_A2A_REQUEST_LIMIT");
|
|
130
164
|
const authHeaders = await abortable(Promise.resolve(options.authorize?.({ endpoint: endpoint.href, signal: owned }) ?? {}), owned);
|
|
131
|
-
const response = await fetcher(endpoint, {
|
|
165
|
+
const response = await fetcher(endpoint, {
|
|
166
|
+
method: "POST",
|
|
167
|
+
signal: owned,
|
|
168
|
+
redirect: "error",
|
|
169
|
+
headers: {
|
|
170
|
+
...headersObject(authHeaders),
|
|
171
|
+
"content-type": "application/a2a+json",
|
|
172
|
+
accept: "application/a2a+json",
|
|
173
|
+
"a2a-version": "1.0",
|
|
174
|
+
},
|
|
175
|
+
body,
|
|
176
|
+
});
|
|
132
177
|
if (!response.ok)
|
|
133
178
|
throw new A2AError("A2A remote request failed", response.status, "ERR_PRISM_A2A_REMOTE");
|
|
134
179
|
const rpc = parseRpcResponse(await readBoundedJson(response, limits.maxResponseBytes, owned), id);
|
|
@@ -140,14 +185,22 @@ export function createA2AClient(options) {
|
|
|
140
185
|
async function sendMessage(message, call = {}) {
|
|
141
186
|
return parseTaskResult(await invoke("SendMessage", { message, configuration: { returnImmediately: call.returnImmediately ?? false } }, call.signal));
|
|
142
187
|
}
|
|
143
|
-
async function getTask(id, call = {}) {
|
|
188
|
+
async function getTask(id, call = {}) {
|
|
189
|
+
return parseTaskResult(await invoke("GetTask", { id, historyLength: call.historyLength ?? 0 }, call.signal));
|
|
190
|
+
}
|
|
144
191
|
async function listTasks(call = {}) {
|
|
145
192
|
const value = await invoke("ListTasks", { pageSize: call.pageSize ?? 50, pageToken: call.pageToken, contextId: call.contextId }, call.signal);
|
|
146
193
|
if (!isRecord(value) || !Array.isArray(value.tasks) || value.tasks.length > limits.maxPageSize)
|
|
147
194
|
throw new A2AError("Malformed A2A task page", 502, "ERR_PRISM_A2A_REMOTE");
|
|
148
|
-
return {
|
|
195
|
+
return {
|
|
196
|
+
tasks: value.tasks.map((task) => parseTaskResult(task)),
|
|
197
|
+
nextPageToken: typeof value.nextPageToken === "string" ? value.nextPageToken : undefined,
|
|
198
|
+
totalSize: typeof value.totalSize === "number" ? value.totalSize : undefined,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
async function cancelTask(id, call = {}) {
|
|
202
|
+
return parseTaskResult(await invoke("CancelTask", { id }, call.signal));
|
|
149
203
|
}
|
|
150
|
-
async function cancelTask(id, call = {}) { return parseTaskResult(await invoke("CancelTask", { id }, call.signal)); }
|
|
151
204
|
async function* subscribeToTask(id, call = {}) {
|
|
152
205
|
if (active >= limits.maxConcurrentRequests)
|
|
153
206
|
throw new A2AError("A2A client concurrency exceeded", 429, "ERR_PRISM_A2A_CONCURRENCY");
|
|
@@ -158,7 +211,18 @@ export function createA2AClient(options) {
|
|
|
158
211
|
await getCardWithin(owned.signal);
|
|
159
212
|
const request = ++requestId;
|
|
160
213
|
const authHeaders = await abortable(Promise.resolve(options.authorize?.({ endpoint: endpoint.href, signal: owned.signal }) ?? {}), owned.signal);
|
|
161
|
-
const response = await fetcher(endpoint, {
|
|
214
|
+
const response = await fetcher(endpoint, {
|
|
215
|
+
method: "POST",
|
|
216
|
+
signal: owned.signal,
|
|
217
|
+
redirect: "error",
|
|
218
|
+
headers: {
|
|
219
|
+
...headersObject(authHeaders),
|
|
220
|
+
"content-type": "application/a2a+json",
|
|
221
|
+
accept: "text/event-stream",
|
|
222
|
+
"a2a-version": "1.0",
|
|
223
|
+
},
|
|
224
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: request, method: "SubscribeToTask", params: { id, afterEventId: call.afterEventId } }),
|
|
225
|
+
});
|
|
162
226
|
if (!response.ok || !response.body || !response.headers.get("content-type")?.startsWith("text/event-stream"))
|
|
163
227
|
throw new A2AError("A2A subscribe request failed", response.status, "ERR_PRISM_A2A_REMOTE");
|
|
164
228
|
reader = response.body.getReader();
|
|
@@ -182,12 +246,38 @@ export function createA2AClient(options) {
|
|
|
182
246
|
active -= 1;
|
|
183
247
|
}
|
|
184
248
|
}
|
|
185
|
-
async function createPushConfig(config, call = {}) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
249
|
+
async function createPushConfig(config, call = {}) {
|
|
250
|
+
return parsePushConfig(await invoke("CreateTaskPushNotificationConfig", { ...config }, call.signal));
|
|
251
|
+
}
|
|
252
|
+
async function getPushConfig(taskId, id, call = {}) {
|
|
253
|
+
return parsePushConfig(await invoke("GetTaskPushNotificationConfig", { taskId, id }, call.signal));
|
|
254
|
+
}
|
|
255
|
+
async function listPushConfigs(taskId, call = {}) {
|
|
256
|
+
const value = await invoke("ListTaskPushNotificationConfigs", { taskId, pageSize: call.pageSize, pageToken: call.pageToken }, call.signal);
|
|
257
|
+
if (!isRecord(value) || !Array.isArray(value.configs))
|
|
258
|
+
throw new A2AError("Malformed A2A push config page", 502, "ERR_PRISM_A2A_REMOTE");
|
|
259
|
+
return {
|
|
260
|
+
configs: value.configs.map(parsePushConfig),
|
|
261
|
+
nextPageToken: typeof value.nextPageToken === "string" ? value.nextPageToken : undefined,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
async function deletePushConfig(taskId, id, call = {}) {
|
|
265
|
+
await invoke("DeleteTaskPushNotificationConfig", { taskId, id }, call.signal);
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
getCard,
|
|
269
|
+
send,
|
|
270
|
+
sendMessage,
|
|
271
|
+
stream,
|
|
272
|
+
getTask,
|
|
273
|
+
listTasks,
|
|
274
|
+
cancelTask,
|
|
275
|
+
subscribeToTask,
|
|
276
|
+
createPushConfig,
|
|
277
|
+
getPushConfig,
|
|
278
|
+
listPushConfigs,
|
|
279
|
+
deletePushConfig,
|
|
280
|
+
};
|
|
191
281
|
}
|
|
192
282
|
async function* readA2AStreamData(reader, limits, signal) {
|
|
193
283
|
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
@@ -295,12 +385,21 @@ function taskResult(task, options) {
|
|
|
295
385
|
throw new A2AError("A2A response task is not terminal", 502, "ERR_PRISM_A2A_REMOTE");
|
|
296
386
|
if (task.status.state === "TASK_STATE_INPUT_REQUIRED" || task.status.state === "TASK_STATE_AUTH_REQUIRED")
|
|
297
387
|
throw new A2AError(`Remote A2A task interrupted: ${task.status.state}`, 409, "ERR_PRISM_A2A_INTERRUPTED");
|
|
298
|
-
const text = (task.artifacts ?? []).flatMap((artifact) => artifact.parts.flatMap((part) => "text" in part ? [part.text] : [])).join("");
|
|
388
|
+
const text = (task.artifacts ?? []).flatMap((artifact) => artifact.parts.flatMap((part) => ("text" in part ? [part.text] : []))).join("");
|
|
299
389
|
const safeText = options.redactor?.redact(text) ?? text;
|
|
300
390
|
const status = task.status.state === "TASK_STATE_COMPLETED" ? "succeeded" : task.status.state === "TASK_STATE_CANCELED" ? "aborted" : "failed";
|
|
301
391
|
const content = safeText ? [{ type: "text", text: safeText }] : [];
|
|
302
392
|
const message = safeText ? { role: "assistant", content } : undefined;
|
|
303
|
-
return Object.freeze({
|
|
393
|
+
return Object.freeze({
|
|
394
|
+
sessionId: task.contextId,
|
|
395
|
+
runId: task.id,
|
|
396
|
+
status,
|
|
397
|
+
text: safeText,
|
|
398
|
+
content,
|
|
399
|
+
message,
|
|
400
|
+
error: status === "failed" ? { message: "Remote A2A task failed" } : undefined,
|
|
401
|
+
abortReason: status === "aborted" ? "Remote A2A task canceled" : undefined,
|
|
402
|
+
});
|
|
304
403
|
}
|
|
305
404
|
function parseTaskResult(value) {
|
|
306
405
|
if (!isRecord(value))
|
|
@@ -308,12 +407,36 @@ function parseTaskResult(value) {
|
|
|
308
407
|
const task = isRecord(value.task) ? value.task : value;
|
|
309
408
|
if (typeof task.id !== "string" || typeof task.contextId !== "string" || !isRecord(task.status) || typeof task.status.state !== "string")
|
|
310
409
|
throw new A2AError("Malformed A2A task", 502, "ERR_PRISM_A2A_REMOTE");
|
|
311
|
-
const states = new Set([
|
|
410
|
+
const states = new Set([
|
|
411
|
+
"TASK_STATE_SUBMITTED",
|
|
412
|
+
"TASK_STATE_WORKING",
|
|
413
|
+
"TASK_STATE_COMPLETED",
|
|
414
|
+
"TASK_STATE_FAILED",
|
|
415
|
+
"TASK_STATE_CANCELED",
|
|
416
|
+
"TASK_STATE_INPUT_REQUIRED",
|
|
417
|
+
"TASK_STATE_REJECTED",
|
|
418
|
+
"TASK_STATE_AUTH_REQUIRED",
|
|
419
|
+
]);
|
|
312
420
|
if (!states.has(task.status.state))
|
|
313
421
|
throw new A2AError("Unknown A2A task state", 502, "ERR_PRISM_A2A_REMOTE");
|
|
314
422
|
const artifacts = task.artifacts === undefined ? undefined : parseArtifacts(task.artifacts);
|
|
315
|
-
const history = task.history === undefined
|
|
316
|
-
|
|
423
|
+
const history = task.history === undefined
|
|
424
|
+
? undefined
|
|
425
|
+
: Array.isArray(task.history)
|
|
426
|
+
? task.history.map(parseRemoteMessage)
|
|
427
|
+
: (() => {
|
|
428
|
+
throw new A2AError("Malformed A2A task history", 502, "ERR_PRISM_A2A_REMOTE");
|
|
429
|
+
})();
|
|
430
|
+
return {
|
|
431
|
+
id: task.id,
|
|
432
|
+
contextId: task.contextId,
|
|
433
|
+
status: {
|
|
434
|
+
state: task.status.state,
|
|
435
|
+
timestamp: typeof task.status.timestamp === "string" ? task.status.timestamp : new Date(0).toISOString(),
|
|
436
|
+
},
|
|
437
|
+
artifacts,
|
|
438
|
+
history,
|
|
439
|
+
};
|
|
317
440
|
}
|
|
318
441
|
function parseArtifacts(value) {
|
|
319
442
|
if (!Array.isArray(value) || value.length > 32)
|
|
@@ -330,10 +453,15 @@ function parseRemotePart(value) {
|
|
|
330
453
|
const keys = ["text", "raw", "url", "data"].filter((key) => Object.hasOwn(value, key));
|
|
331
454
|
if (keys.length !== 1)
|
|
332
455
|
throw new A2AError("Malformed A2A part union", 502, "ERR_PRISM_A2A_REMOTE");
|
|
333
|
-
const base = {
|
|
456
|
+
const base = {
|
|
457
|
+
mediaType: typeof value.mediaType === "string" ? value.mediaType : undefined,
|
|
458
|
+
filename: typeof value.filename === "string" ? value.filename : undefined,
|
|
459
|
+
};
|
|
334
460
|
if (keys[0] === "text" && typeof value.text === "string")
|
|
335
461
|
return { ...base, text: value.text };
|
|
336
|
-
if (keys[0] === "raw" &&
|
|
462
|
+
if (keys[0] === "raw" &&
|
|
463
|
+
typeof value.raw === "string" &&
|
|
464
|
+
/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(value.raw))
|
|
337
465
|
return { ...base, raw: value.raw };
|
|
338
466
|
if (keys[0] === "url" && typeof value.url === "string") {
|
|
339
467
|
const url = new URL(value.url);
|
|
@@ -345,25 +473,74 @@ function parseRemotePart(value) {
|
|
|
345
473
|
return { ...base, data: structuredClone(value.data) };
|
|
346
474
|
throw new A2AError("Malformed A2A part", 502, "ERR_PRISM_A2A_REMOTE");
|
|
347
475
|
}
|
|
348
|
-
function parseRemoteMessage(value) {
|
|
349
|
-
|
|
476
|
+
function parseRemoteMessage(value) {
|
|
477
|
+
if (!isRecord(value) ||
|
|
478
|
+
typeof value.messageId !== "string" ||
|
|
479
|
+
!Array.isArray(value.parts) ||
|
|
480
|
+
(value.role !== "ROLE_USER" && value.role !== "ROLE_AGENT" && value.role !== "user" && value.role !== "agent"))
|
|
481
|
+
throw new A2AError("Malformed A2A message", 502, "ERR_PRISM_A2A_REMOTE");
|
|
482
|
+
return {
|
|
483
|
+
role: value.role,
|
|
484
|
+
messageId: value.messageId,
|
|
485
|
+
parts: value.parts.map(parseRemotePart),
|
|
486
|
+
contextId: typeof value.contextId === "string" ? value.contextId : undefined,
|
|
487
|
+
taskId: typeof value.taskId === "string" ? value.taskId : undefined,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
350
490
|
function parseTaskEvent(value) {
|
|
351
491
|
if (!isRecord(value) || typeof value.eventId !== "string" || !value.eventId)
|
|
352
492
|
throw new A2AError("Malformed A2A task event", 502, "ERR_PRISM_A2A_REMOTE");
|
|
353
493
|
if (isRecord(value.task))
|
|
354
494
|
return { eventId: value.eventId, task: parseTaskResult(value.task) };
|
|
355
|
-
if (isRecord(value.statusUpdate) &&
|
|
356
|
-
|
|
495
|
+
if (isRecord(value.statusUpdate) &&
|
|
496
|
+
typeof value.statusUpdate.taskId === "string" &&
|
|
497
|
+
typeof value.statusUpdate.contextId === "string" &&
|
|
498
|
+
isRecord(value.statusUpdate.status)) {
|
|
499
|
+
const parsed = parseTaskResult({
|
|
500
|
+
id: value.statusUpdate.taskId,
|
|
501
|
+
contextId: value.statusUpdate.contextId,
|
|
502
|
+
status: value.statusUpdate.status,
|
|
503
|
+
});
|
|
357
504
|
return { eventId: value.eventId, statusUpdate: { taskId: parsed.id, contextId: parsed.contextId, status: parsed.status } };
|
|
358
505
|
}
|
|
359
|
-
if (isRecord(value.artifactUpdate) &&
|
|
360
|
-
|
|
506
|
+
if (isRecord(value.artifactUpdate) &&
|
|
507
|
+
typeof value.artifactUpdate.taskId === "string" &&
|
|
508
|
+
typeof value.artifactUpdate.contextId === "string" &&
|
|
509
|
+
isRecord(value.artifactUpdate.artifact))
|
|
510
|
+
return {
|
|
511
|
+
eventId: value.eventId,
|
|
512
|
+
artifactUpdate: {
|
|
513
|
+
taskId: value.artifactUpdate.taskId,
|
|
514
|
+
contextId: value.artifactUpdate.contextId,
|
|
515
|
+
artifact: parseArtifacts([value.artifactUpdate.artifact])[0],
|
|
516
|
+
append: value.artifactUpdate.append === true,
|
|
517
|
+
lastChunk: value.artifactUpdate.lastChunk === true,
|
|
518
|
+
},
|
|
519
|
+
};
|
|
361
520
|
throw new A2AError("Malformed A2A task event", 502, "ERR_PRISM_A2A_REMOTE");
|
|
362
521
|
}
|
|
363
|
-
function parsePushConfig(value) {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
522
|
+
function parsePushConfig(value) {
|
|
523
|
+
if (!isRecord(value) || typeof value.id !== "string" || typeof value.taskId !== "string" || typeof value.url !== "string")
|
|
524
|
+
throw new A2AError("Malformed A2A push config", 502, "ERR_PRISM_A2A_REMOTE");
|
|
525
|
+
const url = new URL(value.url);
|
|
526
|
+
if (url.protocol !== "https:" || url.username || url.password || url.hash)
|
|
527
|
+
throw new A2AError("Unsafe A2A push URL", 502, "ERR_PRISM_A2A_REMOTE");
|
|
528
|
+
return {
|
|
529
|
+
id: value.id,
|
|
530
|
+
taskId: value.taskId,
|
|
531
|
+
url: url.href,
|
|
532
|
+
token: typeof value.token === "string" ? value.token : undefined,
|
|
533
|
+
authentication: isRecord(value.authentication) && typeof value.authentication.scheme === "string"
|
|
534
|
+
? {
|
|
535
|
+
scheme: value.authentication.scheme,
|
|
536
|
+
credentials: typeof value.authentication.credentials === "string" ? value.authentication.credentials : undefined,
|
|
537
|
+
}
|
|
538
|
+
: undefined,
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
function remoteProtocolError(code, message, options) {
|
|
542
|
+
return new A2AError(safeRemote(message, options), code === -32001 ? 404 : code === -32004 ? 501 : 502, code === -32001 ? "ERR_PRISM_A2A_TASK_NOT_FOUND" : code === -32004 ? "ERR_PRISM_A2A_UNSUPPORTED" : "ERR_PRISM_A2A_REMOTE");
|
|
543
|
+
}
|
|
367
544
|
function parseRpcResponse(value, id) {
|
|
368
545
|
if (!isRecord(value) || value.jsonrpc !== "2.0" || value.id !== id)
|
|
369
546
|
throw new A2AError("Malformed A2A JSON-RPC response", 502, "ERR_PRISM_A2A_REMOTE");
|
|
@@ -373,7 +550,16 @@ function parseRpcResponse(value, id) {
|
|
|
373
550
|
return { jsonrpc: "2.0", id, result: value.result, error: error };
|
|
374
551
|
}
|
|
375
552
|
function parseCard(value) {
|
|
376
|
-
if (!isRecord(value) ||
|
|
553
|
+
if (!isRecord(value) ||
|
|
554
|
+
typeof value.name !== "string" ||
|
|
555
|
+
typeof value.description !== "string" ||
|
|
556
|
+
typeof value.version !== "string" ||
|
|
557
|
+
!Array.isArray(value.supportedInterfaces) ||
|
|
558
|
+
!Array.isArray(value.skills) ||
|
|
559
|
+
!stringArray(value.defaultInputModes) ||
|
|
560
|
+
!stringArray(value.defaultOutputModes) ||
|
|
561
|
+
!isRecord(value.capabilities) ||
|
|
562
|
+
typeof value.capabilities.streaming !== "boolean")
|
|
377
563
|
throw new A2AError("Malformed A2A agent card", 502, "ERR_PRISM_A2A_CARD");
|
|
378
564
|
const supportedInterfaces = value.supportedInterfaces.map((item) => {
|
|
379
565
|
if (!isRecord(item) || typeof item.url !== "string" || item.protocolBinding !== "JSONRPC" || item.protocolVersion !== "1.0")
|
|
@@ -381,21 +567,47 @@ function parseCard(value) {
|
|
|
381
567
|
return { url: item.url, protocolBinding: "JSONRPC", protocolVersion: "1.0" };
|
|
382
568
|
});
|
|
383
569
|
const skills = value.skills.map((skill) => {
|
|
384
|
-
if (!isRecord(skill) ||
|
|
570
|
+
if (!isRecord(skill) ||
|
|
571
|
+
typeof skill.id !== "string" ||
|
|
572
|
+
typeof skill.name !== "string" ||
|
|
573
|
+
typeof skill.description !== "string" ||
|
|
574
|
+
!stringArray(skill.tags))
|
|
385
575
|
throw new A2AError("Malformed A2A agent skill", 502, "ERR_PRISM_A2A_CARD");
|
|
386
|
-
return {
|
|
576
|
+
return {
|
|
577
|
+
id: skill.id,
|
|
578
|
+
name: skill.name,
|
|
579
|
+
description: skill.description,
|
|
580
|
+
tags: skill.tags,
|
|
581
|
+
examples: stringArray(skill.examples) ? skill.examples : undefined,
|
|
582
|
+
inputModes: stringArray(skill.inputModes) ? skill.inputModes : undefined,
|
|
583
|
+
outputModes: stringArray(skill.outputModes) ? skill.outputModes : undefined,
|
|
584
|
+
};
|
|
387
585
|
});
|
|
388
|
-
const signatures = value.signatures === undefined
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
586
|
+
const signatures = value.signatures === undefined
|
|
587
|
+
? undefined
|
|
588
|
+
: Array.isArray(value.signatures)
|
|
589
|
+
? value.signatures.map((signature) => {
|
|
590
|
+
if (!isRecord(signature) || typeof signature.protected !== "string" || typeof signature.signature !== "string")
|
|
591
|
+
throw new A2AError("Malformed A2A card signature", 502, "ERR_PRISM_A2A_CARD");
|
|
592
|
+
return {
|
|
593
|
+
protected: signature.protected,
|
|
594
|
+
signature: signature.signature,
|
|
595
|
+
header: isRecord(signature.header) ? signature.header : undefined,
|
|
596
|
+
};
|
|
597
|
+
})
|
|
598
|
+
: (() => {
|
|
599
|
+
throw new A2AError("Malformed A2A card signatures", 502, "ERR_PRISM_A2A_CARD");
|
|
600
|
+
})();
|
|
393
601
|
return createA2AAgentCard({
|
|
394
602
|
name: value.name,
|
|
395
603
|
description: value.description,
|
|
396
604
|
version: value.version,
|
|
397
605
|
supportedInterfaces,
|
|
398
|
-
capabilities: {
|
|
606
|
+
capabilities: {
|
|
607
|
+
streaming: value.capabilities.streaming,
|
|
608
|
+
pushNotifications: typeof value.capabilities.pushNotifications === "boolean" ? value.capabilities.pushNotifications : undefined,
|
|
609
|
+
extendedAgentCard: typeof value.capabilities.extendedAgentCard === "boolean" ? value.capabilities.extendedAgentCard : undefined,
|
|
610
|
+
},
|
|
399
611
|
defaultInputModes: value.defaultInputModes,
|
|
400
612
|
defaultOutputModes: value.defaultOutputModes,
|
|
401
613
|
skills,
|
|
@@ -455,7 +667,13 @@ function ownedSignal(parent, timeoutMs) {
|
|
|
455
667
|
else
|
|
456
668
|
parent?.addEventListener("abort", abort, { once: true });
|
|
457
669
|
const timer = setTimeout(() => controller.abort(new DOMException("A2A request timed out", "AbortError")), timeoutMs);
|
|
458
|
-
return {
|
|
670
|
+
return {
|
|
671
|
+
signal: controller.signal,
|
|
672
|
+
dispose: () => {
|
|
673
|
+
clearTimeout(timer);
|
|
674
|
+
parent?.removeEventListener("abort", abort);
|
|
675
|
+
},
|
|
676
|
+
};
|
|
459
677
|
}
|
|
460
678
|
function abortable(promise, signal) {
|
|
461
679
|
if (signal.aborted)
|
|
@@ -466,11 +684,19 @@ function abortable(promise, signal) {
|
|
|
466
684
|
promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
|
|
467
685
|
});
|
|
468
686
|
}
|
|
469
|
-
function assertInput(input, maxBytes) {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
function
|
|
687
|
+
function assertInput(input, maxBytes) {
|
|
688
|
+
if (new TextEncoder().encode(input).byteLength > maxBytes)
|
|
689
|
+
throw new A2AError("A2A input exceeds max bytes", 413, "ERR_PRISM_A2A_REQUEST_LIMIT");
|
|
690
|
+
}
|
|
691
|
+
function headersObject(headers) {
|
|
692
|
+
return Object.fromEntries(new Headers(headers).entries());
|
|
693
|
+
}
|
|
694
|
+
function isRecord(value) {
|
|
695
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
696
|
+
}
|
|
697
|
+
function stringArray(value) {
|
|
698
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
699
|
+
}
|
|
474
700
|
function parseSecurity(value) {
|
|
475
701
|
if (value === undefined)
|
|
476
702
|
return undefined;
|
|
@@ -482,5 +708,7 @@ function parseSecurity(value) {
|
|
|
482
708
|
return entry;
|
|
483
709
|
});
|
|
484
710
|
}
|
|
485
|
-
function safeRemote(message, options) {
|
|
711
|
+
function safeRemote(message, options) {
|
|
712
|
+
return options.redactor?.redact(message.slice(0, 1024)) ?? message.slice(0, 1024);
|
|
713
|
+
}
|
|
486
714
|
//# sourceMappingURL=a2a-client.js.map
|