@assistant-ui/react-a2a 0.2.30 → 0.2.32
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 +200 -27
- package/dist/A2AClient.js.map +1 -1
- package/dist/A2AThreadRuntimeCore.d.ts +7 -12
- package/dist/A2AThreadRuntimeCore.d.ts.map +1 -1
- package/dist/A2AThreadRuntimeCore.js +65 -147
- package/dist/A2AThreadRuntimeCore.js.map +1 -1
- package/dist/conversions.d.ts +6 -2
- package/dist/conversions.d.ts.map +1 -1
- package/dist/conversions.js +28 -9
- package/dist/conversions.js.map +1 -1
- package/dist/useA2ARuntime.d.ts.map +1 -1
- package/dist/useA2ARuntime.js +17 -7
- package/dist/useA2ARuntime.js.map +1 -1
- package/package.json +6 -6
- package/src/A2AClient.test.ts +714 -0
- package/src/A2AClient.ts +307 -43
- package/src/A2AThreadRuntimeCore.test.ts +248 -0
- package/src/A2AThreadRuntimeCore.ts +104 -212
- package/src/conversions.test.ts +49 -0
- package/src/conversions.ts +56 -7
- package/src/useA2ARuntime.test.tsx +150 -26
- package/src/useA2ARuntime.ts +33 -12
package/src/A2AClient.ts
CHANGED
|
@@ -63,6 +63,37 @@ const OPAQUE_FIELDS = new Set([
|
|
|
63
63
|
"scopes",
|
|
64
64
|
]);
|
|
65
65
|
|
|
66
|
+
const JSONRPC_STATE_MAP: Record<string, string> = {
|
|
67
|
+
"input-required": "input_required",
|
|
68
|
+
"auth-required": "auth_required",
|
|
69
|
+
unknown: "unspecified",
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// JSON-RPC file parts nest the payload under `file`; the internal A2APart is
|
|
73
|
+
// flat, so the nested fields map onto url/raw/mediaType/filename.
|
|
74
|
+
function normalizeParts(value: unknown[]): unknown[] {
|
|
75
|
+
return value.map((raw) => {
|
|
76
|
+
const part = normalizeKeys(raw, false);
|
|
77
|
+
if (part === null || typeof part !== "object" || Array.isArray(part))
|
|
78
|
+
return part;
|
|
79
|
+
const record = part as Record<string, unknown>;
|
|
80
|
+
if (record.kind === undefined) return part;
|
|
81
|
+
const { kind, ...rest } = record;
|
|
82
|
+
const file = rest.file;
|
|
83
|
+
if (kind !== "file" || file === null || typeof file !== "object")
|
|
84
|
+
return rest;
|
|
85
|
+
const { file: _file, ...others } = rest;
|
|
86
|
+
const nested = file as Record<string, unknown>;
|
|
87
|
+
return {
|
|
88
|
+
...others,
|
|
89
|
+
...(nested.uri !== undefined ? { url: nested.uri } : {}),
|
|
90
|
+
...(nested.bytes !== undefined ? { raw: nested.bytes } : {}),
|
|
91
|
+
...(nested.mimeType !== undefined ? { mediaType: nested.mimeType } : {}),
|
|
92
|
+
...(nested.name !== undefined ? { filename: nested.name } : {}),
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
66
97
|
function normalizeKeys(obj: unknown, opaque = false): unknown {
|
|
67
98
|
if (Array.isArray(obj)) return obj.map((v) => normalizeKeys(v, opaque));
|
|
68
99
|
if (obj !== null && typeof obj === "object") {
|
|
@@ -80,12 +111,15 @@ function normalizeKeys(obj: unknown, opaque = false): unknown {
|
|
|
80
111
|
const camelKey = toCamelCase(key);
|
|
81
112
|
const isOpaqueChild = OPAQUE_FIELDS.has(camelKey);
|
|
82
113
|
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
114
|
+
if (camelKey === "state" && typeof value === "string") {
|
|
115
|
+
// Proto-style (TASK_STATE_WORKING) and the JSON-RPC state names map
|
|
116
|
+
// onto the internal snake_case states; anything unrecognized is
|
|
117
|
+
// preserved verbatim.
|
|
118
|
+
if (value.startsWith("TASK_STATE_")) {
|
|
119
|
+
result[camelKey] = value.slice(11).toLowerCase();
|
|
120
|
+
} else {
|
|
121
|
+
result[camelKey] = JSONRPC_STATE_MAP[value] ?? value;
|
|
122
|
+
}
|
|
89
123
|
} else if (
|
|
90
124
|
camelKey === "role" &&
|
|
91
125
|
typeof value === "string" &&
|
|
@@ -94,9 +128,11 @@ function normalizeKeys(obj: unknown, opaque = false): unknown {
|
|
|
94
128
|
result[camelKey] = value.slice(5).toLowerCase();
|
|
95
129
|
} else if (camelKey === "content" && Array.isArray(value)) {
|
|
96
130
|
// v0.3 servers used "content" for message/artifact parts; normalize to "parts" for backward compat
|
|
97
|
-
result.parts =
|
|
98
|
-
} else if (camelKey
|
|
131
|
+
result.parts = normalizeParts(value);
|
|
132
|
+
} else if (camelKey === "parts" && Array.isArray(value)) {
|
|
99
133
|
// dedup: "content" was already mapped to parts above; don't overwrite
|
|
134
|
+
if (!("parts" in result)) result.parts = normalizeParts(value);
|
|
135
|
+
} else if (camelKey !== "parts" || !("parts" in result)) {
|
|
100
136
|
result[camelKey] = isOpaqueChild ? value : normalizeKeys(value, false);
|
|
101
137
|
}
|
|
102
138
|
}
|
|
@@ -123,33 +159,78 @@ function toWireMessage(msg: A2AMessage): unknown {
|
|
|
123
159
|
function discriminateStreamResponse(
|
|
124
160
|
data: Record<string, unknown>,
|
|
125
161
|
): A2AStreamEvent | null {
|
|
126
|
-
if ("task" in data
|
|
127
|
-
|
|
162
|
+
if ("task" in data) {
|
|
163
|
+
const task = toWrappedTask(data.task);
|
|
164
|
+
if (task) return { type: "task", task };
|
|
128
165
|
}
|
|
129
|
-
if ("message" in data
|
|
130
|
-
|
|
166
|
+
if ("message" in data) {
|
|
167
|
+
const message = toWrappedMessage(data.message);
|
|
168
|
+
if (message) return { type: "message", message };
|
|
131
169
|
}
|
|
132
|
-
if ("statusUpdate" in data
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
type: "statusUpdate"
|
|
137
|
-
event:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
170
|
+
if ("statusUpdate" in data) {
|
|
171
|
+
const statusUpdate = toWrappedStatusUpdate(data.statusUpdate);
|
|
172
|
+
if (statusUpdate) {
|
|
173
|
+
return {
|
|
174
|
+
type: "statusUpdate",
|
|
175
|
+
event: statusUpdate as A2AStreamEvent extends {
|
|
176
|
+
type: "statusUpdate";
|
|
177
|
+
event: infer E;
|
|
178
|
+
}
|
|
179
|
+
? E
|
|
180
|
+
: never,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
142
183
|
}
|
|
143
|
-
if ("artifactUpdate" in data
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
type: "artifactUpdate"
|
|
148
|
-
event:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
184
|
+
if ("artifactUpdate" in data) {
|
|
185
|
+
const artifactUpdate = toWrappedArtifactUpdate(data.artifactUpdate);
|
|
186
|
+
if (artifactUpdate) {
|
|
187
|
+
return {
|
|
188
|
+
type: "artifactUpdate",
|
|
189
|
+
event: artifactUpdate as A2AStreamEvent extends {
|
|
190
|
+
type: "artifactUpdate";
|
|
191
|
+
event: infer E;
|
|
192
|
+
}
|
|
193
|
+
? E
|
|
194
|
+
: never,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// JSON-RPC streaming results are the event itself, flat, discriminated by
|
|
199
|
+
// `kind` (per the A2A JSON-RPC schema), rather than wrapped in a
|
|
200
|
+
// REST-style single-key envelope. The field sets cannot collide with the
|
|
201
|
+
// wrapper keys above, so this is a pure fallthrough.
|
|
202
|
+
const { kind, ...flat } = data;
|
|
203
|
+
switch (kind) {
|
|
204
|
+
case "task":
|
|
205
|
+
if (!isTask(flat)) break;
|
|
206
|
+
return { type: "task", task: flat };
|
|
207
|
+
case "message":
|
|
208
|
+
if (!isMessage(flat)) break;
|
|
209
|
+
return { type: "message", message: flat };
|
|
210
|
+
case "status-update": {
|
|
211
|
+
if (!isStatusUpdate(flat)) break;
|
|
212
|
+
const { final: _final, ...event } = flat;
|
|
213
|
+
return {
|
|
214
|
+
type: "statusUpdate",
|
|
215
|
+
event: event as unknown as A2AStreamEvent extends {
|
|
216
|
+
type: "statusUpdate";
|
|
217
|
+
event: infer E;
|
|
218
|
+
}
|
|
219
|
+
? E
|
|
220
|
+
: never,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
case "artifact-update":
|
|
224
|
+
if (!isArtifactUpdate(flat)) break;
|
|
225
|
+
return {
|
|
226
|
+
type: "artifactUpdate",
|
|
227
|
+
event: flat as unknown as A2AStreamEvent extends {
|
|
228
|
+
type: "artifactUpdate";
|
|
229
|
+
event: infer E;
|
|
230
|
+
}
|
|
231
|
+
? E
|
|
232
|
+
: never,
|
|
233
|
+
};
|
|
153
234
|
}
|
|
154
235
|
return null;
|
|
155
236
|
}
|
|
@@ -182,10 +263,20 @@ const ROLES: ReadonlySet<string> = new Set(
|
|
|
182
263
|
const isRole = (value: unknown): value is A2ARole =>
|
|
183
264
|
typeof value === "string" && ROLES.has(value);
|
|
184
265
|
|
|
266
|
+
// Ids reach task state and the next request body unchecked by anything
|
|
267
|
+
// downstream, so an id that is present and not null must be a string.
|
|
268
|
+
// An omitted or null id keeps the acceptance each path already had.
|
|
269
|
+
const hasOptionalStringIds = (
|
|
270
|
+
value: Record<string, unknown>,
|
|
271
|
+
keys: readonly string[],
|
|
272
|
+
): boolean =>
|
|
273
|
+
keys.every((key) => value[key] == null || typeof value[key] === "string");
|
|
274
|
+
|
|
185
275
|
const isTask = (value: unknown): value is A2ATask =>
|
|
186
276
|
isRecord(value) &&
|
|
187
277
|
typeof value.id === "string" &&
|
|
188
278
|
value.id.length > 0 &&
|
|
279
|
+
hasOptionalStringIds(value, ["contextId"]) &&
|
|
189
280
|
isRecord(value.status) &&
|
|
190
281
|
isTaskState(value.status.state);
|
|
191
282
|
|
|
@@ -193,13 +284,146 @@ const isMessage = (value: unknown): value is A2AMessage =>
|
|
|
193
284
|
isRecord(value) &&
|
|
194
285
|
typeof value.messageId === "string" &&
|
|
195
286
|
value.messageId.length > 0 &&
|
|
287
|
+
hasOptionalStringIds(value, ["contextId", "taskId"]) &&
|
|
196
288
|
isRole(value.role) &&
|
|
197
289
|
Array.isArray(value.parts) &&
|
|
198
290
|
value.parts.every(isRecord);
|
|
199
291
|
|
|
292
|
+
// Legacy wrappers use ProtoJSON, where omitted and null fields decode to proto
|
|
293
|
+
// defaults. Normalize those defaults before enforcing semantic requirements.
|
|
294
|
+
// Filling a null id with the ProtoJSON default does not make it a string, and
|
|
295
|
+
// the shape guards below check the fields they name rather than the ids. The
|
|
296
|
+
// runtime reads these straight into task state and the next request body.
|
|
297
|
+
const hasStringIds = (
|
|
298
|
+
value: Record<string, unknown>,
|
|
299
|
+
keys: readonly string[],
|
|
300
|
+
): boolean => keys.every((key) => typeof value[key] === "string");
|
|
301
|
+
|
|
302
|
+
const toWrappedTaskStatus = (
|
|
303
|
+
value: unknown,
|
|
304
|
+
): Record<string, unknown> | null => {
|
|
305
|
+
if (!isRecord(value)) return null;
|
|
306
|
+
return {
|
|
307
|
+
...value,
|
|
308
|
+
state: value.state == null ? "unspecified" : value.state,
|
|
309
|
+
};
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const toWrappedTask = (value: unknown): A2ATask | null => {
|
|
313
|
+
if (!isRecord(value)) return null;
|
|
314
|
+
const status = toWrappedTaskStatus(value.status);
|
|
315
|
+
if (!status) return null;
|
|
316
|
+
|
|
317
|
+
const task = {
|
|
318
|
+
...value,
|
|
319
|
+
contextId: value.contextId == null ? "" : value.contextId,
|
|
320
|
+
status,
|
|
321
|
+
};
|
|
322
|
+
return isTask(task) && hasStringIds(task, ["contextId"]) ? task : null;
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const toWrappedMessage = (value: unknown): A2AMessage | null => {
|
|
326
|
+
if (!isRecord(value)) return null;
|
|
327
|
+
|
|
328
|
+
const message = {
|
|
329
|
+
...value,
|
|
330
|
+
contextId: value.contextId == null ? "" : value.contextId,
|
|
331
|
+
taskId: value.taskId == null ? "" : value.taskId,
|
|
332
|
+
role: value.role == null ? "unspecified" : value.role,
|
|
333
|
+
parts: value.parts == null ? [] : value.parts,
|
|
334
|
+
};
|
|
335
|
+
return isMessage(message) && hasStringIds(message, ["contextId", "taskId"])
|
|
336
|
+
? message
|
|
337
|
+
: null;
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const isStatusUpdate = (
|
|
341
|
+
value: unknown,
|
|
342
|
+
allowEmptyTaskId = false,
|
|
343
|
+
): value is Record<string, unknown> =>
|
|
344
|
+
isRecord(value) &&
|
|
345
|
+
typeof value.taskId === "string" &&
|
|
346
|
+
(allowEmptyTaskId || value.taskId.length > 0) &&
|
|
347
|
+
hasOptionalStringIds(value, ["contextId"]) &&
|
|
348
|
+
isRecord(value.status) &&
|
|
349
|
+
isTaskState(value.status.state);
|
|
350
|
+
|
|
351
|
+
const toWrappedStatusUpdate = (
|
|
352
|
+
value: unknown,
|
|
353
|
+
): Record<string, unknown> | null => {
|
|
354
|
+
if (!isRecord(value) || !isRecord(value.status)) return null;
|
|
355
|
+
|
|
356
|
+
const statusUpdate = {
|
|
357
|
+
...value,
|
|
358
|
+
taskId: value.taskId == null ? "" : value.taskId,
|
|
359
|
+
contextId: value.contextId == null ? "" : value.contextId,
|
|
360
|
+
status: toWrappedTaskStatus(value.status),
|
|
361
|
+
};
|
|
362
|
+
return isStatusUpdate(statusUpdate, true) &&
|
|
363
|
+
hasStringIds(statusUpdate, ["taskId", "contextId"])
|
|
364
|
+
? statusUpdate
|
|
365
|
+
: null;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const isArtifact = (value: unknown): value is Record<string, unknown> =>
|
|
369
|
+
isRecord(value) &&
|
|
370
|
+
typeof value.artifactId === "string" &&
|
|
371
|
+
Array.isArray(value.parts) &&
|
|
372
|
+
value.parts.every(isRecord);
|
|
373
|
+
|
|
374
|
+
const isArtifactUpdate = (value: unknown): value is Record<string, unknown> =>
|
|
375
|
+
isRecord(value) &&
|
|
376
|
+
hasOptionalStringIds(value, ["contextId", "taskId"]) &&
|
|
377
|
+
isArtifact(value.artifact);
|
|
378
|
+
|
|
379
|
+
const toWrappedArtifact = (value: unknown): Record<string, unknown> | null => {
|
|
380
|
+
if (!isRecord(value)) return null;
|
|
381
|
+
|
|
382
|
+
const artifact = {
|
|
383
|
+
...value,
|
|
384
|
+
artifactId: value.artifactId == null ? "" : value.artifactId,
|
|
385
|
+
parts: value.parts == null ? [] : value.parts,
|
|
386
|
+
};
|
|
387
|
+
return isArtifact(artifact) ? artifact : null;
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
const toWrappedArtifactUpdate = (
|
|
391
|
+
value: unknown,
|
|
392
|
+
): Record<string, unknown> | null => {
|
|
393
|
+
if (!isRecord(value)) return null;
|
|
394
|
+
const artifact = toWrappedArtifact(value.artifact);
|
|
395
|
+
if (!artifact) return null;
|
|
396
|
+
|
|
397
|
+
const artifactUpdate = {
|
|
398
|
+
...value,
|
|
399
|
+
taskId: value.taskId == null ? "" : value.taskId,
|
|
400
|
+
contextId: value.contextId == null ? "" : value.contextId,
|
|
401
|
+
artifact,
|
|
402
|
+
};
|
|
403
|
+
return isArtifactUpdate(artifactUpdate) &&
|
|
404
|
+
hasStringIds(artifactUpdate, ["taskId", "contextId"])
|
|
405
|
+
? artifactUpdate
|
|
406
|
+
: null;
|
|
407
|
+
};
|
|
408
|
+
|
|
200
409
|
const isStringArray = (value: unknown): value is string[] =>
|
|
201
410
|
Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
202
411
|
|
|
412
|
+
const toJsonRpcError = (error: unknown): A2AError => {
|
|
413
|
+
const rpcError = error as { code?: number; message?: string; data?: unknown };
|
|
414
|
+
return new A2AError({
|
|
415
|
+
code: rpcError.code ?? -1,
|
|
416
|
+
status: "JSONRPC_ERROR",
|
|
417
|
+
message: rpcError.message ?? "A2A JSON-RPC error",
|
|
418
|
+
details:
|
|
419
|
+
rpcError.data === undefined
|
|
420
|
+
? undefined
|
|
421
|
+
: Array.isArray(rpcError.data)
|
|
422
|
+
? rpcError.data
|
|
423
|
+
: [rpcError.data],
|
|
424
|
+
});
|
|
425
|
+
};
|
|
426
|
+
|
|
203
427
|
const invalidAgentCard = (): never => {
|
|
204
428
|
throw new Error(
|
|
205
429
|
"Invalid A2A agent card response: expected a valid agent card payload.",
|
|
@@ -412,6 +636,17 @@ function signalInit(signal?: AbortSignal): RequestInit {
|
|
|
412
636
|
return signal ? { signal } : {};
|
|
413
637
|
}
|
|
414
638
|
|
|
639
|
+
const SKIPPED_FRAME_SNIPPET_LENGTH = 120;
|
|
640
|
+
|
|
641
|
+
function describeSkippedFrame(data: string, reason: string): string {
|
|
642
|
+
const collapsed = data.replace(/\s+/g, " ");
|
|
643
|
+
const snippet =
|
|
644
|
+
collapsed.length > SKIPPED_FRAME_SNIPPET_LENGTH
|
|
645
|
+
? `${collapsed.slice(0, SKIPPED_FRAME_SNIPPET_LENGTH)}…`
|
|
646
|
+
: collapsed;
|
|
647
|
+
return `${reason} (frame: ${snippet})`;
|
|
648
|
+
}
|
|
649
|
+
|
|
415
650
|
export class A2AClient {
|
|
416
651
|
private baseUrl: string;
|
|
417
652
|
private basePath: string;
|
|
@@ -511,6 +746,19 @@ export class A2AClient {
|
|
|
511
746
|
await this.throwResponseError(response);
|
|
512
747
|
}
|
|
513
748
|
const json = await response.json();
|
|
749
|
+
if (json && typeof json === "object" && "jsonrpc" in json) {
|
|
750
|
+
if ("error" in json && json.error) {
|
|
751
|
+
throw toJsonRpcError(json.error);
|
|
752
|
+
}
|
|
753
|
+
if ("result" in json) {
|
|
754
|
+
const result = normalizeKeys(json.result);
|
|
755
|
+
if (isRecord(result) && typeof result.kind === "string") {
|
|
756
|
+
const { kind: _kind, ...rest } = result;
|
|
757
|
+
return rest as T;
|
|
758
|
+
}
|
|
759
|
+
return result as T;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
514
762
|
return normalizeKeys(json) as T;
|
|
515
763
|
}
|
|
516
764
|
|
|
@@ -594,7 +842,7 @@ export class A2AClient {
|
|
|
594
842
|
await this.throwResponseError(response);
|
|
595
843
|
}
|
|
596
844
|
|
|
597
|
-
yield* this.parseSSE(response);
|
|
845
|
+
return yield* this.parseSSE(response);
|
|
598
846
|
}
|
|
599
847
|
|
|
600
848
|
// --- Tasks ---
|
|
@@ -757,7 +1005,9 @@ export class A2AClient {
|
|
|
757
1005
|
|
|
758
1006
|
// --- SSE Parsing ---
|
|
759
1007
|
|
|
760
|
-
private async *parseSSE(
|
|
1008
|
+
private async *parseSSE(
|
|
1009
|
+
response: Response,
|
|
1010
|
+
): AsyncGenerator<A2AStreamEvent, string | undefined> {
|
|
761
1011
|
const contentType = response.headers.get("Content-Type");
|
|
762
1012
|
const mediaType = contentType?.split(";", 1)[0]?.trim().toLowerCase();
|
|
763
1013
|
if (mediaType !== "text/event-stream") {
|
|
@@ -776,22 +1026,34 @@ export class A2AClient {
|
|
|
776
1026
|
const decoder = new TextDecoder();
|
|
777
1027
|
const sseDecoder = new SSEEventDecoder();
|
|
778
1028
|
|
|
1029
|
+
let firstSkipReason: string | undefined;
|
|
1030
|
+
const noteSkip = (data: string, reason: string) => {
|
|
1031
|
+
firstSkipReason ??= describeSkippedFrame(data, reason);
|
|
1032
|
+
};
|
|
1033
|
+
|
|
779
1034
|
const readEvent = (event: SSEEvent): A2AStreamEvent | null => {
|
|
780
1035
|
try {
|
|
781
1036
|
let parsed = JSON.parse(event.data);
|
|
782
1037
|
|
|
783
|
-
if (
|
|
784
|
-
parsed &&
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
"result" in parsed
|
|
788
|
-
|
|
789
|
-
|
|
1038
|
+
if (parsed && typeof parsed === "object" && "jsonrpc" in parsed) {
|
|
1039
|
+
if ("error" in parsed && parsed.error) {
|
|
1040
|
+
throw toJsonRpcError(parsed.error);
|
|
1041
|
+
}
|
|
1042
|
+
if ("result" in parsed) {
|
|
1043
|
+
parsed = parsed.result;
|
|
1044
|
+
}
|
|
790
1045
|
}
|
|
791
1046
|
|
|
792
1047
|
const normalized = normalizeKeys(parsed) as Record<string, unknown>;
|
|
793
|
-
|
|
794
|
-
|
|
1048
|
+
const streamEvent = discriminateStreamResponse(normalized);
|
|
1049
|
+
if (!streamEvent) noteSkip(event.data, "unrecognized event shape");
|
|
1050
|
+
return streamEvent;
|
|
1051
|
+
} catch (error) {
|
|
1052
|
+
if (error instanceof A2AError) throw error;
|
|
1053
|
+
noteSkip(
|
|
1054
|
+
event.data,
|
|
1055
|
+
error instanceof Error ? error.message : String(error),
|
|
1056
|
+
);
|
|
795
1057
|
return null;
|
|
796
1058
|
}
|
|
797
1059
|
};
|
|
@@ -831,5 +1093,7 @@ export class A2AClient {
|
|
|
831
1093
|
reader.releaseLock();
|
|
832
1094
|
}
|
|
833
1095
|
}
|
|
1096
|
+
|
|
1097
|
+
return firstSkipReason;
|
|
834
1098
|
}
|
|
835
1099
|
}
|