@assistant-ui/react-a2a 0.2.28 → 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":";;;AAMA,SAAS,iBAAiB,WAA6B;CACrD,OAAO,CAAC,CAAC,aAAa,UAAU,WAAW,QAAQ;AACrD;AAEA,SAAgB,iBACd,MAC2C;CAC3C,IAAI,KAAK,SAAS,KAAA,GAChB,OAAO;EAAE,MAAM;EAAQ,MAAM,KAAK;CAAK;CAEzC,IAAI,KAAK,QAAQ,KAAA,GAAW;EAC1B,IAAI,iBAAiB,KAAK,SAAS,GACjC,OAAO;GACL,MAAM;GACN,OAAO,KAAK;GACZ,GAAI,KAAK,YAAY,EAAE,UAAU,KAAK,SAAS;EACjD;EAEF,OAAO;GACL,MAAM;GACN,MAAM,KAAK;GACX,UAAU,KAAK,aAAa;GAC5B,YAAY;GACZ,GAAI,KAAK,YAAY,EAAE,UAAU,KAAK,SAAS;EACjD;CACF;CACA,IAAI,KAAK,QAAQ,KAAA,GAAW;EAC1B,IAAI,iBAAiB,KAAK,SAAS,GACjC,OAAO;GACL,MAAM;GACN,OAAO,QAAQ,KAAK,UAAU,UAAU,KAAK;GAC7C,GAAI,KAAK,YAAY,EAAE,UAAU,KAAK,SAAS;EACjD;EAEF,OAAO;GACL,MAAM;GACN,MAAM,KAAK;GACX,UAAU,KAAK,aAAa;GAC5B,GAAI,KAAK,YAAY,EAAE,UAAU,KAAK,SAAS;EACjD;CACF;CACA,IAAI,KAAK,SAAS,KAAA,GAChB,OAAO;EAAE,MAAM;EAAQ,MAAM,KAAK,UAAU,KAAK,MAAM,MAAM,CAAC;CAAE;CAElE,OAAO;EAAE,MAAM;EAAQ,MAAM;CAAG;AAClC;AAEA,SAAgB,kBACd,OACmC;CACnC,QAAQ,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,EAAA,CAAG,IAAI,gBAAgB;AACjE;AAEA,MAAM,kCAAkB,IAAI,IAAkB;CAC5C;CACA;CACA;CACA;AACF,CAAC;AAED,MAAM,qCAAqB,IAAI,IAAkB,CAC/C,kBACA,eACF,CAAC;AAED,SAAgB,oBAAoB,OAA8B;CAChE,OAAO,gBAAgB,IAAI,KAAK;AAClC;AAEA,SAAgB,uBAAuB,OAA8B;CACnE,OAAO,mBAAmB,IAAI,KAAK;AACrC;AAEA,SAAgB,yBAAyB,OAAoC;CAC3E,QAAQ,OAAR;EACE,KAAK;EACL,KAAK,WACH,OAAO,EAAE,MAAM,UAAU;EAC3B,KAAK,aACH,OAAO;GAAE,MAAM;GAAY,QAAQ;EAAO;EAC5C,KAAK;EACL,KAAK,YACH,OAAO;GAAE,MAAM;GAAc,QAAQ;EAAQ;EAC/C,KAAK,YACH,OAAO;GAAE,MAAM;GAAc,QAAQ;EAAY;EACnD,KAAK;EACL,KAAK,iBACH,OAAO;GAAE,MAAM;GAAmB,QAAQ;EAAY;EACxD,SACE,OAAO,EAAE,MAAM,UAAU;CAC7B;AACF;AAEA,SAAgB,uBACd,SAUA,kBACW;CACX,OAAO,QACJ,KAAK,SAAyB;EAC7B,QAAQ,KAAK,MAAb;GACE,KAAK,QACH,OAAO,EAAE,MAAM,KAAK,QAAQ,GAAG;GACjC,KAAK,SAAS;IACZ,IAAI,CAAC,KAAK,OAAO,OAAO;IACxB,MAAM,SAAS,aAAa,KAAK,KAAK;IACtC,IAAI,QACF,OAAO;KACL,KAAK,OAAO;KACZ,WAAW,OAAO;KAClB,GAAI,KAAK,YAAY,EAAE,UAAU,KAAK,SAAS;IACjD;IAEF,OAAO;KACL,KAAK,KAAK;KACV,GAAI,oBAAoB,EAAE,WAAW,iBAAiB;KACtD,GAAI,KAAK,YAAY,EAAE,UAAU,KAAK,SAAS;IACjD;GACF;GACA,KAAK,QAAQ;IACX,IAAI,OAAO,KAAK,SAAS,YAAY,CAAC,KAAK,MAAM,OAAO;IACxD,MAAM,mBAAmB,KAAK,YAAY;IAC1C,IAAI,KAAK,eAAe,SAAS,eAAe,KAAK,KAAK,IAAI,GAC5D,OAAO;KACL,KAAK,KAAK;KACV,GAAI,oBAAoB,EAAE,WAAW,iBAAiB;KACtD,GAAI,KAAK,YAAY,EAAE,UAAU,KAAK,SAAS;IACjD;IAEF,MAAM,SAAS,aAAa,KAAK,IAAI;IACrC,IAAI,QACF,OAAO;KACL,KAAK,OAAO;KACZ,WAAW,OAAO;KAClB,GAAI,KAAK,YAAY,EAAE,UAAU,KAAK,SAAS;IACjD;IAEF,IAAI,UAAU,KAAK,KAAK,IAAI,GAC1B,OAAO;KACL,KAAK,KAAK;KACV,GAAI,oBAAoB,EAAE,WAAW,iBAAiB;KACtD,GAAI,KAAK,YAAY,EAAE,UAAU,KAAK,SAAS;IACjD;IAEF,OAAO;KACL,KAAK,KAAK;KACV,GAAI,oBAAoB,EAAE,WAAW,iBAAiB;KACtD,GAAI,KAAK,YAAY,EAAE,UAAU,KAAK,SAAS;IACjD;GACF;GACA,KAAK;IACH,IAAI,CAAC,KAAK,OAAO,OAAO;IACxB,OAAO;KACL,KAAK,aAAa,KAAK,MAAM,IAAI,CAAC,EAAE,QAAQ,KAAK,MAAM;KACvD,WAAW,SAAS,KAAK,MAAM;IACjC;GAEF,KAAK;IACH,IAAI,KAAK,SAAS,KAAA,GAAW,OAAO;IACpC,OAAO,EAAE,MAAM,KAAK,KAAK;GAE3B,SACE,OAAO;EACX;CACF,CAAC,CAAC,CACD,QAAQ,MAAoB,MAAM,IAAI;AAC3C;AAEA,SAAgB,oBACd,SACmC;CACnC,OAAO,kBAAkB,SAAS,SAAS,CAAC,CAAC;AAC/C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react-a2a",
3
- "version": "0.2.28",
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.10",
33
- "@assistant-ui/store": "^0.3.8",
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",
@@ -460,6 +460,16 @@ describe("A2AClient", () => {
460
460
  },
461
461
  },
