@assistant-ui/react-a2a 0.2.21 → 0.2.22
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 +8 -5
- package/dist/A2AClient.d.ts.map +1 -1
- package/dist/A2AClient.js +25 -19
- package/dist/A2AClient.js.map +1 -1
- package/dist/A2AThreadRuntimeCore.d.ts +0 -1
- package/dist/A2AThreadRuntimeCore.d.ts.map +1 -1
- package/dist/a2aExtras.d.ts +0 -1
- package/dist/a2aExtras.d.ts.map +1 -1
- package/dist/conversions.d.ts +0 -1
- package/dist/conversions.d.ts.map +1 -1
- package/dist/hooks.d.ts +0 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/types.d.ts +27 -14
- package/dist/types.d.ts.map +1 -1
- package/dist/useA2ARuntime.d.ts +0 -1
- package/dist/useA2ARuntime.d.ts.map +1 -1
- package/package.json +6 -5
- package/src/A2AClient.test.ts +179 -4
- package/src/A2AClient.ts +44 -41
package/dist/A2AClient.d.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { A2AAgentCard, A2AErrorInfo, A2AListTaskPushNotificationConfigsResponse, A2AListTasksRequest, A2AListTasksResponse, A2AMessage, A2ASendMessageConfiguration, A2AStreamEvent, A2ATask, A2ATaskPushNotificationConfig } from "./types.js";
|
|
2
|
-
|
|
3
2
|
//#region src/A2AClient.d.ts
|
|
4
3
|
type A2AClientOptions = {
|
|
5
|
-
baseUrl: string;
|
|
6
|
-
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
/** Optional path prefix for all API endpoints (e.g. "/v1"). Does not affect agent card discovery. */
|
|
6
|
+
basePath?: string | undefined;
|
|
7
|
+
/** Optional tenant ID for multi-tenant servers. */
|
|
7
8
|
tenant?: string | undefined;
|
|
8
|
-
headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>) | undefined;
|
|
9
|
-
|
|
9
|
+
headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>) | undefined;
|
|
10
|
+
/** A2A extension URIs to negotiate. Sent as A2A-Extensions header. */
|
|
11
|
+
extensions?: string[] | undefined;
|
|
12
|
+
/** Extra fetch options applied to every request. */
|
|
10
13
|
fetchOptions?: Omit<RequestInit, "headers" | "body" | "method" | "signal"> | undefined;
|
|
11
14
|
};
|
|
12
15
|
declare class A2AError extends Error {
|
package/dist/A2AClient.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"A2AClient.d.ts","names":[],"sources":["../src/A2AClient.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"A2AClient.d.ts","names":[],"sources":["../src/A2AClient.ts"],"mappings":";;KAiBY;EACV;;EAEA;;EAEA;EACA,UACI,gCACO,yBAAyB,QAAQ;;EAG5C;;EAEA,eACI,KAAK;;cAIE,iBAAiB;EAC5B;EACA;EACA;cAEY,MAAM;;cAuHP;UACH;UACA;UACA;UACA;UACA;UAIA;cAII,SAAS;UAkBb;UAIM;UAqBA;UAyBA;EAuBR,aAAa,SAAS,cAAc,QAAQ;EAe5C,qBAAqB,SAAS,cAAc,QAAQ;EASpD,YACJ,SAAS,YACT,gBAAgB,6BAChB,WAAW,yBACX,SAAS,cACR,QAAQ,UAAU;EAuBd,cACL,SAAS,YACT,gBAAgB,6BAChB,WAAW,yBACX,SAAS,cACR,eAAe;EA6BZ,QACJ,gBACA,wBACA,SAAS,cACR,QAAQ;EAaL,UACJ,UAAU,qBACV,SAAS,cACR,QAAQ;EAoBL,WACJ,gBACA,WAAW,yBACX,SAAS,cACR,QAAQ;EAYJ,gBACL,gBACA,SAAS,cACR,eAAe;EAqBZ,iCACJ,QAAQ,+BACR,SAAS,cACR,QAAQ;EAaL,8BACJ,gBACA,kBACA,SAAS,cACR,QAAQ;EAOL,gCACJ,gBACA;IAAY;IAAmB;KAC/B,SAAS,cACR,QAAQ;EAYL,iCACJ,gBACA,kBACA,SAAS,cACR;UAmBY"}
|
package/dist/A2AClient.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import "./types.js";
|
|
2
|
+
import { SSEEventDecoder } from "assistant-stream/utils";
|
|
2
3
|
//#region src/A2AClient.ts
|
|
3
4
|
var A2AError = class extends Error {
|
|
4
5
|
code;
|
|
@@ -251,32 +252,37 @@ var A2AClient = class {
|
|
|
251
252
|
if (!response.ok) await this.throwResponseError(response);
|
|
252
253
|
}
|
|
253
254
|
async *parseSSE(response) {
|
|
255
|
+
const contentType = response.headers.get("Content-Type");
|
|
256
|
+
if (contentType?.split(";", 1)[0]?.trim().toLowerCase() !== "text/event-stream") {
|
|
257
|
+
const received = contentType ? `"${contentType}"` : "no Content-Type header";
|
|
258
|
+
throw new Error(`Expected A2A stream response Content-Type "text/event-stream", received ${received}`);
|
|
259
|
+
}
|
|
254
260
|
const reader = response.body?.getReader();
|
|
255
261
|
if (!reader) throw new Error("No response body");
|
|
256
262
|
const decoder = new TextDecoder();
|
|
257
|
-
|
|
263
|
+
const sseDecoder = new SSEEventDecoder();
|
|
264
|
+
const readEvent = (event) => {
|
|
265
|
+
try {
|
|
266
|
+
let parsed = JSON.parse(event.data);
|
|
267
|
+
if (parsed && typeof parsed === "object" && "jsonrpc" in parsed && "result" in parsed) parsed = parsed.result;
|
|
268
|
+
return discriminateStreamResponse(normalizeKeys(parsed));
|
|
269
|
+
} catch {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
};
|
|
258
273
|
try {
|
|
259
274
|
while (true) {
|
|
260
275
|
const { done, value } = await reader.read();
|
|
261
|
-
if (done)
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const eventText = buffer.slice(0, eventEnd);
|
|
266
|
-
buffer = buffer.slice(eventEnd + 2);
|
|
267
|
-
const dataLines = [];
|
|
268
|
-
for (const line of eventText.split("\n")) {
|
|
269
|
-
const trimmed = line.replace(/\r$/, "");
|
|
270
|
-
if (trimmed.startsWith("data:")) dataLines.push(trimmed.slice(5).trim());
|
|
276
|
+
if (done) {
|
|
277
|
+
for (const event of sseDecoder.push(decoder.decode())) {
|
|
278
|
+
const parsed = readEvent(event);
|
|
279
|
+
if (parsed) yield parsed;
|
|
271
280
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
if (event) yield event;
|
|
278
|
-
} catch {}
|
|
279
|
-
eventEnd = buffer.indexOf("\n\n");
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
for (const event of sseDecoder.push(decoder.decode(value, { stream: true }))) {
|
|
284
|
+
const parsed = readEvent(event);
|
|
285
|
+
if (parsed) yield parsed;
|
|
280
286
|
}
|
|
281
287
|
}
|
|
282
288
|
} finally {
|
package/dist/A2AClient.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"A2AClient.js","names":[],"sources":["../src/A2AClient.ts"],"sourcesContent":["import type {\n A2AAgentCard,\n A2AErrorInfo,\n A2AListTaskPushNotificationConfigsResponse,\n A2AListTasksRequest,\n A2AListTasksResponse,\n A2AMessage,\n A2ARole,\n A2ASendMessageConfiguration,\n A2AStreamEvent,\n A2ATask,\n A2ATaskPushNotificationConfig,\n A2ATaskState,\n} from \"./types\";\nimport { A2A_PROTOCOL_VERSION } from \"./types\";\n\nexport type A2AClientOptions = {\n baseUrl: string;\n /** Optional path prefix for all API endpoints (e.g. \"/v1\"). Does not affect agent card discovery. */\n basePath?: string | undefined;\n /** Optional tenant ID for multi-tenant servers. */\n tenant?: string | undefined;\n headers?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>)\n | undefined;\n /** A2A extension URIs to negotiate. Sent as A2A-Extensions header. */\n extensions?: string[] | undefined;\n /** Extra fetch options applied to every request. */\n fetchOptions?:\n | Omit<RequestInit, \"headers\" | \"body\" | \"method\" | \"signal\">\n | undefined;\n};\n\nexport class A2AError extends Error {\n code: number;\n status: string;\n details: unknown[] | undefined;\n\n constructor(info: A2AErrorInfo) {\n super(info.message);\n this.name = \"A2AError\";\n this.code = info.code;\n this.status = info.status;\n this.details = info.details;\n }\n}\n\n// Incoming key normalization: snake_case → camelCase, plus ProtoJSON enum normalization.\nfunction toCamelCase(key: string): string {\n return key.replace(/_([a-z])/g, (_, c: string) => c.toUpperCase());\n}\n\n// Fields whose values are opaque user data (google.protobuf.Struct / Value).\n// Keys inside these objects must NOT be camelCased or have enum normalization applied.\nconst OPAQUE_FIELDS = new Set([\n \"metadata\",\n \"data\",\n \"params\",\n \"forwardedProps\",\n \"scopes\",\n]);\n\nfunction normalizeKeys(obj: unknown, opaque = false): unknown {\n if (Array.isArray(obj)) return obj.map((v) => normalizeKeys(v, opaque));\n if (obj !== null && typeof obj === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n // Inside opaque fields: preserve keys and values as-is (only recurse arrays/objects structurally)\n if (opaque) {\n result[key] =\n typeof value === \"object\" && value !== null\n ? normalizeKeys(value, true)\n : value;\n continue;\n }\n\n const camelKey = toCamelCase(key);\n const isOpaqueChild = OPAQUE_FIELDS.has(camelKey);\n\n if (\n camelKey === \"state\" &&\n typeof value === \"string\" &&\n value.startsWith(\"TASK_STATE_\")\n ) {\n result[camelKey] = value.slice(11).toLowerCase();\n } else if (\n camelKey === \"role\" &&\n typeof value === \"string\" &&\n value.startsWith(\"ROLE_\")\n ) {\n result[camelKey] = value.slice(5).toLowerCase();\n } else if (camelKey === \"content\" && Array.isArray(value)) {\n // v0.3 servers used \"content\" for message/artifact parts; normalize to \"parts\" for backward compat\n result.parts = normalizeKeys(value, false);\n } else if (camelKey !== \"parts\" || !(\"parts\" in result)) {\n // dedup: \"content\" was already mapped to parts above; don't overwrite\n result[camelKey] = isOpaqueChild ? value : normalizeKeys(value, false);\n }\n }\n return result;\n }\n return obj;\n}\n\n// Outgoing enum conversion (v1.0 ProtoJSON format)\nfunction toWireRole(role: A2ARole): string {\n if (role === \"user\") return \"ROLE_USER\";\n if (role === \"agent\") return \"ROLE_AGENT\";\n return \"ROLE_UNSPECIFIED\";\n}\n\nfunction toWireTaskState(state: A2ATaskState): string {\n return `TASK_STATE_${state.toUpperCase()}`;\n}\n\nfunction toWireMessage(msg: A2AMessage): unknown {\n return { ...msg, role: toWireRole(msg.role) };\n}\n\nfunction discriminateStreamResponse(\n data: Record<string, unknown>,\n): A2AStreamEvent | null {\n if (\"task\" in data && data.task) {\n return { type: \"task\", task: data.task as A2ATask };\n }\n if (\"message\" in data && data.message) {\n return { type: \"message\", message: data.message as A2AMessage };\n }\n if (\"statusUpdate\" in data && data.statusUpdate) {\n return {\n type: \"statusUpdate\",\n event: data.statusUpdate as A2AStreamEvent extends {\n type: \"statusUpdate\";\n event: infer E;\n }\n ? E\n : never,\n };\n }\n if (\"artifactUpdate\" in data && data.artifactUpdate) {\n return {\n type: \"artifactUpdate\",\n event: data.artifactUpdate as A2AStreamEvent extends {\n type: \"artifactUpdate\";\n event: infer E;\n }\n ? E\n : never,\n };\n }\n return null;\n}\n\nfunction signalInit(signal?: AbortSignal): RequestInit {\n return signal ? { signal } : {};\n}\n\nexport class A2AClient {\n private baseUrl: string;\n private basePath: string;\n private tenant: string | undefined;\n private extensionUris: string[] | undefined;\n private fetchOptions: Omit<\n RequestInit,\n \"headers\" | \"body\" | \"method\" | \"signal\"\n >;\n private headersFn:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n\n constructor(options: A2AClientOptions) {\n this.baseUrl = options.baseUrl.replace(/\\/$/, \"\");\n this.basePath = options.basePath\n ? `/${options.basePath.replace(/^\\/|\\/$/g, \"\")}`\n : \"\";\n this.tenant = options.tenant;\n this.extensionUris = options.extensions;\n const {\n headers: _h,\n body: _b,\n method: _m,\n signal: _s,\n ...safeFetchOptions\n } = (options.fetchOptions ?? {}) as RequestInit;\n this.fetchOptions = safeFetchOptions;\n this.headersFn = options.headers ?? {};\n }\n\n private getBasePath(): string {\n return `${this.basePath}${this.tenant ? `/${encodeURIComponent(this.tenant)}` : \"\"}`;\n }\n\n private async getHeaders(\n includeContentType = true,\n ): Promise<Record<string, string>> {\n const custom =\n typeof this.headersFn === \"function\"\n ? await this.headersFn()\n : this.headersFn;\n const headers: Record<string, string> = {\n Accept: \"application/a2a+json, application/json\",\n \"A2A-Version\": A2A_PROTOCOL_VERSION,\n ...custom,\n };\n if (includeContentType) {\n headers[\"Content-Type\"] = \"application/a2a+json\";\n }\n if (this.extensionUris?.length) {\n headers[\"A2A-Extensions\"] = this.extensionUris.join(\", \");\n }\n return headers;\n }\n\n private async throwResponseError(response: Response): Promise<never> {\n let errorBody: unknown;\n try {\n errorBody = await response.json();\n } catch {\n // no parseable body\n }\n\n if (errorBody && typeof errorBody === \"object\" && \"error\" in errorBody) {\n const err = (errorBody as Record<string, any>).error;\n throw new A2AError({\n code: err.code ?? response.status,\n status: err.status ?? response.statusText,\n message: err.message ?? `A2A request failed: ${response.status}`,\n details: err.details,\n });\n }\n\n throw new A2AError({\n code: response.status,\n status: response.statusText,\n message: `A2A request failed: ${response.status} ${response.statusText}`,\n });\n }\n\n private async fetchJSON<T>(\n path: string,\n options: RequestInit = {},\n ): Promise<T> {\n const isGet = !options.method || options.method.toUpperCase() === \"GET\";\n const headers = await this.getHeaders(!isGet);\n const response = await fetch(`${this.baseUrl}${path}`, {\n ...this.fetchOptions,\n ...options,\n headers: {\n ...headers,\n ...(options.headers as Record<string, string>),\n },\n });\n if (!response.ok) {\n await this.throwResponseError(response);\n }\n const json = await response.json();\n return normalizeKeys(json) as T;\n }\n\n // --- Agent Card ---\n\n async getAgentCard(signal?: AbortSignal): Promise<A2AAgentCard> {\n const headers = await this.getHeaders(false); // GET: no Content-Type\n const url = `${this.baseUrl}/.well-known/agent-card.json`;\n const response = await fetch(url, {\n ...this.fetchOptions,\n headers,\n ...signalInit(signal),\n });\n if (!response.ok) {\n await this.throwResponseError(response);\n }\n const json = await response.json();\n return normalizeKeys(json) as A2AAgentCard;\n }\n\n async getExtendedAgentCard(signal?: AbortSignal): Promise<A2AAgentCard> {\n return this.fetchJSON<A2AAgentCard>(\n `${this.getBasePath()}/extendedAgentCard`,\n signalInit(signal),\n );\n }\n\n // --- Message ---\n\n async sendMessage(\n message: A2AMessage,\n configuration?: A2ASendMessageConfiguration,\n metadata?: Record<string, unknown>,\n signal?: AbortSignal,\n ): Promise<A2ATask | A2AMessage> {\n const body: Record<string, unknown> = {\n message: toWireMessage(message),\n };\n if (configuration) body.configuration = configuration;\n if (metadata) body.metadata = metadata;\n\n const result = await this.fetchJSON<Record<string, unknown>>(\n `${this.getBasePath()}/message:send`,\n {\n method: \"POST\",\n body: JSON.stringify(body),\n ...signalInit(signal),\n },\n );\n\n // Unwrap SendMessageResponse: {task: Task} | {message: Message}\n if (\"task\" in result && result.task) return result.task as A2ATask;\n if (\"message\" in result && result.message)\n return result.message as A2AMessage;\n return result as unknown as A2ATask | A2AMessage;\n }\n\n async *streamMessage(\n message: A2AMessage,\n configuration?: A2ASendMessageConfiguration,\n metadata?: Record<string, unknown>,\n signal?: AbortSignal,\n ): AsyncGenerator<A2AStreamEvent> {\n const headers = await this.getHeaders(true);\n headers.Accept = \"text/event-stream\";\n\n const body: Record<string, unknown> = {\n message: toWireMessage(message),\n };\n if (configuration) body.configuration = configuration;\n if (metadata) body.metadata = metadata;\n\n const response = await fetch(\n `${this.baseUrl}${this.getBasePath()}/message:stream`,\n {\n ...this.fetchOptions,\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n ...signalInit(signal),\n },\n );\n if (!response.ok) {\n await this.throwResponseError(response);\n }\n\n yield* this.parseSSE(response);\n }\n\n // --- Tasks ---\n\n async getTask(\n taskId: string,\n historyLength?: number,\n signal?: AbortSignal,\n ): Promise<A2ATask> {\n const params = new URLSearchParams();\n if (historyLength !== undefined) {\n // Proto field name for HTTP transcoding query params\n params.set(\"history_length\", String(historyLength));\n }\n const qs = params.toString();\n return this.fetchJSON<A2ATask>(\n `${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}${qs ? `?${qs}` : \"\"}`,\n signalInit(signal),\n );\n }\n\n async listTasks(\n request?: A2AListTasksRequest,\n signal?: AbortSignal,\n ): Promise<A2AListTasksResponse> {\n const params = new URLSearchParams();\n if (request?.contextId) params.set(\"context_id\", request.contextId);\n if (request?.status) params.set(\"status\", toWireTaskState(request.status));\n if (request?.pageSize !== undefined)\n params.set(\"page_size\", String(request.pageSize));\n if (request?.pageToken) params.set(\"page_token\", request.pageToken);\n if (request?.historyLength !== undefined)\n params.set(\"history_length\", String(request.historyLength));\n if (request?.statusTimestampAfter)\n params.set(\"status_timestamp_after\", request.statusTimestampAfter);\n if (request?.includeArtifacts !== undefined)\n params.set(\"include_artifacts\", String(request.includeArtifacts));\n const qs = params.toString();\n return this.fetchJSON<A2AListTasksResponse>(\n `${this.getBasePath()}/tasks${qs ? `?${qs}` : \"\"}`,\n signalInit(signal),\n );\n }\n\n async cancelTask(\n taskId: string,\n metadata?: Record<string, unknown>,\n signal?: AbortSignal,\n ): Promise<A2ATask> {\n const body = metadata ? { metadata } : {};\n return this.fetchJSON<A2ATask>(\n `${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}:cancel`,\n {\n method: \"POST\",\n body: JSON.stringify(body),\n ...signalInit(signal),\n },\n );\n }\n\n async *subscribeToTask(\n taskId: string,\n signal?: AbortSignal,\n ): AsyncGenerator<A2AStreamEvent> {\n const headers = await this.getHeaders(false); // GET: no Content-Type\n headers.Accept = \"text/event-stream\";\n\n const response = await fetch(\n `${this.baseUrl}${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}:subscribe`,\n {\n ...this.fetchOptions,\n headers,\n ...signalInit(signal),\n },\n );\n if (!response.ok) {\n await this.throwResponseError(response);\n }\n\n yield* this.parseSSE(response);\n }\n\n // --- Push Notification Configs ---\n\n async createTaskPushNotificationConfig(\n config: A2ATaskPushNotificationConfig,\n signal?: AbortSignal,\n ): Promise<A2ATaskPushNotificationConfig> {\n const taskId = config.taskId;\n if (!taskId) throw new Error(\"taskId is required\");\n return this.fetchJSON<A2ATaskPushNotificationConfig>(\n `${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}/pushNotificationConfigs`,\n {\n method: \"POST\",\n body: JSON.stringify(config),\n ...signalInit(signal),\n },\n );\n }\n\n async getTaskPushNotificationConfig(\n taskId: string,\n configId: string,\n signal?: AbortSignal,\n ): Promise<A2ATaskPushNotificationConfig> {\n return this.fetchJSON<A2ATaskPushNotificationConfig>(\n `${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}/pushNotificationConfigs/${encodeURIComponent(configId)}`,\n signalInit(signal),\n );\n }\n\n async listTaskPushNotificationConfigs(\n taskId: string,\n options?: { pageSize?: number; pageToken?: string },\n signal?: AbortSignal,\n ): Promise<A2AListTaskPushNotificationConfigsResponse> {\n const params = new URLSearchParams();\n if (options?.pageSize !== undefined)\n params.set(\"page_size\", String(options.pageSize));\n if (options?.pageToken) params.set(\"page_token\", options.pageToken);\n const qs = params.toString();\n return this.fetchJSON<A2AListTaskPushNotificationConfigsResponse>(\n `${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}/pushNotificationConfigs${qs ? `?${qs}` : \"\"}`,\n signalInit(signal),\n );\n }\n\n async deleteTaskPushNotificationConfig(\n taskId: string,\n configId: string,\n signal?: AbortSignal,\n ): Promise<void> {\n const isGet = false;\n const headers = await this.getHeaders(!isGet);\n const response = await fetch(\n `${this.baseUrl}${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}/pushNotificationConfigs/${encodeURIComponent(configId)}`,\n {\n ...this.fetchOptions,\n method: \"DELETE\",\n headers,\n ...signalInit(signal),\n },\n );\n if (!response.ok) {\n await this.throwResponseError(response);\n }\n }\n\n // --- SSE Parsing ---\n\n private async *parseSSE(response: Response): AsyncGenerator<A2AStreamEvent> {\n const reader = response.body?.getReader();\n if (!reader) throw new Error(\"No response body\");\n\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n\n let eventEnd: number = buffer.indexOf(\"\\n\\n\");\n while (eventEnd !== -1) {\n const eventText = buffer.slice(0, eventEnd);\n buffer = buffer.slice(eventEnd + 2);\n\n const dataLines: string[] = [];\n\n for (const line of eventText.split(\"\\n\")) {\n const trimmed = line.replace(/\\r$/, \"\");\n if (trimmed.startsWith(\"data:\")) {\n dataLines.push(trimmed.slice(5).trim());\n }\n // event:, id:, retry: lines are parsed but not used —\n // we discriminate event type from the JSON payload.\n }\n\n if (dataLines.length === 0) continue;\n\n try {\n let parsed = JSON.parse(dataLines.join(\"\\n\"));\n\n // Unwrap JSON-RPC envelope if present\n if (\n parsed &&\n typeof parsed === \"object\" &&\n \"jsonrpc\" in parsed &&\n \"result\" in parsed\n ) {\n parsed = parsed.result;\n }\n\n const normalized = normalizeKeys(parsed) as Record<string, unknown>;\n const event = discriminateStreamResponse(normalized);\n if (event) yield event;\n } catch {\n // Skip malformed events\n }\n eventEnd = buffer.indexOf(\"\\n\\n\");\n }\n }\n } finally {\n reader.releaseLock();\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"A2AClient.js","names":[],"sources":["../src/A2AClient.ts"],"sourcesContent":["import { SSEEventDecoder, type SSEEvent } from \"assistant-stream/utils\";\nimport type {\n A2AAgentCard,\n A2AErrorInfo,\n A2AListTaskPushNotificationConfigsResponse,\n A2AListTasksRequest,\n A2AListTasksResponse,\n A2AMessage,\n A2ARole,\n A2ASendMessageConfiguration,\n A2AStreamEvent,\n A2ATask,\n A2ATaskPushNotificationConfig,\n A2ATaskState,\n} from \"./types\";\nimport { A2A_PROTOCOL_VERSION } from \"./types\";\n\nexport type A2AClientOptions = {\n baseUrl: string;\n /** Optional path prefix for all API endpoints (e.g. \"/v1\"). Does not affect agent card discovery. */\n basePath?: string | undefined;\n /** Optional tenant ID for multi-tenant servers. */\n tenant?: string | undefined;\n headers?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>)\n | undefined;\n /** A2A extension URIs to negotiate. Sent as A2A-Extensions header. */\n extensions?: string[] | undefined;\n /** Extra fetch options applied to every request. */\n fetchOptions?:\n | Omit<RequestInit, \"headers\" | \"body\" | \"method\" | \"signal\">\n | undefined;\n};\n\nexport class A2AError extends Error {\n code: number;\n status: string;\n details: unknown[] | undefined;\n\n constructor(info: A2AErrorInfo) {\n super(info.message);\n this.name = \"A2AError\";\n this.code = info.code;\n this.status = info.status;\n this.details = info.details;\n }\n}\n\n// Incoming key normalization: snake_case → camelCase, plus ProtoJSON enum normalization.\nfunction toCamelCase(key: string): string {\n return key.replace(/_([a-z])/g, (_, c: string) => c.toUpperCase());\n}\n\n// Fields whose values are opaque user data (google.protobuf.Struct / Value).\n// Keys inside these objects must NOT be camelCased or have enum normalization applied.\nconst OPAQUE_FIELDS = new Set([\n \"metadata\",\n \"data\",\n \"params\",\n \"forwardedProps\",\n \"scopes\",\n]);\n\nfunction normalizeKeys(obj: unknown, opaque = false): unknown {\n if (Array.isArray(obj)) return obj.map((v) => normalizeKeys(v, opaque));\n if (obj !== null && typeof obj === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n // Inside opaque fields: preserve keys and values as-is (only recurse arrays/objects structurally)\n if (opaque) {\n result[key] =\n typeof value === \"object\" && value !== null\n ? normalizeKeys(value, true)\n : value;\n continue;\n }\n\n const camelKey = toCamelCase(key);\n const isOpaqueChild = OPAQUE_FIELDS.has(camelKey);\n\n if (\n camelKey === \"state\" &&\n typeof value === \"string\" &&\n value.startsWith(\"TASK_STATE_\")\n ) {\n result[camelKey] = value.slice(11).toLowerCase();\n } else if (\n camelKey === \"role\" &&\n typeof value === \"string\" &&\n value.startsWith(\"ROLE_\")\n ) {\n result[camelKey] = value.slice(5).toLowerCase();\n } else if (camelKey === \"content\" && Array.isArray(value)) {\n // v0.3 servers used \"content\" for message/artifact parts; normalize to \"parts\" for backward compat\n result.parts = normalizeKeys(value, false);\n } else if (camelKey !== \"parts\" || !(\"parts\" in result)) {\n // dedup: \"content\" was already mapped to parts above; don't overwrite\n result[camelKey] = isOpaqueChild ? value : normalizeKeys(value, false);\n }\n }\n return result;\n }\n return obj;\n}\n\n// Outgoing enum conversion (v1.0 ProtoJSON format)\nfunction toWireRole(role: A2ARole): string {\n if (role === \"user\") return \"ROLE_USER\";\n if (role === \"agent\") return \"ROLE_AGENT\";\n return \"ROLE_UNSPECIFIED\";\n}\n\nfunction toWireTaskState(state: A2ATaskState): string {\n return `TASK_STATE_${state.toUpperCase()}`;\n}\n\nfunction toWireMessage(msg: A2AMessage): unknown {\n return { ...msg, role: toWireRole(msg.role) };\n}\n\nfunction discriminateStreamResponse(\n data: Record<string, unknown>,\n): A2AStreamEvent | null {\n if (\"task\" in data && data.task) {\n return { type: \"task\", task: data.task as A2ATask };\n }\n if (\"message\" in data && data.message) {\n return { type: \"message\", message: data.message as A2AMessage };\n }\n if (\"statusUpdate\" in data && data.statusUpdate) {\n return {\n type: \"statusUpdate\",\n event: data.statusUpdate as A2AStreamEvent extends {\n type: \"statusUpdate\";\n event: infer E;\n }\n ? E\n : never,\n };\n }\n if (\"artifactUpdate\" in data && data.artifactUpdate) {\n return {\n type: \"artifactUpdate\",\n event: data.artifactUpdate as A2AStreamEvent extends {\n type: \"artifactUpdate\";\n event: infer E;\n }\n ? E\n : never,\n };\n }\n return null;\n}\n\nfunction signalInit(signal?: AbortSignal): RequestInit {\n return signal ? { signal } : {};\n}\n\nexport class A2AClient {\n private baseUrl: string;\n private basePath: string;\n private tenant: string | undefined;\n private extensionUris: string[] | undefined;\n private fetchOptions: Omit<\n RequestInit,\n \"headers\" | \"body\" | \"method\" | \"signal\"\n >;\n private headersFn:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n\n constructor(options: A2AClientOptions) {\n this.baseUrl = options.baseUrl.replace(/\\/$/, \"\");\n this.basePath = options.basePath\n ? `/${options.basePath.replace(/^\\/|\\/$/g, \"\")}`\n : \"\";\n this.tenant = options.tenant;\n this.extensionUris = options.extensions;\n const {\n headers: _h,\n body: _b,\n method: _m,\n signal: _s,\n ...safeFetchOptions\n } = (options.fetchOptions ?? {}) as RequestInit;\n this.fetchOptions = safeFetchOptions;\n this.headersFn = options.headers ?? {};\n }\n\n private getBasePath(): string {\n return `${this.basePath}${this.tenant ? `/${encodeURIComponent(this.tenant)}` : \"\"}`;\n }\n\n private async getHeaders(\n includeContentType = true,\n ): Promise<Record<string, string>> {\n const custom =\n typeof this.headersFn === \"function\"\n ? await this.headersFn()\n : this.headersFn;\n const headers: Record<string, string> = {\n Accept: \"application/a2a+json, application/json\",\n \"A2A-Version\": A2A_PROTOCOL_VERSION,\n ...custom,\n };\n if (includeContentType) {\n headers[\"Content-Type\"] = \"application/a2a+json\";\n }\n if (this.extensionUris?.length) {\n headers[\"A2A-Extensions\"] = this.extensionUris.join(\", \");\n }\n return headers;\n }\n\n private async throwResponseError(response: Response): Promise<never> {\n let errorBody: unknown;\n try {\n errorBody = await response.json();\n } catch {\n // no parseable body\n }\n\n if (errorBody && typeof errorBody === \"object\" && \"error\" in errorBody) {\n const err = (errorBody as Record<string, any>).error;\n throw new A2AError({\n code: err.code ?? response.status,\n status: err.status ?? response.statusText,\n message: err.message ?? `A2A request failed: ${response.status}`,\n details: err.details,\n });\n }\n\n throw new A2AError({\n code: response.status,\n status: response.statusText,\n message: `A2A request failed: ${response.status} ${response.statusText}`,\n });\n }\n\n private async fetchJSON<T>(\n path: string,\n options: RequestInit = {},\n ): Promise<T> {\n const isGet = !options.method || options.method.toUpperCase() === \"GET\";\n const headers = await this.getHeaders(!isGet);\n const response = await fetch(`${this.baseUrl}${path}`, {\n ...this.fetchOptions,\n ...options,\n headers: {\n ...headers,\n ...(options.headers as Record<string, string>),\n },\n });\n if (!response.ok) {\n await this.throwResponseError(response);\n }\n const json = await response.json();\n return normalizeKeys(json) as T;\n }\n\n // --- Agent Card ---\n\n async getAgentCard(signal?: AbortSignal): Promise<A2AAgentCard> {\n const headers = await this.getHeaders(false); // GET: no Content-Type\n const url = `${this.baseUrl}/.well-known/agent-card.json`;\n const response = await fetch(url, {\n ...this.fetchOptions,\n headers,\n ...signalInit(signal),\n });\n if (!response.ok) {\n await this.throwResponseError(response);\n }\n const json = await response.json();\n return normalizeKeys(json) as A2AAgentCard;\n }\n\n async getExtendedAgentCard(signal?: AbortSignal): Promise<A2AAgentCard> {\n return this.fetchJSON<A2AAgentCard>(\n `${this.getBasePath()}/extendedAgentCard`,\n signalInit(signal),\n );\n }\n\n // --- Message ---\n\n async sendMessage(\n message: A2AMessage,\n configuration?: A2ASendMessageConfiguration,\n metadata?: Record<string, unknown>,\n signal?: AbortSignal,\n ): Promise<A2ATask | A2AMessage> {\n const body: Record<string, unknown> = {\n message: toWireMessage(message),\n };\n if (configuration) body.configuration = configuration;\n if (metadata) body.metadata = metadata;\n\n const result = await this.fetchJSON<Record<string, unknown>>(\n `${this.getBasePath()}/message:send`,\n {\n method: \"POST\",\n body: JSON.stringify(body),\n ...signalInit(signal),\n },\n );\n\n // Unwrap SendMessageResponse: {task: Task} | {message: Message}\n if (\"task\" in result && result.task) return result.task as A2ATask;\n if (\"message\" in result && result.message)\n return result.message as A2AMessage;\n return result as unknown as A2ATask | A2AMessage;\n }\n\n async *streamMessage(\n message: A2AMessage,\n configuration?: A2ASendMessageConfiguration,\n metadata?: Record<string, unknown>,\n signal?: AbortSignal,\n ): AsyncGenerator<A2AStreamEvent> {\n const headers = await this.getHeaders(true);\n headers.Accept = \"text/event-stream\";\n\n const body: Record<string, unknown> = {\n message: toWireMessage(message),\n };\n if (configuration) body.configuration = configuration;\n if (metadata) body.metadata = metadata;\n\n const response = await fetch(\n `${this.baseUrl}${this.getBasePath()}/message:stream`,\n {\n ...this.fetchOptions,\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n ...signalInit(signal),\n },\n );\n if (!response.ok) {\n await this.throwResponseError(response);\n }\n\n yield* this.parseSSE(response);\n }\n\n // --- Tasks ---\n\n async getTask(\n taskId: string,\n historyLength?: number,\n signal?: AbortSignal,\n ): Promise<A2ATask> {\n const params = new URLSearchParams();\n if (historyLength !== undefined) {\n // Proto field name for HTTP transcoding query params\n params.set(\"history_length\", String(historyLength));\n }\n const qs = params.toString();\n return this.fetchJSON<A2ATask>(\n `${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}${qs ? `?${qs}` : \"\"}`,\n signalInit(signal),\n );\n }\n\n async listTasks(\n request?: A2AListTasksRequest,\n signal?: AbortSignal,\n ): Promise<A2AListTasksResponse> {\n const params = new URLSearchParams();\n if (request?.contextId) params.set(\"context_id\", request.contextId);\n if (request?.status) params.set(\"status\", toWireTaskState(request.status));\n if (request?.pageSize !== undefined)\n params.set(\"page_size\", String(request.pageSize));\n if (request?.pageToken) params.set(\"page_token\", request.pageToken);\n if (request?.historyLength !== undefined)\n params.set(\"history_length\", String(request.historyLength));\n if (request?.statusTimestampAfter)\n params.set(\"status_timestamp_after\", request.statusTimestampAfter);\n if (request?.includeArtifacts !== undefined)\n params.set(\"include_artifacts\", String(request.includeArtifacts));\n const qs = params.toString();\n return this.fetchJSON<A2AListTasksResponse>(\n `${this.getBasePath()}/tasks${qs ? `?${qs}` : \"\"}`,\n signalInit(signal),\n );\n }\n\n async cancelTask(\n taskId: string,\n metadata?: Record<string, unknown>,\n signal?: AbortSignal,\n ): Promise<A2ATask> {\n const body = metadata ? { metadata } : {};\n return this.fetchJSON<A2ATask>(\n `${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}:cancel`,\n {\n method: \"POST\",\n body: JSON.stringify(body),\n ...signalInit(signal),\n },\n );\n }\n\n async *subscribeToTask(\n taskId: string,\n signal?: AbortSignal,\n ): AsyncGenerator<A2AStreamEvent> {\n const headers = await this.getHeaders(false); // GET: no Content-Type\n headers.Accept = \"text/event-stream\";\n\n const response = await fetch(\n `${this.baseUrl}${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}:subscribe`,\n {\n ...this.fetchOptions,\n headers,\n ...signalInit(signal),\n },\n );\n if (!response.ok) {\n await this.throwResponseError(response);\n }\n\n yield* this.parseSSE(response);\n }\n\n // --- Push Notification Configs ---\n\n async createTaskPushNotificationConfig(\n config: A2ATaskPushNotificationConfig,\n signal?: AbortSignal,\n ): Promise<A2ATaskPushNotificationConfig> {\n const taskId = config.taskId;\n if (!taskId) throw new Error(\"taskId is required\");\n return this.fetchJSON<A2ATaskPushNotificationConfig>(\n `${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}/pushNotificationConfigs`,\n {\n method: \"POST\",\n body: JSON.stringify(config),\n ...signalInit(signal),\n },\n );\n }\n\n async getTaskPushNotificationConfig(\n taskId: string,\n configId: string,\n signal?: AbortSignal,\n ): Promise<A2ATaskPushNotificationConfig> {\n return this.fetchJSON<A2ATaskPushNotificationConfig>(\n `${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}/pushNotificationConfigs/${encodeURIComponent(configId)}`,\n signalInit(signal),\n );\n }\n\n async listTaskPushNotificationConfigs(\n taskId: string,\n options?: { pageSize?: number; pageToken?: string },\n signal?: AbortSignal,\n ): Promise<A2AListTaskPushNotificationConfigsResponse> {\n const params = new URLSearchParams();\n if (options?.pageSize !== undefined)\n params.set(\"page_size\", String(options.pageSize));\n if (options?.pageToken) params.set(\"page_token\", options.pageToken);\n const qs = params.toString();\n return this.fetchJSON<A2AListTaskPushNotificationConfigsResponse>(\n `${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}/pushNotificationConfigs${qs ? `?${qs}` : \"\"}`,\n signalInit(signal),\n );\n }\n\n async deleteTaskPushNotificationConfig(\n taskId: string,\n configId: string,\n signal?: AbortSignal,\n ): Promise<void> {\n const isGet = false;\n const headers = await this.getHeaders(!isGet);\n const response = await fetch(\n `${this.baseUrl}${this.getBasePath()}/tasks/${encodeURIComponent(taskId)}/pushNotificationConfigs/${encodeURIComponent(configId)}`,\n {\n ...this.fetchOptions,\n method: \"DELETE\",\n headers,\n ...signalInit(signal),\n },\n );\n if (!response.ok) {\n await this.throwResponseError(response);\n }\n }\n\n // --- SSE Parsing ---\n\n private async *parseSSE(response: Response): AsyncGenerator<A2AStreamEvent> {\n const contentType = response.headers.get(\"Content-Type\");\n const mediaType = contentType?.split(\";\", 1)[0]?.trim().toLowerCase();\n if (mediaType !== \"text/event-stream\") {\n const received = contentType\n ? `\"${contentType}\"`\n : \"no Content-Type header\";\n throw new Error(\n `Expected A2A stream response Content-Type \"text/event-stream\", received ${received}`,\n );\n }\n\n const reader = response.body?.getReader();\n if (!reader) throw new Error(\"No response body\");\n\n const decoder = new TextDecoder();\n const sseDecoder = new SSEEventDecoder();\n\n const readEvent = (event: SSEEvent): A2AStreamEvent | null => {\n try {\n let parsed = JSON.parse(event.data);\n\n if (\n parsed &&\n typeof parsed === \"object\" &&\n \"jsonrpc\" in parsed &&\n \"result\" in parsed\n ) {\n parsed = parsed.result;\n }\n\n const normalized = normalizeKeys(parsed) as Record<string, unknown>;\n return discriminateStreamResponse(normalized);\n } catch {\n return null;\n }\n };\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n for (const event of sseDecoder.push(decoder.decode())) {\n const parsed = readEvent(event);\n if (parsed) yield parsed;\n }\n break;\n }\n\n for (const event of sseDecoder.push(\n decoder.decode(value, { stream: true }),\n )) {\n const parsed = readEvent(event);\n if (parsed) yield parsed;\n }\n }\n } finally {\n reader.releaseLock();\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { A2AAgentCard, A2AArtifact, A2ASendMessageConfiguration, A2ATask } from "./types.js";
|
|
2
2
|
import { A2AClient } from "./A2AClient.js";
|
|
3
3
|
import { AppendMessage, AssistantRuntime, ThreadHistoryAdapter, ThreadMessage } from "@assistant-ui/core";
|
|
4
|
-
|
|
5
4
|
//#region src/A2AThreadRuntimeCore.d.ts
|
|
6
5
|
type A2AThreadRuntimeCoreOptions = {
|
|
7
6
|
client: A2AClient;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"A2AThreadRuntimeCore.d.ts","names":[],"sources":["../src/A2AThreadRuntimeCore.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"A2AThreadRuntimeCore.d.ts","names":[],"sources":["../src/A2AThreadRuntimeCore.ts"],"mappings":";;;;KA6BY;EACV,QAAQ;EACR;EACA,gBAAgB;EAChB,YAAY,OAAO;EACnB;EACA,uBAAuB,UAAU;EACjC,UAAU;EACV;;cAQW;UACH;UACA;UACA;UACA;UACA;UACA;UACA;mBACS;UAET;UACA;UACA;UACA;UACA;UAGA;UACA;UACA;mBAGS;mBACA;UACT;UACA;cAEI,SAAS;EAWrB,cAAc,SAAS,KAAK;EAU5B,cAAc,SAAS;EAIvB;EASA,cAAc;EAId,wBAAwB;EAIxB,WAAW;EAIX,yBAAyB;EAIzB,gBAAgB;EAIhB;MAII;EAIJ,mBAAmB;EAgCb,OAAO,SAAS,gBAAgB;EAsBhC,KAAK,SAAS,gBAAgB;EAI9B,OACJ,yBACA;IAAW,YAAY;MACtB;EAaG,UAAU;EAiBhB,sBAAsB,mBAAmB;UAe3B;UAwEA;UA4BA;UA4BN;UAiBA;UA6BA;UAqCA;UAQA;UAuBA;UAiCA;UAqBA;UAWA;UAgBA;UASA;UAKA;UAOA;UAcA;UAIA;UAQA;UAeA"}
|
package/dist/a2aExtras.d.ts
CHANGED
package/dist/a2aExtras.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"a2aExtras.d.ts","names":[],"sources":["../src/a2aExtras.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"a2aExtras.d.ts","names":[],"sources":["../src/a2aExtras.ts"],"mappings":";;cAGa,iDAAS,cAAA"}
|
package/dist/conversions.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { A2AMessage, A2APart, A2ATaskState } from "./types.js";
|
|
2
2
|
import { MessageStatus, ThreadAssistantMessage } from "@assistant-ui/core";
|
|
3
|
-
|
|
4
3
|
//#region src/conversions.d.ts
|
|
5
4
|
declare function a2aPartToContent(part: A2APart): ThreadAssistantMessage["content"][number];
|
|
6
5
|
declare function a2aPartsToContent(parts: A2APart[]): ThreadAssistantMessage["content"];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversions.d.ts","names":[],"sources":["../src/conversions.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"conversions.d.ts","names":[],"sources":["../src/conversions.ts"],"mappings":";;;iBASgB,iBACd,MAAM,UACL;iBA+Ba,kBACd,OAAO,YACN;iBAgBa,oBAAoB,OAAO;iBAI3B,uBAAuB,OAAO;iBAI9B,yBAAyB,OAAO,eAAe;iBAoB/C,uBACd,SAAS;EACP;EACA;EACA;KAED;iBAgBa,oBACd,SAAS,aACR"}
|
package/dist/hooks.d.ts
CHANGED
package/dist/hooks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.ts","names":[],"sources":["../src/hooks.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","names":[],"sources":["../src/hooks.ts"],"mappings":";;;cAKa,kBAAA;;cAGA,gCAAA;;cAGA,uBAAA"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { A2AClient, A2AClientOptions } from "./A2AClient.js";
|
|
2
2
|
import { AttachmentAdapter, DictationAdapter, ExternalStoreSharedOptions, FeedbackAdapter, RealtimeVoiceAdapter, SpeechSynthesisAdapter, ThreadHistoryAdapter, ThreadMessage } from "@assistant-ui/core";
|
|
3
|
-
|
|
4
3
|
//#region src/types.d.ts
|
|
5
4
|
declare const A2A_PROTOCOL_VERSION = "1.0";
|
|
6
5
|
type A2ARole = "unspecified" | "user" | "agent";
|
|
@@ -157,8 +156,10 @@ type A2APasswordOAuthFlow = {
|
|
|
157
156
|
};
|
|
158
157
|
type A2AOAuthFlows = {
|
|
159
158
|
authorizationCode?: A2AAuthorizationCodeOAuthFlow | undefined;
|
|
160
|
-
clientCredentials?: A2AClientCredentialsOAuthFlow | undefined;
|
|
161
|
-
|
|
159
|
+
clientCredentials?: A2AClientCredentialsOAuthFlow | undefined;
|
|
160
|
+
/** @deprecated */
|
|
161
|
+
implicit?: A2AImplicitOAuthFlow | undefined;
|
|
162
|
+
/** @deprecated */
|
|
162
163
|
password?: A2APasswordOAuthFlow | undefined;
|
|
163
164
|
deviceCode?: A2ADeviceCodeOAuthFlow | undefined;
|
|
164
165
|
};
|
|
@@ -251,17 +252,29 @@ type UseA2AThreadListAdapter = {
|
|
|
251
252
|
}>;
|
|
252
253
|
};
|
|
253
254
|
type UseA2ARuntimeOptions = ExternalStoreSharedOptions & {
|
|
254
|
-
/** Pre-built A2A client instance. Provide this OR baseUrl. */
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
255
|
+
/** Pre-built A2A client instance. Provide this OR baseUrl. */
|
|
256
|
+
client?: A2AClient;
|
|
257
|
+
/** Base URL of the A2A server. Used to create a client if `client` is not provided. */
|
|
258
|
+
baseUrl?: string;
|
|
259
|
+
/** Optional path prefix for all API endpoints (e.g. "/v1"). Does not affect agent card discovery. Only used with baseUrl. */
|
|
260
|
+
basePath?: string;
|
|
261
|
+
/** Optional tenant ID for multi-tenant servers. Only used with baseUrl. */
|
|
262
|
+
tenant?: string;
|
|
263
|
+
/** Headers for the A2A client (only used with baseUrl). */
|
|
264
|
+
headers?: A2AClientOptions["headers"];
|
|
265
|
+
/** A2A extension URIs to negotiate. Only used with baseUrl. */
|
|
266
|
+
extensions?: string[];
|
|
267
|
+
/** Extra fetch options (e.g. `{ credentials: 'include' }`). Only used with `baseUrl`. */
|
|
268
|
+
fetchOptions?: A2AClientOptions["fetchOptions"];
|
|
269
|
+
/** Initial context ID for the conversation. */
|
|
270
|
+
contextId?: string;
|
|
271
|
+
/** Default send message configuration. */
|
|
272
|
+
configuration?: A2ASendMessageConfiguration;
|
|
273
|
+
/** Called when an error occurs. */
|
|
274
|
+
onError?: (error: Error) => void;
|
|
275
|
+
/** Called when a run is cancelled. */
|
|
276
|
+
onCancel?: () => void;
|
|
277
|
+
/** Called when an artifact is fully received (lastChunk). */
|
|
265
278
|
onArtifactComplete?: (artifact: A2AArtifact) => void;
|
|
266
279
|
adapters?: {
|
|
267
280
|
attachments?: AttachmentAdapter;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;cAgBa;KAED;KAEA;EACV;EACA;EACA;EACA;EAEA,WAAW;EACX;EACA;;KAGU;EACV;EACA;EACA;EACA,MAAM;EACN,OAAO;EACP,WAAW;EACX;EACA;;KAGU;KAWA;EACV,OAAO;EACP,UAAU;EACV;;KAGU;EACV;EACA;EACA;EACA,OAAO;EACP,WAAW;EACX;;KAGU;EACV;EACA;EACA,QAAQ;EACR,YAAY;EACZ,UAAU;EACV,WAAW;;KAID;EACV;EACA;EACA,QAAQ;EACR,WAAW;;KAGD;EACV;EACA;EACA,UAAU;EACV;EACA;EACA,WAAW;;KAGD;EACN;EAAc,MAAM;;EACpB;EAAiB,SAAS;;EAC1B;EAAsB,OAAO;;EAC7B;EAAwB,OAAO;;KAEzB;EACV;EACA;;KAGU;EACV;EACA;EACA;EACA;EACA;EACA,iBAAiB;;KAGP;EACV,SAAS;EACT;;KAGU;EACV;EACA,6BAA6B;EAC7B;EACA;;KAGU;EACV;EACA,SAAS;EACT;EACA;EACA;EACA;EACA;;KAGU;EACV,OAAO;EACP;EACA;EACA;;KAIU;EACV;EACA;EACA;EACA;;KAGU;EACV;EACA;EACA;;KAGU;EACV;EACA;EACA;;KAGU;EACV;EACA;EACA;EACA,QAAQ;EACR;;KAGU;EACV;EACA;EACA,QAAQ;;KAGE;EACV;EACA;EACA;EACA,QAAQ;;;KAIE;EACV;EACA;EACA,SAAS;;;KAIC;EACV;EACA;EACA,SAAS;;KAGC;EACV,oBAAoB;EACpB,oBAAoB;;EAEpB,WAAW;;EAEX,WAAW;EACX,aAAa;;KAGH;EACV;EACA,OAAO;EACP;;KAGU;EACV;EACA;;KAGU;EACV;;KAGU;EACV,uBAAuB;EACvB,yBAAyB;EACzB,uBAAuB;EACvB,8BAA8B;EAC9B,qBAAqB;;KAGX;EACV,SAAS;IAAiB;;;KAIhB;EACV;EACA;EACA,SAAS;;KAGC;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA,uBAAuB;;KAGb;EACV;EACA;EACA,aACI;IACE;IACA;IACA;IACA,SAAS;;EAGf;;KAGU;EACV;EACA;EACA;EACA;;KAGU;EACV;EACA;EACA,qBAAqB;EACrB;IAEM;IACA;;EAGN;EACA;EACA,cAAc;EACd,kBAAkB,eAAe;EACjC,uBAAuB;EACvB;EACA;EACA,QAAQ;EACR,aAAa;EACb;;;KAIU;EACV,MAAM;EACN,oBAAoB;EACpB,WAAW;;KAGD;EACV;EACA,4BAA4B;EAC5B,oBAAoB,qBAAqB;IACvC,mBAAmB;;;KAIX,uBAAuB;;EAEjC,SAAS;;EAET;;EAEA;;EAEA;;EAEA,UAAU;;EAEV;;EAEA,eAAe;;EAGf;;EAEA,gBAAgB;;EAGhB,WAAW,OAAO;;EAElB;;EAEA,sBAAsB,UAAU;EAEhC;IACE,cAAc;IACd,SAAS;IACT,YAAY;IACZ,QAAQ;IACR,WAAW;IACX,UAAU;IACV,aAAa"}
|
package/dist/useA2ARuntime.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useA2ARuntime.d.ts","names":[],"sources":["../src/useA2ARuntime.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"useA2ARuntime.d.ts","names":[],"sources":["../src/useA2ARuntime.ts"],"mappings":";;;iBAmBgB,cAAc,SAAS,uBAAuB"}
|
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.22",
|
|
4
4
|
"description": "A2A (Agent-to-Agent) v1.0 protocol adapter for assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"a2a",
|
|
@@ -29,8 +29,9 @@
|
|
|
29
29
|
],
|
|
30
30
|
"sideEffects": false,
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@assistant-ui/core": "^0.2.
|
|
33
|
-
"@assistant-ui/store": "^0.2.
|
|
32
|
+
"@assistant-ui/core": "^0.2.21",
|
|
33
|
+
"@assistant-ui/store": "^0.2.20",
|
|
34
|
+
"assistant-stream": "^0.3.26"
|
|
34
35
|
},
|
|
35
36
|
"peerDependencies": {
|
|
36
37
|
"@types/react": "*",
|
|
@@ -44,8 +45,8 @@
|
|
|
44
45
|
"devDependencies": {
|
|
45
46
|
"@types/react": "^19.2.17",
|
|
46
47
|
"react": "^19.2.7",
|
|
47
|
-
"vitest": "^4.1.
|
|
48
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
48
|
+
"vitest": "^4.1.10",
|
|
49
|
+
"@assistant-ui/x-buildutils": "0.0.18"
|
|
49
50
|
},
|
|
50
51
|
"publishConfig": {
|
|
51
52
|
"access": "public",
|
package/src/A2AClient.test.ts
CHANGED
|
@@ -14,17 +14,23 @@ function mockFetchResponse(body: unknown, ok = true, status = 200): Response {
|
|
|
14
14
|
} as unknown as Response;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
function
|
|
18
|
-
|
|
17
|
+
function mockSSETextResponse(
|
|
18
|
+
text: string | string[],
|
|
19
|
+
contentType: string | null = "text/event-stream",
|
|
20
|
+
): Response {
|
|
19
21
|
const encoder = new TextEncoder();
|
|
20
|
-
const chunks =
|
|
22
|
+
const chunks = (Array.isArray(text) ? text : [text]).map((chunk) =>
|
|
23
|
+
encoder.encode(chunk),
|
|
24
|
+
);
|
|
21
25
|
let index = 0;
|
|
26
|
+
const headers = new Headers();
|
|
27
|
+
if (contentType !== null) headers.set("content-type", contentType);
|
|
22
28
|
|
|
23
29
|
return {
|
|
24
30
|
ok: true,
|
|
25
31
|
status: 200,
|
|
26
32
|
statusText: "OK",
|
|
27
|
-
headers
|
|
33
|
+
headers,
|
|
28
34
|
body: {
|
|
29
35
|
getReader: () => ({
|
|
30
36
|
read: vi.fn().mockImplementation(() => {
|
|
@@ -42,6 +48,13 @@ function mockSSEResponse(lines: string[]): Response {
|
|
|
42
48
|
} as unknown as Response;
|
|
43
49
|
}
|
|
44
50
|
|
|
51
|
+
function mockSSEResponse(
|
|
52
|
+
lines: string[],
|
|
53
|
+
contentType: string | null = "text/event-stream",
|
|
54
|
+
): Response {
|
|
55
|
+
return mockSSETextResponse(lines.join("\n"), contentType);
|
|
56
|
+
}
|
|
57
|
+
|
|
45
58
|
const userMessage: A2AMessage = {
|
|
46
59
|
messageId: "msg-1",
|
|
47
60
|
role: "user",
|
|
@@ -799,6 +812,111 @@ describe("A2AClient", () => {
|
|
|
799
812
|
expect(evt.event.status.message?.role).toBe("agent");
|
|
800
813
|
});
|
|
801
814
|
|
|
815
|
+
it("parses CRLF-delimited SSE events", async () => {
|
|
816
|
+
const sseData = JSON.stringify({
|
|
817
|
+
status_update: {
|
|
818
|
+
task_id: "t1",
|
|
819
|
+
context_id: "ctx-1",
|
|
820
|
+
status: { state: "TASK_STATE_WORKING" },
|
|
821
|
+
},
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
fetchMock.mockResolvedValue(
|
|
825
|
+
mockSSETextResponse(`data: ${sseData}\r\n\r\n`),
|
|
826
|
+
);
|
|
827
|
+
|
|
828
|
+
const events: A2AStreamEvent[] = [];
|
|
829
|
+
for await (const event of client.streamMessage(userMessage)) {
|
|
830
|
+
events.push(event);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
expect(events).toHaveLength(1);
|
|
834
|
+
expect(events[0]!.type).toBe("statusUpdate");
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
it("parses CR-delimited SSE events", async () => {
|
|
838
|
+
const sseData = JSON.stringify({
|
|
839
|
+
status_update: {
|
|
840
|
+
task_id: "t1",
|
|
841
|
+
context_id: "ctx-1",
|
|
842
|
+
status: { state: "TASK_STATE_WORKING" },
|
|
843
|
+
},
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
fetchMock.mockResolvedValue(mockSSETextResponse(`data: ${sseData}\r\r`));
|
|
847
|
+
|
|
848
|
+
const events: A2AStreamEvent[] = [];
|
|
849
|
+
for await (const event of client.streamMessage(userMessage)) {
|
|
850
|
+
events.push(event);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
expect(events).toHaveLength(1);
|
|
854
|
+
expect(events[0]!.type).toBe("statusUpdate");
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
it("parses CRLF delimiters split across chunks", async () => {
|
|
858
|
+
const sseData = JSON.stringify({
|
|
859
|
+
status_update: {
|
|
860
|
+
task_id: "t1",
|
|
861
|
+
context_id: "ctx-1",
|
|
862
|
+
status: { state: "TASK_STATE_WORKING" },
|
|
863
|
+
},
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
fetchMock.mockResolvedValue(
|
|
867
|
+
mockSSETextResponse([`data: ${sseData}\r`, "\n\r", "\n"]),
|
|
868
|
+
);
|
|
869
|
+
|
|
870
|
+
const events: A2AStreamEvent[] = [];
|
|
871
|
+
for await (const event of client.streamMessage(userMessage)) {
|
|
872
|
+
events.push(event);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
expect(events).toHaveLength(1);
|
|
876
|
+
expect(events[0]!.type).toBe("statusUpdate");
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
it("parses CR-delimited events split across chunks", async () => {
|
|
880
|
+
const sseData = JSON.stringify({
|
|
881
|
+
status_update: {
|
|
882
|
+
task_id: "t1",
|
|
883
|
+
context_id: "ctx-1",
|
|
884
|
+
status: { state: "TASK_STATE_WORKING" },
|
|
885
|
+
},
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
fetchMock.mockResolvedValue(
|
|
889
|
+
mockSSETextResponse([`data: ${sseData}\r`, "\r"]),
|
|
890
|
+
);
|
|
891
|
+
|
|
892
|
+
const events: A2AStreamEvent[] = [];
|
|
893
|
+
for await (const event of client.streamMessage(userMessage)) {
|
|
894
|
+
events.push(event);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
expect(events).toHaveLength(1);
|
|
898
|
+
expect(events[0]!.type).toBe("statusUpdate");
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
it("does not dispatch unterminated SSE events", async () => {
|
|
902
|
+
const sseData = JSON.stringify({
|
|
903
|
+
status_update: {
|
|
904
|
+
task_id: "t1",
|
|
905
|
+
context_id: "ctx-1",
|
|
906
|
+
status: { state: "TASK_STATE_WORKING" },
|
|
907
|
+
},
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
fetchMock.mockResolvedValue(mockSSETextResponse(`data: ${sseData}`));
|
|
911
|
+
|
|
912
|
+
const events: A2AStreamEvent[] = [];
|
|
913
|
+
for await (const event of client.streamMessage(userMessage)) {
|
|
914
|
+
events.push(event);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
expect(events).toHaveLength(0);
|
|
918
|
+
});
|
|
919
|
+
|
|
802
920
|
it("parses SSE artifact update events", async () => {
|
|
803
921
|
const sseData = JSON.stringify({
|
|
804
922
|
artifact_update: {
|
|
@@ -887,6 +1005,63 @@ describe("A2AClient", () => {
|
|
|
887
1005
|
expect(events).toHaveLength(2);
|
|
888
1006
|
});
|
|
889
1007
|
|
|
1008
|
+
it("accepts parameterized event-stream content types", async () => {
|
|
1009
|
+
const sseData = JSON.stringify({
|
|
1010
|
+
status_update: {
|
|
1011
|
+
task_id: "t1",
|
|
1012
|
+
context_id: "ctx-1",
|
|
1013
|
+
status: { state: "TASK_STATE_COMPLETED" },
|
|
1014
|
+
},
|
|
1015
|
+
});
|
|
1016
|
+
|
|
1017
|
+
fetchMock.mockResolvedValue(
|
|
1018
|
+
mockSSEResponse(
|
|
1019
|
+
[`data: ${sseData}`, "", ""],
|
|
1020
|
+
"text/event-stream; charset=utf-8",
|
|
1021
|
+
),
|
|
1022
|
+
);
|
|
1023
|
+
|
|
1024
|
+
const events: A2AStreamEvent[] = [];
|
|
1025
|
+
for await (const event of client.streamMessage(userMessage)) {
|
|
1026
|
+
events.push(event);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
expect(events).toHaveLength(1);
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
it("rejects successful responses that are not event streams", async () => {
|
|
1033
|
+
fetchMock.mockResolvedValue(
|
|
1034
|
+
mockSSETextResponse(
|
|
1035
|
+
"<html><body>Please sign in</body></html>",
|
|
1036
|
+
"text/html; charset=utf-8",
|
|
1037
|
+
),
|
|
1038
|
+
);
|
|
1039
|
+
|
|
1040
|
+
const consumeStream = async () => {
|
|
1041
|
+
for await (const event of client.streamMessage(userMessage)) {
|
|
1042
|
+
void event;
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
await expect(consumeStream()).rejects.toThrow(
|
|
1047
|
+
'Expected A2A stream response Content-Type "text/event-stream", received "text/html; charset=utf-8"',
|
|
1048
|
+
);
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
it("rejects task subscriptions without a content type", async () => {
|
|
1052
|
+
fetchMock.mockResolvedValue(mockSSETextResponse("", null));
|
|
1053
|
+
|
|
1054
|
+
const consumeStream = async () => {
|
|
1055
|
+
for await (const event of client.subscribeToTask("t1")) {
|
|
1056
|
+
void event;
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1060
|
+
await expect(consumeStream()).rejects.toThrow(
|
|
1061
|
+
'Expected A2A stream response Content-Type "text/event-stream", received no Content-Type header',
|
|
1062
|
+
);
|
|
1063
|
+
});
|
|
1064
|
+
|
|
890
1065
|
it("normalizes 'content' array from v0.3 server response to 'parts' in SSE artifact update events", async () => {
|
|
891
1066
|
const sseData = JSON.stringify({
|
|
892
1067
|
artifact_update: {
|
package/src/A2AClient.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SSEEventDecoder, type SSEEvent } from "assistant-stream/utils";
|
|
1
2
|
import type {
|
|
2
3
|
A2AAgentCard,
|
|
3
4
|
A2AErrorInfo,
|
|
@@ -493,57 +494,59 @@ export class A2AClient {
|
|
|
493
494
|
// --- SSE Parsing ---
|
|
494
495
|
|
|
495
496
|
private async *parseSSE(response: Response): AsyncGenerator<A2AStreamEvent> {
|
|
497
|
+
const contentType = response.headers.get("Content-Type");
|
|
498
|
+
const mediaType = contentType?.split(";", 1)[0]?.trim().toLowerCase();
|
|
499
|
+
if (mediaType !== "text/event-stream") {
|
|
500
|
+
const received = contentType
|
|
501
|
+
? `"${contentType}"`
|
|
502
|
+
: "no Content-Type header";
|
|
503
|
+
throw new Error(
|
|
504
|
+
`Expected A2A stream response Content-Type "text/event-stream", received ${received}`,
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
|
|
496
508
|
const reader = response.body?.getReader();
|
|
497
509
|
if (!reader) throw new Error("No response body");
|
|
498
510
|
|
|
499
511
|
const decoder = new TextDecoder();
|
|
500
|
-
|
|
512
|
+
const sseDecoder = new SSEEventDecoder();
|
|
513
|
+
|
|
514
|
+
const readEvent = (event: SSEEvent): A2AStreamEvent | null => {
|
|
515
|
+
try {
|
|
516
|
+
let parsed = JSON.parse(event.data);
|
|
517
|
+
|
|
518
|
+
if (
|
|
519
|
+
parsed &&
|
|
520
|
+
typeof parsed === "object" &&
|
|
521
|
+
"jsonrpc" in parsed &&
|
|
522
|
+
"result" in parsed
|
|
523
|
+
) {
|
|
524
|
+
parsed = parsed.result;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const normalized = normalizeKeys(parsed) as Record<string, unknown>;
|
|
528
|
+
return discriminateStreamResponse(normalized);
|
|
529
|
+
} catch {
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
};
|
|
501
533
|
|
|
502
534
|
try {
|
|
503
535
|
while (true) {
|
|
504
536
|
const { done, value } = await reader.read();
|
|
505
|
-
if (done)
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
let eventEnd: number = buffer.indexOf("\n\n");
|
|
510
|
-
while (eventEnd !== -1) {
|
|
511
|
-
const eventText = buffer.slice(0, eventEnd);
|
|
512
|
-
buffer = buffer.slice(eventEnd + 2);
|
|
513
|
-
|
|
514
|
-
const dataLines: string[] = [];
|
|
515
|
-
|
|
516
|
-
for (const line of eventText.split("\n")) {
|
|
517
|
-
const trimmed = line.replace(/\r$/, "");
|
|
518
|
-
if (trimmed.startsWith("data:")) {
|
|
519
|
-
dataLines.push(trimmed.slice(5).trim());
|
|
520
|
-
}
|
|
521
|
-
// event:, id:, retry: lines are parsed but not used —
|
|
522
|
-
// we discriminate event type from the JSON payload.
|
|
537
|
+
if (done) {
|
|
538
|
+
for (const event of sseDecoder.push(decoder.decode())) {
|
|
539
|
+
const parsed = readEvent(event);
|
|
540
|
+
if (parsed) yield parsed;
|
|
523
541
|
}
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
524
544
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
// Unwrap JSON-RPC envelope if present
|
|
531
|
-
if (
|
|
532
|
-
parsed &&
|
|
533
|
-
typeof parsed === "object" &&
|
|
534
|
-
"jsonrpc" in parsed &&
|
|
535
|
-
"result" in parsed
|
|
536
|
-
) {
|
|
537
|
-
parsed = parsed.result;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
const normalized = normalizeKeys(parsed) as Record<string, unknown>;
|
|
541
|
-
const event = discriminateStreamResponse(normalized);
|
|
542
|
-
if (event) yield event;
|
|
543
|
-
} catch {
|
|
544
|
-
// Skip malformed events
|
|
545
|
-
}
|
|
546
|
-
eventEnd = buffer.indexOf("\n\n");
|
|
545
|
+
for (const event of sseDecoder.push(
|
|
546
|
+
decoder.decode(value, { stream: true }),
|
|
547
|
+
)) {
|
|
548
|
+
const parsed = readEvent(event);
|
|
549
|
+
if (parsed) yield parsed;
|
|
547
550
|
}
|
|
548
551
|
}
|
|
549
552
|
} finally {
|