@assistant-ui/react-a2a 0.2.29 → 0.2.30

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 +1 @@
1
- {"version":3,"file":"conversions.js","names":[],"sources":["../src/conversions.ts"],"sourcesContent":["\"use client\";\n\nimport type { MessageStatus, ThreadAssistantMessage } from \"@assistant-ui/core\";\nimport { httpUrlPattern, parseDataUrl } from \"@assistant-ui/core/internal\";\nimport type { A2AMessage, A2APart, A2ATaskState } from \"./types\";\n\nfunction isImageMediaType(mediaType?: string): boolean {\n return !!mediaType && mediaType.startsWith(\"image/\");\n}\n\nexport function a2aPartToContent(\n part: A2APart,\n): ThreadAssistantMessage[\"content\"][number] {\n if (part.text !== undefined) {\n return { type: \"text\", text: part.text };\n }\n if (part.url !== undefined) {\n if (isImageMediaType(part.mediaType)) {\n return {\n type: \"image\",\n image: part.url,\n ...(part.filename && { filename: part.filename }),\n };\n }\n return {\n type: \"file\",\n data: part.url,\n mimeType: part.mediaType ?? \"application/octet-stream\",\n sourceType: \"url\",\n ...(part.filename && { filename: part.filename }),\n };\n }\n if (part.raw !== undefined) {\n if (isImageMediaType(part.mediaType)) {\n return {\n type: \"image\",\n image: `data:${part.mediaType};base64,${part.raw}`,\n ...(part.filename && { filename: part.filename }),\n };\n }\n return {\n type: \"file\",\n data: part.raw,\n mimeType: part.mediaType ?? \"application/octet-stream\",\n ...(part.filename && { filename: part.filename }),\n };\n }\n if (part.data !== undefined) {\n return { type: \"text\", text: JSON.stringify(part.data, null, 2) };\n }\n return { type: \"text\", text: \"\" };\n}\n\nexport function a2aPartsToContent(\n parts: A2APart[],\n): ThreadAssistantMessage[\"content\"] {\n return parts.map(a2aPartToContent);\n}\n\nconst TERMINAL_STATES = new Set<A2ATaskState>([\n \"completed\",\n \"failed\",\n \"canceled\",\n \"rejected\",\n]);\n\nconst INTERRUPTED_STATES = new Set<A2ATaskState>([\n \"input_required\",\n \"auth_required\",\n]);\n\nexport function isTerminalTaskState(state: A2ATaskState): boolean {\n return TERMINAL_STATES.has(state);\n}\n\nexport function isInterruptedTaskState(state: A2ATaskState): boolean {\n return INTERRUPTED_STATES.has(state);\n}\n\nexport function taskStateToMessageStatus(state: A2ATaskState): MessageStatus {\n switch (state) {\n case \"submitted\":\n case \"working\":\n return { type: \"running\" };\n case \"completed\":\n return { type: \"complete\", reason: \"stop\" };\n case \"failed\":\n case \"rejected\":\n return { type: \"incomplete\", reason: \"error\" };\n case \"canceled\":\n return { type: \"incomplete\", reason: \"cancelled\" };\n case \"input_required\":\n case \"auth_required\":\n return { type: \"requires-action\", reason: \"interrupt\" };\n default:\n return { type: \"running\" };\n }\n}\n\nexport function contentPartsToA2AParts(\n content: ReadonlyArray<{\n type: string;\n text?: string | undefined;\n image?: string | undefined;\n data?: unknown;\n mimeType?: string | undefined;\n filename?: string | undefined;\n sourceType?: \"url\" | \"id\" | undefined;\n audio?: { data: string; format: string } | undefined;\n }>,\n fallbackMimeType?: string,\n): A2APart[] {\n return content\n .map((part): A2APart | null => {\n switch (part.type) {\n case \"text\":\n return { text: part.text ?? \"\" };\n case \"image\": {\n if (!part.image) return null;\n const parsed = parseDataUrl(part.image);\n if (parsed) {\n return {\n raw: parsed.data,\n mediaType: parsed.mimeType,\n ...(part.filename && { filename: part.filename }),\n };\n }\n return {\n url: part.image,\n ...(fallbackMimeType && { mediaType: fallbackMimeType }),\n ...(part.filename && { filename: part.filename }),\n };\n }\n case \"file\": {\n if (typeof part.data !== \"string\" || !part.data) return null;\n const declaredMimeType = part.mimeType || fallbackMimeType;\n if (part.sourceType === \"url\" || httpUrlPattern.test(part.data)) {\n return {\n url: part.data,\n ...(declaredMimeType && { mediaType: declaredMimeType }),\n ...(part.filename && { filename: part.filename }),\n };\n }\n const parsed = parseDataUrl(part.data);\n if (parsed) {\n return {\n raw: parsed.data,\n mediaType: parsed.mimeType,\n ...(part.filename && { filename: part.filename }),\n };\n }\n if (/^data:/i.test(part.data)) {\n return {\n url: part.data,\n ...(declaredMimeType && { mediaType: declaredMimeType }),\n ...(part.filename && { filename: part.filename }),\n };\n }\n return {\n raw: part.data,\n ...(declaredMimeType && { mediaType: declaredMimeType }),\n ...(part.filename && { filename: part.filename }),\n };\n }\n case \"audio\": {\n if (!part.audio) return null;\n return {\n raw: parseDataUrl(part.audio.data)?.data ?? part.audio.data,\n mediaType: `audio/${part.audio.format}`,\n };\n }\n case \"data\": {\n if (part.data === undefined) return null;\n return { data: part.data };\n }\n default:\n return null;\n }\n })\n .filter((p): p is A2APart => p !== null);\n}\n\nexport function a2aMessageToContent(\n message: A2AMessage,\n): ThreadAssistantMessage[\"content\"] {\n return a2aPartsToContent(message.parts);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"conversions.js","names":[],"sources":["../src/conversions.ts"],"sourcesContent":["\"use client\";\n\nimport type { MessageStatus, ThreadAssistantMessage } from \"@assistant-ui/core\";\nimport { httpUrlPattern, parseDataUrl } from \"@assistant-ui/core/internal\";\nimport type { A2AMessage, A2APart, A2ATaskState } from \"./types\";\n\nfunction isImageMediaType(mediaType?: string): boolean {\n return !!mediaType && mediaType.startsWith(\"image/\");\n}\n\nexport function a2aPartToContent(\n part: A2APart,\n): ThreadAssistantMessage[\"content\"][number] {\n if (part.text !== undefined) {\n return { type: \"text\", text: part.text };\n }\n if (part.url !== undefined) {\n if (isImageMediaType(part.mediaType)) {\n return {\n type: \"image\",\n image: part.url,\n ...(part.filename && { filename: part.filename }),\n };\n }\n return {\n type: \"file\",\n data: part.url,\n mimeType: part.mediaType ?? \"application/octet-stream\",\n sourceType: \"url\",\n ...(part.filename && { filename: part.filename }),\n };\n }\n if (part.raw !== undefined) {\n if (isImageMediaType(part.mediaType)) {\n return {\n type: \"image\",\n image: `data:${part.mediaType};base64,${part.raw}`,\n ...(part.filename && { filename: part.filename }),\n };\n }\n return {\n type: \"file\",\n data: part.raw,\n mimeType: part.mediaType ?? \"application/octet-stream\",\n ...(part.filename && { filename: part.filename }),\n };\n }\n if (part.data !== undefined) {\n return { type: \"text\", text: JSON.stringify(part.data, null, 2) };\n }\n return { type: \"text\", text: \"\" };\n}\n\nexport function a2aPartsToContent(\n parts: A2APart[],\n): ThreadAssistantMessage[\"content\"] {\n return (Array.isArray(parts) ? parts : []).map(a2aPartToContent);\n}\n\nconst TERMINAL_STATES = new Set<A2ATaskState>([\n \"completed\",\n \"failed\",\n \"canceled\",\n \"rejected\",\n]);\n\nconst INTERRUPTED_STATES = new Set<A2ATaskState>([\n \"input_required\",\n \"auth_required\",\n]);\n\nexport function isTerminalTaskState(state: A2ATaskState): boolean {\n return TERMINAL_STATES.has(state);\n}\n\nexport function isInterruptedTaskState(state: A2ATaskState): boolean {\n return INTERRUPTED_STATES.has(state);\n}\n\nexport function taskStateToMessageStatus(state: A2ATaskState): MessageStatus {\n switch (state) {\n case \"submitted\":\n case \"working\":\n return { type: \"running\" };\n case \"completed\":\n return { type: \"complete\", reason: \"stop\" };\n case \"failed\":\n case \"rejected\":\n return { type: \"incomplete\", reason: \"error\" };\n case \"canceled\":\n return { type: \"incomplete\", reason: \"cancelled\" };\n case \"input_required\":\n case \"auth_required\":\n return { type: \"requires-action\", reason: \"interrupt\" };\n default:\n return { type: \"running\" };\n }\n}\n\nexport function contentPartsToA2AParts(\n content: ReadonlyArray<{\n type: string;\n text?: string | undefined;\n image?: string | undefined;\n data?: unknown;\n mimeType?: string | undefined;\n filename?: string | undefined;\n sourceType?: \"url\" | \"id\" | undefined;\n audio?: { data: string; format: string } | undefined;\n }>,\n fallbackMimeType?: string,\n): A2APart[] {\n return content\n .map((part): A2APart | null => {\n switch (part.type) {\n case \"text\":\n return { text: part.text ?? \"\" };\n case \"image\": {\n if (!part.image) return null;\n const parsed = parseDataUrl(part.image);\n if (parsed) {\n return {\n raw: parsed.data,\n mediaType: parsed.mimeType,\n ...(part.filename && { filename: part.filename }),\n };\n }\n return {\n url: part.image,\n ...(fallbackMimeType && { mediaType: fallbackMimeType }),\n ...(part.filename && { filename: part.filename }),\n };\n }\n case \"file\": {\n if (typeof part.data !== \"string\" || !part.data) return null;\n const declaredMimeType = part.mimeType || fallbackMimeType;\n if (part.sourceType === \"url\" || httpUrlPattern.test(part.data)) {\n return {\n url: part.data,\n ...(declaredMimeType && { mediaType: declaredMimeType }),\n ...(part.filename && { filename: part.filename }),\n };\n }\n const parsed = parseDataUrl(part.data);\n if (parsed) {\n return {\n raw: parsed.data,\n mediaType: parsed.mimeType,\n ...(part.filename && { filename: part.filename }),\n };\n }\n if (/^data:/i.test(part.data)) {\n return {\n url: part.data,\n ...(declaredMimeType && { mediaType: declaredMimeType }),\n ...(part.filename && { filename: part.filename }),\n };\n }\n return {\n raw: part.data,\n ...(declaredMimeType && { mediaType: declaredMimeType }),\n ...(part.filename && { filename: part.filename }),\n };\n }\n case \"audio\": {\n if (!part.audio) return null;\n return {\n raw: parseDataUrl(part.audio.data)?.data ?? part.audio.data,\n mediaType: `audio/${part.audio.format}`,\n };\n }\n case \"data\": {\n if (part.data === undefined) return null;\n return { data: part.data };\n }\n default:\n return null;\n }\n })\n .filter((p): p is A2APart => p !== null);\n}\n\nexport function a2aMessageToContent(\n message: A2AMessage,\n): ThreadAssistantMessage[\"content\"] {\n return a2aPartsToContent(message?.parts ?? []);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react-a2a",
3
- "version": "0.2.29",
3
+ "version": "0.2.30",
4
4
  "description": "A2A (Agent-to-Agent) v1.0 protocol adapter for assistant-ui",
5
5
  "keywords": [
6
6
  "a2a",
@@ -29,9 +29,9 @@
29
29
  ],
30
30
  "sideEffects": false,
31
31
  "dependencies": {
32
- "@assistant-ui/core": "^0.3.13",
33
- "@assistant-ui/store": "^0.3.9",
34
- "assistant-stream": "^0.3.36"
32
+ "@assistant-ui/core": "^0.3.14",
33
+ "@assistant-ui/store": "^0.3.10",
34
+ "assistant-stream": "^0.3.38"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "@types/react": "*",
@@ -50,7 +50,7 @@
50
50
  "react": "^19.2.8",
51
51
  "react-dom": "^19.2.8",
52
52
  "vitest": "^4.1.10",
53
- "@assistant-ui/x-buildutils": "0.0.22"
53
+ "@assistant-ui/x-buildutils": "0.0.23"
54
54
  },
55
55
  "publishConfig": {
56
56
  "access": "public",
@@ -734,6 +734,14 @@ describe("A2AClient", () => {
734
734
  const [url] = fetchMock.mock.calls[0]!;
735
735
  expect(url).toBe("https://agent.test/tasks/t1?history_length=5");
736
736
  });
737
+
738
+ it("rejects malformed successful responses", async () => {
739
+ fetchMock.mockResolvedValue(mockFetchResponse({}));
740
+
741
+ await expect(client.getTask("t1")).rejects.toThrow(
742
+ "Invalid A2A tasks:get response: expected a valid task payload.",
743
+ );
744
+ });
737
745
  });
738
746
 
739
747
  // --- listTasks ---
@@ -761,6 +769,65 @@ describe("A2AClient", () => {
761
769
  expect(url).toContain("status=TASK_STATE_WORKING");
762
770
  expect(url).toContain("page_size=10");
763
771
  });
772
+
773
+ it("rejects malformed successful responses", async () => {
774
+ fetchMock.mockResolvedValue(mockFetchResponse({ tasks: 42 }));
775
+
776
+ await expect(client.listTasks()).rejects.toThrow(
777
+ "Invalid A2A tasks:list response: expected a valid task list payload.",
778
+ );
779
+ });
780
+
781
+ it("normalizes empty responses", async () => {
782
+ fetchMock.mockResolvedValue(mockFetchResponse({}));
783
+
784
+ await expect(client.listTasks()).resolves.toEqual({
785
+ tasks: [],
786
+ nextPageToken: "",
787
+ pageSize: 0,
788
+ totalSize: 0,
789
+ });
790
+ });
791
+
792
+ it("rejects malformed tasks in successful responses", async () => {
793
+ fetchMock.mockResolvedValue(
794
+ mockFetchResponse({
795
+ tasks: [{}],
796
+ nextPageToken: "",
797
+ pageSize: 1,
798
+ totalSize: 1,
799
+ }),
800
+ );
801
+
802
+ await expect(client.listTasks()).rejects.toThrow(
803
+ "Invalid A2A tasks:list response: expected a valid task list payload.",
804
+ );
805
+ });
806
+
807
+ it("normalizes omitted pagination defaults", async () => {
808
+ const tasks = [{ id: "t1", status: { state: "completed" } }];
809
+ fetchMock.mockResolvedValue(mockFetchResponse({ tasks }));
810
+
811
+ await expect(client.listTasks()).resolves.toEqual({
812
+ tasks,
813
+ nextPageToken: "",
814
+ pageSize: 0,
815
+ totalSize: 0,
816
+ });
817
+ });
818
+
819
+ it("rejects malformed pagination fields", async () => {
820
+ fetchMock.mockResolvedValue(
821
+ mockFetchResponse({
822
+ tasks: [],
823
+ nextPageToken: 42,
824
+ }),
825
+ );
826
+
827
+ await expect(client.listTasks()).rejects.toThrow(
828
+ "Invalid A2A tasks:list response: expected a valid task list payload.",
829
+ );
830
+ });
764
831
  });
