@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.
- package/dist/A2AClient.d.ts.map +1 -1
- package/dist/A2AClient.js +82 -7
- package/dist/A2AClient.js.map +1 -1
- package/dist/A2AThreadRuntimeCore.d.ts.map +1 -1
- package/dist/A2AThreadRuntimeCore.js +19 -3
- package/dist/A2AThreadRuntimeCore.js.map +1 -1
- package/dist/conversions.js +2 -2
- package/dist/conversions.js.map +1 -1
- package/package.json +5 -5
- package/src/A2AClient.test.ts +233 -9
- package/src/A2AClient.ts +167 -8
- package/src/A2AThreadRuntimeCore.test.ts +135 -10
- package/src/A2AThreadRuntimeCore.ts +34 -4
- package/src/conversions.test.ts +16 -0
- package/src/conversions.ts +2 -2
- package/src/useA2ARuntime.test.tsx +20 -6
package/dist/conversions.js.map
CHANGED
|
@@ -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
|
|
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.
|
|
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.
|
|
33
|
-
"@assistant-ui/store": "^0.3.
|
|
34
|
-
"assistant-stream": "^0.3.
|
|
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.
|
|
53
|
+
"@assistant-ui/x-buildutils": "0.0.23"
|
|
54
54
|
},
|
|
55
55
|
"publishConfig": {
|
|
56
56
|
"access": "public",
|
package/src/A2AClient.test.ts
CHANGED
|
@@ -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
|
-
|
|
1217
|
-
"
|
|
1218
|
-
|
|
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(
|
|
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([
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
);
|