462
462
  ],
463
+ [
464
+ "a message with an unknown role",
465
+ {
466
+ message: {
467
+ messageId: "m2",
468
+ role: "banana",
469
+ parts: [{ text: "Hi" }],
470
+ },
471
+ },
472
+ ],
463
473
  ])("rejects %s returned with a successful status", async (_name, body) => {
464
474
  fetchMock.mockResolvedValue(mockFetchResponse(body));
465
475
 
@@ -724,6 +734,14 @@ describe("A2AClient", () => {
724
734
  const [url] = fetchMock.mock.calls[0]!;
725
735
  expect(url).toBe("https://agent.test/tasks/t1?history_length=5");
726
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
+ });
727
745
  });
728
746
 
729
747
  // --- listTasks ---
@@ -751,6 +769,65 @@ describe("A2AClient", () => {
751
769
  expect(url).toContain("status=TASK_STATE_WORKING");
752
770
  expect(url).toContain("page_size=10");
753
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
+ });
754
831
  });
755
832
 
756
833
  // --- cancelTask ---
@@ -778,6 +855,14 @@ describe("A2AClient", () => {
778
855
  const body = JSON.parse(fetchMock.mock.calls[0]![1].body);
779
856
  expect(body.metadata).toEqual({ reason: "user requested" });
780
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
+ });
781
866
  });
782
867
 
783
868
  // --- getAgentCard ---
@@ -1211,12 +1296,13 @@ describe("A2AClient", () => {
1211
1296
  expect(events).toHaveLength(1);
1212
1297
  });
1213
1298
 