765
832
 
766
833
  // --- cancelTask ---
@@ -788,6 +855,14 @@ describe("A2AClient", () => {
788
855
  const body = JSON.parse(fetchMock.mock.calls[0]![1].body);
789
856
  expect(body.metadata).toEqual({ reason: "user requested" });
790
857
  });
858
+
859
+ it("rejects malformed successful responses", async () => {
860
+ fetchMock.mockResolvedValue(mockFetchResponse({}));
861
+
862
+ await expect(client.cancelTask("t1")).rejects.toThrow(
863
+ "Invalid A2A tasks:cancel response: expected a valid task payload.",
864
+ );
865
+ });
791
866
  });
792
867
 
793
868
  // --- getAgentCard ---
@@ -1221,12 +1296,13 @@ describe("A2AClient", () => {
1221
1296
  expect(events).toHaveLength(1);
1222
1297
  });
1223
1298
 
1224
- it("rejects successful responses that are not event streams", async () => {
1299
+ it("rejects and cancels successful responses that are not event streams", async () => {
1300
+ const cancel = vi.fn();
1301
+ const body = new ReadableStream<Uint8Array>({ cancel });
1225
1302
  fetchMock.mockResolvedValue(
1226
- mockSSETextResponse(
1227
- "<html><body>Please sign in</body></html>",
1228
- "text/html; charset=utf-8",
1229
- ),
1303
+ new Response(body, {
1304
+ headers: { "Content-Type": "text/html; charset=utf-8" },
1305
+ }),
1230
1306
  );
1231
1307
 
1232
1308
  const consumeStream = async () => {
@@ -1238,10 +1314,13 @@ describe("A2AClient", () => {
1238
1314
  await expect(consumeStream()).rejects.toThrow(
1239
1315
  'Expected A2A stream response Content-Type "text/event-stream", received "text/html; charset=utf-8"',
1240
1316
  );
1317
+ expect(cancel).toHaveBeenCalledOnce();
1241
1318
  });
1242
1319
 
1243
1320
  it("rejects task subscriptions without a content type", async () => {
1244
- fetchMock.mockResolvedValue(mockSSETextResponse("", null));
1321
+ fetchMock.mockResolvedValue(
1322
+ new Response(new ReadableStream<Uint8Array>()),
1323
+ );
1245
1324
 
1246
1325
  const consumeStream = async () => {
1247
1326
  for await (const event of client.subscribeToTask("t1")) {
@@ -1322,9 +1401,34 @@ describe("A2AClient", () => {
1322
1401
  expect((evt.event.status.message as any)?.content).toBeUndefined();
1323
1402
  });
1324
1403
 
1325
- it("skips malformed SSE events", async () => {
1404
+ it("skips malformed and unrecognized SSE events", async () => {
1405
+ const first = JSON.stringify({
1406
+ status_update: {
1407
+ task_id: "t1",
1408
+ context_id: "ctx-1",
1409
+ status: { state: "TASK_STATE_WORKING" },
1410
+ },
1411
+ });
1412
+ const second = JSON.stringify({
1413
+ status_update: {
1414
+ task_id: "t1",
1415
+ context_id: "ctx-1",
1416
+ status: { state: "TASK_STATE_COMPLETED" },
1417
+ },
1418
+ });
1419
+
1326
1420
  fetchMock.mockResolvedValue(
1327
- mockSSEResponse(["data: {invalid json}", "", ""]),
1421
+ mockSSEResponse([
1422
+ `data: ${first}`,
1423
+ "",
1424
+ "data: {invalid json}",
1425
+ "",
1426
+ "data: {}",
1427
+ "",
1428
+ `data: ${second}`,
1429
+ "",
1430
+ "",
1431
+ ]),
1328
1432
  );
1329
1433
 
1330
1434
  const events: A2AStreamEvent[] = [];
@@ -1332,7 +1436,11 @@ describe("A2AClient", () => {
1332
1436
  events.push(event);
1333
1437
  }
1334
1438
 
1335
- expect(events).toHaveLength(0);
1439
+ expect(events).toHaveLength(2);
1440
+ expect(events.map((event) => event.type)).toEqual([
1441
+ "statusUpdate",
1442
+ "statusUpdate",
1443
+ ]);
1336
1444
  });
1337
1445
  });
1338
1446
 
@@ -1403,6 +1511,112 @@ describe("A2AClient", () => {
1403
1511
  expect(url).toBe("https://agent.test/tasks/t1/pushNotificationConfigs");
1404
1512
  });
1405
1513
 
1514
+ it.each([
1515
+ [
1516
+ "pushNotificationConfigs:create",
1517
+ () =>
1518
+ client.createTaskPushNotificationConfig({
1519
+ taskId: "t1",
1520
+ url: "https://hook.test",
1521
+ }),
1522
+ ],
1523
+ [
1524
+ "pushNotificationConfigs:get",
1525
+ () => client.getTaskPushNotificationConfig("t1", "pnc-1"),
1526
+ ],
1527
+ ])("rejects malformed %s responses", async (operation, request) => {
1528
+ fetchMock.mockResolvedValue(mockFetchResponse({}));
1529
+
1530
+ await expect(request()).rejects.toThrow(
1531
+ `Invalid A2A ${operation} response`,
1532
+ );
1533
+ });
1534
+
1535
+ it.each([
1536
+ { configs: {} },
1537
+ { configs: [{}] },
1538
+ { configs: [], nextPageToken: 42 },
1539
+ ])("rejects malformed list responses", async (response) => {
1540
+ fetchMock.mockResolvedValue(mockFetchResponse(response));
1541
+
1542
+ await expect(
1543
+ client.listTaskPushNotificationConfigs("t1"),
1544
+ ).rejects.toThrow("Invalid A2A pushNotificationConfigs:list response");
1545
+ });
1546
+
1547
+ it.each([{}, { configs: null, nextPageToken: null }])(
1548
+ "normalizes omitted ProtoJSON list defaults",
1549
+ async (response) => {
1550
+ fetchMock.mockResolvedValue(mockFetchResponse(response));
1551
+
1552
+ await expect(
1553
+ client.listTaskPushNotificationConfigs("t1"),
1554
+ ).resolves.toEqual({
1555
+ configs: [],
1556
+ });
1557
+ },
1558
+ );
1559
+
1560
+ it("normalizes an omitted authentication scheme", async () => {
1561
+ fetchMock.mockResolvedValue(
1562
+ mockFetchResponse({
1563
+ url: "https://hook.test",
1564
+ authentication: { credentials: "secret" },
1565
+ }),
1566
+ );
1567
+
1568
+ await expect(
1569
+ client.getTaskPushNotificationConfig("t1", "pnc-1"),
1570
+ ).resolves.toEqual({
1571
+ url: "https://hook.test",
1572
+ authentication: { scheme: "", credentials: "secret" },
1573
+ });
1574
+ });
1575
+
1576
+ it("rejects malformed nested authentication responses", async () => {
1577
+ fetchMock.mockResolvedValue(
1578
+ mockFetchResponse({
1579
+ url: "https://hook.test",
1580
+ authentication: { scheme: 42 },
1581
+ }),
1582
+ );
1583
+
1584
+ await expect(
1585
+ client.getTaskPushNotificationConfig("t1", "pnc-1"),
1586
+ ).rejects.toThrow("Invalid A2A pushNotificationConfigs:get response");
1587
+ });
1588
+
1589
+ it("accepts valid nested authentication responses", async () => {
1590
+ const config = {
1591
+ url: "https://hook.test",
1592
+ authentication: { scheme: "Bearer", credentials: "secret" },
1593
+ };
1594
+ fetchMock.mockResolvedValue(mockFetchResponse(config));
1595
+
1596
+ await expect(
1597
+ client.getTaskPushNotificationConfig("t1", "pnc-1"),
1598
+ ).resolves.toEqual(config);
1599
+ });
1600
+
1601
+ it("normalizes nullable optional config fields", async () => {
1602
+ fetchMock.mockResolvedValue(
1603
+ mockFetchResponse({
1604
+ tenant: null,
1605
+ id: null,
1606
+ taskId: null,
1607
+ url: "https://hook.test",
1608
+ token: null,
1609
+ authentication: null,
1610
+ }),
1611
+ );
1612
+
1613
+ await expect(
1614
+ client.getTaskPushNotificationConfig("t1", "pnc-1"),
1615
+ ).resolves.toEqual({
1616
+ url: "https://hook.test",
1617
+ });
1618
+ });
1619
+
1406
1620
  it("deleteTaskPushNotificationConfig sends DELETE", async () => {
1407
1621
  fetchMock.mockResolvedValue({
1408
1622
  ok: true,
package/src/A2AClient.ts CHANGED
@@ -272,6 +272,142 @@ const parseSendMessageResponse = (value: unknown): A2ATask | A2AMessage => {
272
272
  );
273
273
  };
274
274
 
275
+ const parseTaskResponse = (
276
+ value: unknown,
277
+ operation: "tasks:get" | "tasks:cancel",
278
+ ): A2ATask => {
279
+ if (isTask(value)) return value;
280
+
281
+ throw new Error(
282
+ `Invalid A2A ${operation} response: expected a valid task payload.`,
283
+ );
284
+ };
285
+
286
+ const isNonNegativeInteger = (value: unknown): value is number =>
287
+ typeof value === "number" && Number.isInteger(value) && value >= 0;
288
+
289
+ const invalidListTasksResponse = (): never => {
290
+ throw new Error(
291
+ "Invalid A2A tasks:list response: expected a valid task list payload.",
292
+ );
293
+ };
294
+
295
+ const parseListTasksResponse = (value: unknown): A2AListTasksResponse => {
296
+ if (!isRecord(value)) return invalidListTasksResponse();
297
+
298
+ const tasks = value.tasks ?? [];
299
+ if (!Array.isArray(tasks) || !tasks.every(isTask)) {
300
+ return invalidListTasksResponse();
301
+ }
302
+
303
+ const { nextPageToken, pageSize, totalSize } = value;
304
+ if (
305
+ (nextPageToken != null && typeof nextPageToken !== "string") ||
306
+ (pageSize != null && !isNonNegativeInteger(pageSize)) ||
307
+ (totalSize != null && !isNonNegativeInteger(totalSize))
308
+ ) {
309
+ return invalidListTasksResponse();
310
+ }
311
+
312
+ return {
313
+ ...value,
314
+ tasks,
315
+ nextPageToken: nextPageToken ?? "",
316
+ pageSize: pageSize ?? 0,
317
+ totalSize: totalSize ?? 0,
318
+ };
319
+ };
320
+
321
+ const invalidPushNotificationConfigResponse =
322
+ (
323
+ operation: "pushNotificationConfigs:create" | "pushNotificationConfigs:get",
324
+ ) =>
325
+ (): never => {
326
+ throw new Error(
327
+ `Invalid A2A ${operation} response: expected a valid push notification config payload.`,
328
+ );
329
+ };
330
+
331
+ const invalidListPushNotificationConfigsResponse = (): never => {
332
+ throw new Error(
333
+ "Invalid A2A pushNotificationConfigs:list response: expected a valid push notification config list payload.",
334
+ );
335
+ };
336
+
337
+ const parseOptionalString = (
338
+ value: unknown,
339
+ invalid: () => never,
340
+ ): string | undefined =>
341
+ value == null ? undefined : typeof value === "string" ? value : invalid();
342
+
343
+ const parseTaskPushNotificationConfigResponse = (
344
+ value: unknown,
345
+ invalid: () => never,
346
+ ): A2ATaskPushNotificationConfig => {
347
+ if (
348
+ !isRecord(value) ||
349
+ typeof value.url !== "string" ||
350
+ value.url.length === 0
351
+ ) {
352
+ return invalid();
353
+ }
354
+
355
+ const { tenant, id, taskId, url, token, authentication, ...extra } = value;
356
+ let normalizedAuthentication: A2ATaskPushNotificationConfig["authentication"];
357
+ if (authentication != null) {
358
+ if (!isRecord(authentication)) return invalid();
359
+ const { scheme, credentials, ...authenticationExtra } = authentication;
360
+ normalizedAuthentication = {
361
+ ...authenticationExtra,
362
+ scheme: parseOptionalString(scheme, invalid) ?? "",
363
+ ...(credentials == null
364
+ ? {}
365
+ : { credentials: parseOptionalString(credentials, invalid) }),
366
+ };
367
+ }
368
+
369
+ return {
370
+ ...extra,
371
+ ...(tenant == null ? {} : { tenant: parseOptionalString(tenant, invalid) }),
372
+ ...(id == null ? {} : { id: parseOptionalString(id, invalid) }),
373
+ ...(taskId == null ? {} : { taskId: parseOptionalString(taskId, invalid) }),
374
+ url,
375
+ ...(token == null ? {} : { token: parseOptionalString(token, invalid) }),
376
+ ...(normalizedAuthentication === undefined
377
+ ? {}
378
+ : { authentication: normalizedAuthentication }),
379
+ };
380
+ };
381
+
382
+ const parseListTaskPushNotificationConfigsResponse = (
383
+ value: unknown,
384
+ ): A2AListTaskPushNotificationConfigsResponse => {
385
+ if (!isRecord(value)) return invalidListPushNotificationConfigsResponse();
386
+
387
+ const { configs: rawConfigs, nextPageToken, ...extra } = value;
388
+ if (rawConfigs != null && !Array.isArray(rawConfigs)) {
389
+ return invalidListPushNotificationConfigsResponse();
390
+ }
391
+
392
+ return {
393
+ ...extra,
394
+ configs: (rawConfigs ?? []).map((config) =>
395
+ parseTaskPushNotificationConfigResponse(
396
+ config,
397
+ invalidListPushNotificationConfigsResponse,
398
+ ),
399
+ ),
400
+ ...(nextPageToken == null
401
+ ? {}
402
+ : {
403
+ nextPageToken: parseOptionalString(
404
+ nextPageToken,
405
+ invalidListPushNotificationConfigsResponse,
406
+ ),
407
+ }),
408
+ };
409
+ };
410
+
275
411
  function signalInit(signal?: AbortSignal): RequestInit {
276
412
  return signal ? { signal } : {};
277
413
  }
@@ -474,10 +610,11 @@ export class A2AClient {
474
610
  params.set("history_length", String(historyLength));
475
611
  }
476
612
  const qs = params.toString();
477
- return this.fetchJSON<A2ATask>(
613
+ const result = await this.fetchJSON<unknown>(
478
614
  `${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}${qs ? `?${qs}` : ""}`,
479
615
  signalInit(signal),
480
616
  );
617
+ return parseTaskResponse(result, "tasks:get");
481
618
  }
482
619
 
483
620
  async listTasks(
@@ -497,10 +634,11 @@ export class A2AClient {
497
634
  if (request?.includeArtifacts !== undefined)
498
635
  params.set("include_artifacts", String(request.includeArtifacts));
499
636
  const qs = params.toString();
500
- return this.fetchJSON<A2AListTasksResponse>(
637
+ const result = await this.fetchJSON<unknown>(
501
638
  `${this.getBasePath()}/tasks${qs ? `?${qs}` : ""}`,
502
639
  signalInit(signal),
503
640
  );
641
+ return parseListTasksResponse(result);
504
642
  }
505
643
 
506
644
  async cancelTask(
@@ -509,7 +647,7 @@ export class A2AClient {
509
647
  signal?: AbortSignal,
510
648
  ): Promise<A2ATask> {
511
649
  const body = metadata ? { metadata } : {};
512
- return this.fetchJSON<A2ATask>(
650
+ const result = await this.fetchJSON<unknown>(
513
651
  `${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}:cancel`,
514
652
  {
515
653
  method: "POST",
@@ -517,6 +655,7 @@ export class A2AClient {
517
655
  ...signalInit(signal),
518
656
  },
519
657
  );
658
+ return parseTaskResponse(result, "tasks:cancel");
520
659
  }
521
660
 
522
661
  async *subscribeToTask(
@@ -549,7 +688,7 @@ export class A2AClient {
549
688
  ): Promise<A2ATaskPushNotificationConfig> {
550
689
  const taskId = config.taskId;
551
690
  if (!taskId) throw new Error("taskId is required");
552
- return this.fetchJSON<A2ATaskPushNotificationConfig>(
691
+ const result = await this.fetchJSON<unknown>(
553
692
  `${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}/pushNotificationConfigs`,
554
693
  {
555
694
  method: "POST",
@@ -557,6 +696,10 @@ export class A2AClient {
557
696
  ...signalInit(signal),
558
697
  },
559
698
  );
699
+ return parseTaskPushNotificationConfigResponse(
700
+ result,
701
+ invalidPushNotificationConfigResponse("pushNotificationConfigs:create"),
702
+ );
560
703
  }
561
704
 
562
705
  async getTaskPushNotificationConfig(
@@ -564,10 +707,14 @@ export class A2AClient {
564
707
  configId: string,
565
708
  signal?: AbortSignal,
566
709
  ): Promise<A2ATaskPushNotificationConfig> {
567
- return this.fetchJSON<A2ATaskPushNotificationConfig>(
710
+ const result = await this.fetchJSON<unknown>(
568
711
  `${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}/pushNotificationConfigs/${encodeURIComponent(configId)}`,
569
712
  signalInit(signal),
570
713
  );
714
+ return parseTaskPushNotificationConfigResponse(
715
+ result,
716
+ invalidPushNotificationConfigResponse("pushNotificationConfigs:get"),
717
+ );
571
718
  }
572
719
 
573
720
  async listTaskPushNotificationConfigs(
@@ -580,10 +727,11 @@ export class A2AClient {
580
727
  params.set("page_size", String(options.pageSize));
581
728
  if (options?.pageToken) params.set("page_token", options.pageToken);
582
729
  const qs = params.toString();
583
- return this.fetchJSON<A2AListTaskPushNotificationConfigsResponse>(
730
+ const result = await this.fetchJSON<unknown>(
584
731
  `${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}/pushNotificationConfigs${qs ? `?${qs}` : ""}`,
585
732
  signalInit(signal),
586
733
  );
734
+ return parseListTaskPushNotificationConfigsResponse(result);
587
735
  }
588
736
 
589
737
  async deleteTaskPushNotificationConfig(
@@ -616,6 +764,7 @@ export class A2AClient {
616
764
  const received = contentType
617
765
  ? `"${contentType}"`
618
766
  : "no Content-Type header";
767
+ void response.body?.cancel().catch(() => undefined);
619
768
  throw new Error(
620
769
  `Expected A2A stream response Content-Type "text/event-stream", received ${received}`,
621
770
  );