1214
- 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 });
1215
1302
  fetchMock.mockResolvedValue(
1216
- mockSSETextResponse(
1217
- "<html><body>Please sign in</body></html>",
1218
- "text/html; charset=utf-8",
1219
- ),
1303
+ new Response(body, {
1304
+ headers: { "Content-Type": "text/html; charset=utf-8" },
1305
+ }),
1220
1306
  );
1221
1307
 
1222
1308
  const consumeStream = async () => {
@@ -1228,10 +1314,13 @@ describe("A2AClient", () => {
1228
1314
  await expect(consumeStream()).rejects.toThrow(
1229
1315
  'Expected A2A stream response Content-Type "text/event-stream", received "text/html; charset=utf-8"',
1230
1316
  );
1317
+ expect(cancel).toHaveBeenCalledOnce();
1231
1318
  });
1232
1319
 
1233
1320
  it("rejects task subscriptions without a content type", async () => {
1234
- fetchMock.mockResolvedValue(mockSSETextResponse("", null));
1321
+ fetchMock.mockResolvedValue(
1322
+ new Response(new ReadableStream<Uint8Array>()),
1323
+ );
1235
1324
 
1236
1325
  const consumeStream = async () => {
1237
1326
  for await (const event of client.subscribeToTask("t1")) {
@@ -1312,9 +1401,34 @@ describe("A2AClient", () => {
1312
1401
  expect((evt.event.status.message as any)?.content).toBeUndefined();
1313
1402
  });
1314
1403
 
1315
- 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
+
1316
1420
  fetchMock.mockResolvedValue(
1317
- mockSSEResponse(["data: {invalid json}", "", ""]),
1421
+ mockSSEResponse([
1422
+ `data: ${first}`,
1423
+ "",
1424
+ "data: {invalid json}",
1425
+ "",
1426
+ "data: {}",
1427
+ "",
1428
+ `data: ${second}`,
1429
+ "",
1430
+ "",
1431
+ ]),
1318
1432
  );
1319
1433
 
1320
1434
  const events: A2AStreamEvent[] = [];
@@ -1322,7 +1436,11 @@ describe("A2AClient", () => {
1322
1436
  events.push(event);
1323
1437
  }
1324
1438
 
1325
- expect(events).toHaveLength(0);
1439
+ expect(events).toHaveLength(2);
1440
+ expect(events.map((event) => event.type)).toEqual([
1441
+ "statusUpdate",
1442
+ "statusUpdate",
1443
+ ]);
1326
1444
  });
1327
1445
  });
1328
1446
 
@@ -1393,6 +1511,112 @@ describe("A2AClient", () => {
1393
1511
  expect(url).toBe("https://agent.test/tasks/t1/pushNotificationConfigs");
1394
1512
  });
1395
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
+
1396
1620
  it("deleteTaskPushNotificationConfig sends DELETE", async () => {
1397
1621
  fetchMock.mockResolvedValue({
1398
1622
  ok: true,
package/src/A2AClient.ts CHANGED
@@ -171,6 +171,17 @@ const TASK_STATES: ReadonlySet<string> = new Set(
171
171
  const isTaskState = (value: unknown): value is A2ATaskState =>
172
172
  typeof value === "string" && TASK_STATES.has(value);
173
173
 
174
+ const ROLES: ReadonlySet<string> = new Set(
175
+ Object.keys({
176
+ unspecified: true,
177
+ user: true,
178
+ agent: true,
179
+ } satisfies Record<A2ARole, true>),
180
+ );
181
+
182
+ const isRole = (value: unknown): value is A2ARole =>
183
+ typeof value === "string" && ROLES.has(value);
184
+
174
185
  const isTask = (value: unknown): value is A2ATask =>
175
186
  isRecord(value) &&
176
187
  typeof value.id === "string" &&
@@ -182,8 +193,7 @@ const isMessage = (value: unknown): value is A2AMessage =>
182
193
  isRecord(value) &&
183
194
  typeof value.messageId === "string" &&
184
195
  value.messageId.length > 0 &&
185
- typeof value.role === "string" &&
186
- value.role.length > 0 &&
196
+ isRole(value.role) &&
187
197
  Array.isArray(value.parts) &&
188
198
  value.parts.every(isRecord);
189
199
 
@@ -262,6 +272,142 @@ const parseSendMessageResponse = (value: unknown): A2ATask | A2AMessage => {
262
272
  );
263
273
  };
264
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
+
265
411
  function signalInit(signal?: AbortSignal): RequestInit {
266
412
  return signal ? { signal } : {};
267
413
  }
@@ -464,10 +610,11 @@ export class A2AClient {
464
610
  params.set("history_length", String(historyLength));
465
611
  }
466
612
  const qs = params.toString();
467
- return this.fetchJSON<A2ATask>(
613
+ const result = await this.fetchJSON<unknown>(
468
614
  `${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}${qs ? `?${qs}` : ""}`,
469
615
  signalInit(signal),
470
616
  );
617
+ return parseTaskResponse(result, "tasks:get");
471
618
  }
472
619
 
473
620
  async listTasks(
@@ -487,10 +634,11 @@ export class A2AClient {
487
634
  if (request?.includeArtifacts !== undefined)
488
635
  params.set("include_artifacts", String(request.includeArtifacts));
489
636
  const qs = params.toString();
490
- return this.fetchJSON<A2AListTasksResponse>(
637
+ const result = await this.fetchJSON<unknown>(
491
638
  `${this.getBasePath()}/tasks${qs ? `?${qs}` : ""}`,
492
639
  signalInit(signal),
493
640
  );
641
+ return parseListTasksResponse(result);
494
642
  }
495
643
 
496
644
  async cancelTask(
@@ -499,7 +647,7 @@ export class A2AClient {
499
647
  signal?: AbortSignal,
500
648
  ): Promise<A2ATask> {
501
649
  const body = metadata ? { metadata } : {};
502
- return this.fetchJSON<A2ATask>(
650
+ const result = await this.fetchJSON<unknown>(
503
651
  `${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}:cancel`,
504
652
  {
505
653
  method: "POST",
@@ -507,6 +655,7 @@ export class A2AClient {
507
655
  ...signalInit(signal),
508
656
  },
509
657
  );
658
+ return parseTaskResponse(result, "tasks:cancel");
510
659
  }
511
660
 
512
661
  async *subscribeToTask(
@@ -539,7 +688,7 @@ export class A2AClient {
539
688
  ): Promise<A2ATaskPushNotificationConfig> {
540
689
  const taskId = config.taskId;
541
690
  if (!taskId) throw new Error("taskId is required");
542
- return this.fetchJSON<A2ATaskPushNotificationConfig>(
691
+ const result = await this.fetchJSON<unknown>(
543
692
  `${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}/pushNotificationConfigs`,
544
693
  {
545
694
  method: "POST",
@@ -547,6 +696,10 @@ export class A2AClient {
547
696
  ...signalInit(signal),
548
697
  },
549
698
  );
699
+ return parseTaskPushNotificationConfigResponse(
700
+ result,
701
+ invalidPushNotificationConfigResponse("pushNotificationConfigs:create"),
702
+ );
550
703
  }
551
704
 
552
705
  async getTaskPushNotificationConfig(
@@ -554,10 +707,14 @@ export class A2AClient {
554
707
  configId: string,
555
708
  signal?: AbortSignal,
556
709
  ): Promise<A2ATaskPushNotificationConfig> {
557
- return this.fetchJSON<A2ATaskPushNotificationConfig>(
710
+ const result = await this.fetchJSON<unknown>(
558
711
  `${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}/pushNotificationConfigs/${encodeURIComponent(configId)}`,
559
712
  signalInit(signal),
560
713
  );
714
+ return parseTaskPushNotificationConfigResponse(
715
+ result,
716
+ invalidPushNotificationConfigResponse("pushNotificationConfigs:get"),
717
+ );
561
718
  }
562
719
 
563
720
  async listTaskPushNotificationConfigs(
@@ -570,10 +727,11 @@ export class A2AClient {
570
727
  params.set("page_size", String(options.pageSize));
571
728
  if (options?.pageToken) params.set("page_token", options.pageToken);
572
729
  const qs = params.toString();
573
- return this.fetchJSON<A2AListTaskPushNotificationConfigsResponse>(
730
+ const result = await this.fetchJSON<unknown>(
574
731
  `${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}/pushNotificationConfigs${qs ? `?${qs}` : ""}`,
575
732
  signalInit(signal),
576
733
  );
734
+ return parseListTaskPushNotificationConfigsResponse(result);
577
735
  }
578
736
 
579
737
  async deleteTaskPushNotificationConfig(
@@ -606,6 +764,7 @@ export class A2AClient {
606
764
  const received = contentType
607
765
  ? `"${contentType}"`
608
766
  : "no Content-Type header";
767
+ void response.body?.cancel().catch(() => undefined);
609
768
  throw new Error(
610
769
  `Expected A2A stream response Content-Type "text/event-stream", received ${received}`,
611
770
  );