@alvin0/ai-agent-sdk-a2a 0.1.0

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.
@@ -0,0 +1,651 @@
1
+ import { i as beginA2AIntegrationOperation, n as a2aErrorCode, t as cleanupFailure } from "./cleanup-report-CTjMJ9EU.mjs";
2
+ import { Role, TaskState } from "@a2a-js/sdk";
3
+ import { ClientFactory, ClientFactory as ClientFactory$1, DefaultAgentCardResolver, DefaultAgentCardResolver as DefaultAgentCardResolver$1, JsonRpcTransportFactory, JsonRpcTransportFactory as JsonRpcTransportFactory$1, RestTransportFactory, RestTransportFactory as RestTransportFactory$1 } from "@a2a-js/sdk/client";
4
+ import { detachedFrozen, waitForSettlement } from "@alvin0/ai-agent-sdk-core";
5
+
6
+ //#region src/client/link-helpers.ts
7
+ function unlinkReport(status, alreadyUnlinked, error = status === "failed" ? cleanupFailure("A2A_UNLINK_FAILED", "a2a-unlink", "A2A link removal failed") : void 0) {
8
+ return Object.freeze({
9
+ status,
10
+ alreadyUnlinked,
11
+ ...error === void 0 ? {} : { error }
12
+ });
13
+ }
14
+ function defaultFactory(options) {
15
+ const transportOptions = {
16
+ ...options.fetch === void 0 ? {} : { fetchImpl: options.fetch },
17
+ legacyCompat: { enabled: options.legacyCompat ?? false }
18
+ };
19
+ return new ClientFactory({ transports: [new JsonRpcTransportFactory(transportOptions), new RestTransportFactory(transportOptions)] });
20
+ }
21
+
22
+ //#endregion
23
+ //#region src/client/http-redirect.ts
24
+ const REDIRECT_STATUSES = /* @__PURE__ */ new Set([
25
+ 301,
26
+ 302,
27
+ 303,
28
+ 307,
29
+ 308
30
+ ]);
31
+ const MAX_REDIRECT_HOPS = 5;
32
+ /** Apply redirect policy manually so every target is validated before contact. */
33
+ async function fetchA2AEndpoint(baseFetch, input, init, options) {
34
+ let currentInput = input;
35
+ let currentUrl = options.validateEndpoint(inputUrl(input));
36
+ let requestInit = {
37
+ ...init,
38
+ signal: options.signal,
39
+ redirect: "manual"
40
+ };
41
+ for (let hop = 0;; hop++) {
42
+ const response = await raceAbort(Promise.resolve(baseFetch(currentInput, requestInit)), options.signal);
43
+ if (response.redirected === true || response.type === "opaqueredirect" || response.url.length > 0 && response.url !== currentUrl.href) {
44
+ await cancelResponse(response, options.teardownTimeoutMs);
45
+ throw new Error("A2A HTTP transport rejected an already-followed redirect");
46
+ }
47
+ if (!REDIRECT_STATUSES.has(response.status)) return response;
48
+ if (!options.allowRedirects) {
49
+ await cancelResponse(response, options.teardownTimeoutMs);
50
+ throw new Error("A2A HTTP transport rejected a redirect");
51
+ }
52
+ if (hop >= MAX_REDIRECT_HOPS) {
53
+ await cancelResponse(response, options.teardownTimeoutMs);
54
+ throw new Error(`A2A HTTP transport exceeded the ${MAX_REDIRECT_HOPS}-redirect limit`);
55
+ }
56
+ const location = response.headers.get("location");
57
+ if (location === null) {
58
+ await cancelResponse(response, options.teardownTimeoutMs);
59
+ throw new Error("A2A HTTP transport received a redirect without a location");
60
+ }
61
+ const nextUrl = options.validateEndpoint(new URL(location, currentUrl));
62
+ await cancelResponse(response, options.teardownTimeoutMs);
63
+ requestInit = redirectedInit(requestInit, input, response.status, nextUrl.origin !== currentUrl.origin);
64
+ currentInput = nextUrl;
65
+ currentUrl = nextUrl;
66
+ }
67
+ }
68
+ function redirectedInit(previous, original, status, crossesOrigin) {
69
+ const inputMethod = typeof Request !== "undefined" && original instanceof Request ? original.method : void 0;
70
+ const method = (previous.method ?? inputMethod ?? "GET").toUpperCase();
71
+ const switchesToGet = status === 303 || (status === 301 || status === 302) && method === "POST";
72
+ const inputHasBody = typeof Request !== "undefined" && original instanceof Request && original.body !== null;
73
+ if (!switchesToGet && (inputHasBody || typeof ReadableStream !== "undefined" && previous.body instanceof ReadableStream)) throw new Error("A2A HTTP transport cannot replay a streaming request across a redirect");
74
+ const sourceHeaders = previous.headers ?? (typeof Request !== "undefined" && original instanceof Request ? original.headers : void 0);
75
+ const headers = crossesOrigin ? new Headers() : new Headers(sourceHeaders);
76
+ if (switchesToGet) {
77
+ headers.delete("content-length");
78
+ headers.delete("content-type");
79
+ }
80
+ return {
81
+ ...previous,
82
+ redirect: "manual",
83
+ headers,
84
+ ...switchesToGet ? {
85
+ method: "GET",
86
+ body: null
87
+ } : {}
88
+ };
89
+ }
90
+ function inputUrl(input) {
91
+ return typeof input === "string" || input instanceof URL ? input.toString() : input.url;
92
+ }
93
+ async function cancelResponse(response, timeoutMs) {
94
+ if (response.body === null) return;
95
+ await waitForSettlement(response.body.cancel().catch(() => void 0), timeoutMs);
96
+ }
97
+ function raceAbort(pending, signal) {
98
+ if (signal.aborted) return Promise.reject(signal.reason ?? /* @__PURE__ */ new Error("A2A operation aborted"));
99
+ return new Promise((resolve, reject) => {
100
+ const abort = () => {
101
+ cleanup();
102
+ reject(signal.reason ?? /* @__PURE__ */ new Error("A2A operation aborted"));
103
+ };
104
+ const cleanup = () => signal.removeEventListener("abort", abort);
105
+ signal.addEventListener("abort", abort, { once: true });
106
+ pending.then((value) => {
107
+ cleanup();
108
+ resolve(value);
109
+ }, (error) => {
110
+ cleanup();
111
+ reject(error);
112
+ });
113
+ });
114
+ }
115
+
116
+ //#endregion
117
+ //#region src/client.ts
118
+ /** Official A2A Protocol client transport and AgentTeam linking helpers. */
119
+ /** A resolved official SDK client presented as an AgentTeam transport. */
120
+ var A2AAgentLink = class {
121
+ protocol = "a2a/1.0";
122
+ agentId;
123
+ client;
124
+ agentCard;
125
+ options;
126
+ contexts = /* @__PURE__ */ new Map();
127
+ pendingContextKeys = /* @__PURE__ */ new Set();
128
+ timeoutMs;
129
+ teardownTimeoutMs;
130
+ maxRequestBytes;
131
+ maxResponseBytes;
132
+ maxTransportBytes;
133
+ maxStreamEvents;
134
+ maxStreamBytes;
135
+ maxContexts;
136
+ contextTtlMs;
137
+ constructor(client, options, agentCard) {
138
+ this.client = client;
139
+ this.options = snapshotLinkOptions(options);
140
+ this.agentCard = agentCard === void 0 ? void 0 : detachedFrozen(agentCard);
141
+ this.agentId = nonEmpty(options.agentId ?? agentCard?.name ?? options.baseUrl, "A2A linked agent id");
142
+ this.timeoutMs = positiveInteger(options.timeoutMs ?? 12e4, "timeoutMs");
143
+ this.teardownTimeoutMs = positiveInteger(options.teardownTimeoutMs ?? 3e4, "teardownTimeoutMs");
144
+ this.maxRequestBytes = positiveInteger(options.maxRequestBytes ?? 1048576, "maxRequestBytes");
145
+ this.maxResponseBytes = positiveInteger(options.maxResponseBytes ?? 1048576, "maxResponseBytes");
146
+ this.maxTransportBytes = positiveInteger(options.maxTransportBytes ?? 16777216, "maxTransportBytes");
147
+ this.maxStreamEvents = positiveInteger(options.maxStreamEvents ?? 1e4, "maxStreamEvents");
148
+ this.maxStreamBytes = positiveInteger(options.maxStreamBytes ?? 8388608, "maxStreamBytes");
149
+ this.maxContexts = positiveInteger(options.maxContexts ?? 1e3, "maxContexts");
150
+ this.contextTtlMs = positiveInteger(options.contextTtlMs ?? 18e5, "contextTtlMs");
151
+ if (options.historyLength !== void 0) positiveInteger(options.historyLength, "historyLength");
152
+ }
153
+ async send(input) {
154
+ const operation = beginA2AIntegrationOperation(input.logger, "a2a-client-link", "send");
155
+ const attempt = operation.attempt(1);
156
+ let contextKey;
157
+ let signal = input.signal;
158
+ try {
159
+ input.signal?.throwIfAborted();
160
+ contextKey = JSON.stringify([input.teamId, input.sender]);
161
+ const context = this.reserveContext(contextKey);
162
+ const request = this.request(input, context?.id);
163
+ if (byteLength(request) > this.maxRequestBytes) throw new Error(`A2A request exceeds the ${this.maxRequestBytes}-byte limit`);
164
+ signal = combineSignals(input.signal, AbortSignal.timeout(this.timeoutMs));
165
+ const requestOptions = {
166
+ signal,
167
+ ...this.options.serviceParameters === void 0 ? {} : { serviceParameters: this.options.serviceParameters }
168
+ };
169
+ const streaming = this.options.streaming ?? this.agentCard?.capabilities?.streaming ?? false;
170
+ let result;
171
+ if (streaming) {
172
+ const stream = beginA2AIntegrationOperation(input.logger, "a2a-client-link", "stream");
173
+ const streamAttempt = stream.attempt(1);
174
+ try {
175
+ result = await this.sendStreaming(request, requestOptions);
176
+ streamAttempt.success();
177
+ stream.success();
178
+ } catch (error) {
179
+ if (signal.aborted) {
180
+ streamAttempt.abort();
181
+ stream.abort();
182
+ } else {
183
+ const code = a2aErrorCode(error);
184
+ streamAttempt.fail(code);
185
+ stream.fail(code);
186
+ }
187
+ throw error;
188
+ }
189
+ } else {
190
+ const wireResult = await raceWithSignal(this.client.sendMessage(request, requestOptions), signal);
191
+ if (byteLength(wireResult) > this.maxTransportBytes) throw new Error(`A2A transport response exceeds the ${this.maxTransportBytes}-byte limit`);
192
+ result = normalizeResult(wireResult);
193
+ }
194
+ if (byteLength(result) > this.maxResponseBytes) throw new Error(`A2A response exceeds the ${this.maxResponseBytes}-byte limit`);
195
+ if (result.contextId.length > 0) this.contexts.set(contextKey, {
196
+ id: result.contextId,
197
+ lastAccess: Date.now()
198
+ });
199
+ attempt.success();
200
+ operation.success();
201
+ return result;
202
+ } catch (error) {
203
+ if (signal?.aborted === true) {
204
+ attempt.abort();
205
+ operation.abort();
206
+ } else {
207
+ const code = a2aErrorCode(error);
208
+ attempt.fail(code);
209
+ operation.fail(code);
210
+ }
211
+ throw error;
212
+ } finally {
213
+ if (contextKey !== void 0) this.pendingContextKeys.delete(contextKey);
214
+ }
215
+ }
216
+ request(input, contextId) {
217
+ return {
218
+ tenant: "",
219
+ message: {
220
+ messageId: input.messageId,
221
+ contextId: contextId ?? "",
222
+ taskId: "",
223
+ role: Role.ROLE_USER,
224
+ parts: input.content.map(contentPart),
225
+ metadata: {
226
+ teamId: input.teamId,
227
+ sender: input.sender,
228
+ senderAgentId: input.senderAgentId
229
+ },
230
+ extensions: [],
231
+ referenceTaskIds: []
232
+ },
233
+ configuration: {
234
+ acceptedOutputModes: [...this.options.acceptedOutputModes ?? ["text/plain", "application/json"]],
235
+ taskPushNotificationConfig: void 0,
236
+ ...this.options.historyLength === void 0 ? {} : { historyLength: this.options.historyLength },
237
+ returnImmediately: false
238
+ },
239
+ metadata: {
240
+ teamId: input.teamId,
241
+ sender: input.sender
242
+ }
243
+ };
244
+ }
245
+ async sendStreaming(request, options) {
246
+ let lastTask;
247
+ let lastMessage;
248
+ let taskId = "";
249
+ let contextId = request.message?.contextId ?? "";
250
+ let state;
251
+ const streamedArtifactText = [];
252
+ const streamedStatusText = [];
253
+ let eventCount = 0;
254
+ let streamBytes = 0;
255
+ const iterator = this.client.sendMessageStream(request, options)[Symbol.asyncIterator]();
256
+ let exhausted = false;
257
+ try {
258
+ while (true) {
259
+ const next = await raceWithSignal(iterator.next(), options.signal);
260
+ if (next.done === true) {
261
+ exhausted = true;
262
+ break;
263
+ }
264
+ const rawEvent = next.value;
265
+ eventCount++;
266
+ streamBytes += byteLength(rawEvent);
267
+ if (eventCount > this.maxStreamEvents) throw new Error(`A2A stream exceeds the ${this.maxStreamEvents}-event limit`);
268
+ if (streamBytes > this.maxStreamBytes) throw new Error(`A2A stream exceeds the ${this.maxStreamBytes}-byte limit`);
269
+ const event = detachedFrozen(rawEvent);
270
+ try {
271
+ this.options.onStreamEvent?.(event);
272
+ } catch {}
273
+ const payload = event.payload;
274
+ if (payload?.$case === "task") {
275
+ lastTask = payload.value;
276
+ taskId = payload.value.id;
277
+ contextId = payload.value.contextId;
278
+ state = payload.value.status?.state;
279
+ } else if (payload?.$case === "message") {
280
+ lastMessage = payload.value;
281
+ contextId = payload.value.contextId;
282
+ taskId = payload.value.taskId;
283
+ } else if (payload?.$case === "statusUpdate") {
284
+ taskId = payload.value.taskId;
285
+ contextId = payload.value.contextId;
286
+ state = payload.value.status?.state;
287
+ const text = textOfMessage(payload.value.status?.message);
288
+ if (text.length > 0) streamedStatusText.push(text);
289
+ } else if (payload?.$case === "artifactUpdate") {
290
+ taskId = payload.value.taskId;
291
+ contextId = payload.value.contextId;
292
+ const text = textOfParts(payload.value.artifact?.parts ?? []);
293
+ if (text.length > 0) streamedArtifactText.push(text);
294
+ }
295
+ }
296
+ } finally {
297
+ if (!exhausted) {
298
+ const close = iterator.return?.bind(iterator);
299
+ if (close !== void 0) {
300
+ if (!await waitForSettlement(Promise.resolve().then(async () => {
301
+ await close();
302
+ }), this.teardownTimeoutMs)) throw new Error(`A2A stream teardown exceeded ${this.teardownTimeoutMs}ms`);
303
+ }
304
+ }
305
+ }
306
+ if (lastMessage !== void 0 && (state === void 0 || taskId.length === 0)) return normalizeMessage(lastMessage);
307
+ if (lastMessage !== void 0) return Object.freeze({
308
+ kind: "task",
309
+ succeeded: state === TaskState.TASK_STATE_COMPLETED,
310
+ text: textOfMessage(lastMessage) || streamedArtifactText.join("") || streamedStatusText.at(-1) || "",
311
+ contextId,
312
+ taskId,
313
+ ...state === void 0 ? {} : { state: taskStateName(state) }
314
+ });
315
+ if (lastTask !== void 0) {
316
+ const normalized = normalizeTask(lastTask);
317
+ const streamed = streamedArtifactText.join("") || streamedStatusText.at(-1) || "";
318
+ const effectiveState = state ?? lastTask.status?.state;
319
+ return Object.freeze({
320
+ ...normalized,
321
+ succeeded: effectiveState === TaskState.TASK_STATE_COMPLETED,
322
+ text: normalized.text || streamed,
323
+ ...effectiveState === void 0 ? {} : { state: taskStateName(effectiveState) }
324
+ });
325
+ }
326
+ if (taskId.length === 0) throw new Error("A2A stream ended without a message or task");
327
+ return Object.freeze({
328
+ kind: "task",
329
+ succeeded: state === TaskState.TASK_STATE_COMPLETED,
330
+ text: streamedArtifactText.join("") || streamedStatusText.at(-1) || "",
331
+ contextId,
332
+ taskId,
333
+ ...state === void 0 ? {} : { state: taskStateName(state) }
334
+ });
335
+ }
336
+ reserveContext(key) {
337
+ const now = Date.now();
338
+ for (const [candidate, value] of this.contexts) if (now - value.lastAccess >= this.contextTtlMs) this.contexts.delete(candidate);
339
+ const existing = this.contexts.get(key);
340
+ if (existing !== void 0) {
341
+ existing.lastAccess = now;
342
+ return existing;
343
+ }
344
+ if (!this.pendingContextKeys.has(key) && this.contexts.size + this.pendingContextKeys.size >= this.maxContexts) throw new Error(`A2A link reached its ${this.maxContexts}-context limit`);
345
+ this.pendingContextKeys.add(key);
346
+ }
347
+ };
348
+ /** Discover an Agent Card and construct a protocol link with official transports. */
349
+ async function createA2AAgentLink(options) {
350
+ const operation = beginA2AIntegrationOperation(options.logger, "a2a-client-link", "agent-card-resolve");
351
+ const attempt = operation.attempt(1);
352
+ try {
353
+ options = snapshotLinkOptions(options);
354
+ if ([
355
+ options.client,
356
+ options.agentCard,
357
+ options.baseUrl
358
+ ].filter((value) => value !== void 0).length !== 1) throw new TypeError("createA2AAgentLink requires exactly one of client, agentCard, or baseUrl");
359
+ if (options.client !== void 0) {
360
+ const link = new A2AAgentLink(options.client, options);
361
+ attempt.success();
362
+ operation.success();
363
+ return link;
364
+ }
365
+ if (options.agentCard !== void 0) {
366
+ const cardOptions = { ...options };
367
+ validateAgentCard(options.agentCard, cardOptions);
368
+ const guardedFetch = endpointFetch(options.fetch ?? globalThis.fetch, cardOptions);
369
+ const factory = options.clientFactory ?? defaultFactory({
370
+ ...cardOptions,
371
+ fetch: guardedFetch
372
+ });
373
+ const signal = AbortSignal.timeout(positiveInteger(options.timeoutMs ?? 12e4, "timeoutMs"));
374
+ const link = new A2AAgentLink(await raceWithSignal(factory.createFromAgentCard(options.agentCard), signal), cardOptions, options.agentCard);
375
+ attempt.success();
376
+ operation.success();
377
+ return link;
378
+ }
379
+ const baseUrl = validateEndpoint(options.baseUrl, options);
380
+ const discoveryOptions = { ...options };
381
+ const guardedFetch = endpointFetch(options.fetch ?? globalThis.fetch, discoveryOptions);
382
+ const resolver = new DefaultAgentCardResolver({
383
+ fetchImpl: guardedFetch,
384
+ legacyCompat: { enabled: options.legacyCompat ?? false }
385
+ });
386
+ const signal = AbortSignal.timeout(positiveInteger(options.timeoutMs ?? 12e4, "timeoutMs"));
387
+ const agentCard = await raceWithSignal(resolver.resolve(baseUrl.href, options.cardPath), signal);
388
+ validateAgentCard(agentCard, discoveryOptions);
389
+ const client = await raceWithSignal((options.clientFactory ?? defaultFactory({
390
+ ...discoveryOptions,
391
+ fetch: guardedFetch
392
+ })).createFromAgentCard(agentCard), signal);
393
+ const link = new A2AAgentLink(client, discoveryOptions, agentCard);
394
+ attempt.success();
395
+ operation.success();
396
+ return link;
397
+ } catch (error) {
398
+ const code = a2aErrorCode(error);
399
+ attempt.fail(code);
400
+ operation.fail(code);
401
+ throw error;
402
+ }
403
+ }
404
+ /** Discover and add a remote A2A peer to the same roster local agents use. */
405
+ async function linkA2AAgent(team, options) {
406
+ const linkOperation = beginA2AIntegrationOperation(options.logger, "a2a-client-link", "link");
407
+ const linkAttempt = linkOperation.attempt(1);
408
+ let link;
409
+ let removeLink;
410
+ try {
411
+ link = await createA2AAgentLink(options);
412
+ removeLink = team.linkAgent({
413
+ name: options.name,
414
+ transport: link,
415
+ ...options.description === void 0 ? {} : { description: options.description }
416
+ });
417
+ linkAttempt.success();
418
+ linkOperation.success();
419
+ } catch (error) {
420
+ const code = a2aErrorCode(error);
421
+ linkAttempt.fail(code);
422
+ linkOperation.fail(code);
423
+ throw error;
424
+ }
425
+ let report;
426
+ const unlink = () => {
427
+ if (report !== void 0) return;
428
+ const operation = beginA2AIntegrationOperation(options.logger, "a2a-client-link", "unlink");
429
+ const attempt = operation.attempt(1);
430
+ try {
431
+ removeLink();
432
+ report = unlinkReport("unlinked", false);
433
+ attempt.success();
434
+ operation.success();
435
+ } catch (error) {
436
+ report = unlinkReport("failed", false);
437
+ const code = a2aErrorCode(error);
438
+ attempt.fail(code);
439
+ operation.fail(code);
440
+ throw error;
441
+ }
442
+ };
443
+ const unlinkWithReport = () => {
444
+ if (report !== void 0) return unlinkReport(report.status, true, report.error);
445
+ const operation = beginA2AIntegrationOperation(options.logger, "a2a-client-link", "unlink");
446
+ const attempt = operation.attempt(1);
447
+ try {
448
+ removeLink();
449
+ report = unlinkReport("unlinked", false);
450
+ attempt.success();
451
+ operation.success();
452
+ } catch (error) {
453
+ report = unlinkReport("failed", false);
454
+ const code = a2aErrorCode(error);
455
+ attempt.fail(code);
456
+ operation.fail(code);
457
+ }
458
+ return report;
459
+ };
460
+ return Object.freeze({
461
+ link,
462
+ unlink,
463
+ unlinkWithReport
464
+ });
465
+ }
466
+ function contentPart(block) {
467
+ if (block.type === "text") return part({
468
+ $case: "text",
469
+ value: block.text
470
+ }, "text/plain");
471
+ if (block.type === "image") {
472
+ if (block.source.kind === "url") return part({
473
+ $case: "url",
474
+ value: block.source.url
475
+ }, "image/*");
476
+ if (block.source.kind === "base64") return part({
477
+ $case: "url",
478
+ value: `data:${block.source.mediaType};base64,${block.source.data}`
479
+ }, block.source.mediaType);
480
+ return part({
481
+ $case: "data",
482
+ value: {
483
+ type: "image-file",
484
+ fileId: block.source.fileId
485
+ }
486
+ }, "application/json");
487
+ }
488
+ return part({
489
+ $case: "data",
490
+ value: structuredClone(block)
491
+ }, "application/json");
492
+ }
493
+ function part(content, mediaType) {
494
+ return {
495
+ content,
496
+ metadata: void 0,
497
+ filename: "",
498
+ mediaType
499
+ };
500
+ }
501
+ function normalizeResult(result) {
502
+ return "messageId" in result ? normalizeMessage(result) : normalizeTask(result);
503
+ }
504
+ function normalizeMessage(message) {
505
+ return Object.freeze({
506
+ kind: "message",
507
+ succeeded: message.role === Role.ROLE_AGENT,
508
+ text: textOfMessage(message),
509
+ contextId: message.contextId,
510
+ ...message.taskId.length === 0 ? {} : { taskId: message.taskId }
511
+ });
512
+ }
513
+ function normalizeTask(task) {
514
+ const state = task.status?.state;
515
+ const artifactText = task.artifacts.map((artifact) => textOfParts(artifact.parts)).filter(Boolean).join("\n");
516
+ const statusText = textOfMessage(task.status?.message);
517
+ const historyText = [...task.history].reverse().find((message) => message.role === Role.ROLE_AGENT);
518
+ return Object.freeze({
519
+ kind: "task",
520
+ succeeded: state === TaskState.TASK_STATE_COMPLETED,
521
+ text: artifactText || statusText || textOfMessage(historyText),
522
+ contextId: task.contextId,
523
+ taskId: task.id,
524
+ ...state === void 0 ? {} : { state: taskStateName(state) }
525
+ });
526
+ }
527
+ function textOfMessage(message) {
528
+ return message === void 0 ? "" : textOfParts(message.parts);
529
+ }
530
+ function textOfParts(parts) {
531
+ return parts.flatMap((item) => item.content?.$case === "text" ? [item.content.value] : []).join("");
532
+ }
533
+ function taskStateName(state) {
534
+ return TaskState[state] ?? String(state);
535
+ }
536
+ function nonEmpty(value, label) {
537
+ if (typeof value !== "string" || value.trim().length === 0) throw new TypeError(`${label} must be a non-empty string`);
538
+ if (value.length > 256) throw new TypeError(`${label} must be at most 256 characters`);
539
+ return value;
540
+ }
541
+ function positiveInteger(value, label) {
542
+ if (!Number.isSafeInteger(value) || value < 1) throw new TypeError(`${label} must be a positive integer`);
543
+ return value;
544
+ }
545
+ function utf8Bytes(value) {
546
+ return new TextEncoder().encode(value).byteLength;
547
+ }
548
+ function byteLength(value) {
549
+ const serialized = JSON.stringify(value);
550
+ if (serialized === void 0) throw new TypeError("A2A value is not JSON serializable");
551
+ return utf8Bytes(serialized);
552
+ }
553
+ function combineSignals(...signals) {
554
+ const active = signals.filter((signal) => signal !== void 0);
555
+ if (active.length === 1) return active[0];
556
+ return AbortSignal.any(active);
557
+ }
558
+ async function raceWithSignal(pending, signal) {
559
+ if (signal === void 0) return pending;
560
+ if (signal.aborted) throw signal.reason ?? /* @__PURE__ */ new Error("A2A operation aborted");
561
+ return await new Promise((resolve, reject) => {
562
+ const abort = () => {
563
+ signal.removeEventListener("abort", abort);
564
+ reject(signal.reason ?? /* @__PURE__ */ new Error("A2A operation aborted"));
565
+ };
566
+ signal.addEventListener("abort", abort, { once: true });
567
+ pending.then((value) => {
568
+ signal.removeEventListener("abort", abort);
569
+ resolve(value);
570
+ }, (error) => {
571
+ signal.removeEventListener("abort", abort);
572
+ reject(error);
573
+ });
574
+ });
575
+ }
576
+ function validateAgentCard(card, options) {
577
+ const maxBytes = positiveInteger(options.maxResponseBytes ?? 1048576, "maxResponseBytes");
578
+ if (byteLength(card) > maxBytes) throw new RangeError(`A2A Agent Card exceeds the ${maxBytes}-byte limit`);
579
+ if (card.supportedInterfaces.length === 0) throw new TypeError("A2A Agent Card must advertise at least one interface");
580
+ for (const item of card.supportedInterfaces) validateEndpoint(item.url, options);
581
+ }
582
+ function snapshotLinkOptions(options) {
583
+ return Object.freeze({
584
+ ...options,
585
+ ...options.allowedOrigins === void 0 ? {} : { allowedOrigins: Object.freeze([...options.allowedOrigins]) },
586
+ ...options.acceptedOutputModes === void 0 ? {} : { acceptedOutputModes: Object.freeze([...options.acceptedOutputModes]) },
587
+ ...options.serviceParameters === void 0 ? {} : { serviceParameters: detachedFrozen(options.serviceParameters) },
588
+ ...options.agentCard === void 0 ? {} : { agentCard: detachedFrozen(options.agentCard) }
589
+ });
590
+ }
591
+ function validateEndpoint(value, options) {
592
+ const url = new URL(value);
593
+ if (url.username.length > 0 || url.password.length > 0) throw new TypeError("A2A endpoint URL must not contain credentials");
594
+ if (url.protocol !== "https:" && url.protocol !== "http:") throw new TypeError("A2A endpoint URL must use http or https");
595
+ if (options.requireHttps === true && url.protocol !== "https:") throw new TypeError("A2A endpoint URL must use https under the configured policy");
596
+ const allowedOrigins = options.allowedOrigins?.map((origin) => new URL(origin).origin);
597
+ if (allowedOrigins !== void 0 && !allowedOrigins.includes(url.origin)) throw new TypeError(`A2A endpoint origin '${url.origin}' is not allowed`);
598
+ if (options.allowPrivateNetwork === false && isPrivateHostname(url.hostname)) throw new TypeError(`A2A endpoint host '${url.hostname}' is private or local`);
599
+ options.validateEndpoint?.(new URL(url));
600
+ return url;
601
+ }
602
+ function endpointFetch(baseFetch, options) {
603
+ if (typeof baseFetch !== "function") throw new TypeError("A2A endpoint resolution requires fetch");
604
+ const maxBytes = positiveInteger(options.maxTransportBytes ?? 16777216, "maxTransportBytes");
605
+ const timeoutMs = positiveInteger(options.timeoutMs ?? 12e4, "timeoutMs");
606
+ const teardownTimeoutMs = positiveInteger(options.teardownTimeoutMs ?? 3e4, "teardownTimeoutMs");
607
+ return (async (input, init) => {
608
+ validateEndpoint(typeof input === "string" || input instanceof URL ? input.toString() : input.url, options);
609
+ const signal = combineSignals(init?.signal ?? void 0, AbortSignal.timeout(timeoutMs));
610
+ const response = await fetchA2AEndpoint(baseFetch, input, init, {
611
+ signal,
612
+ allowRedirects: options.allowRedirects !== false,
613
+ teardownTimeoutMs,
614
+ validateEndpoint: (value) => validateEndpoint(value.toString(), options)
615
+ });
616
+ if (response.url.length > 0) validateEndpoint(response.url, options);
617
+ const declared = Number(response.headers.get("content-length"));
618
+ if (Number.isFinite(declared) && declared > maxBytes) {
619
+ if (response.body !== null) await waitForSettlement(response.body.cancel().catch(() => void 0), teardownTimeoutMs);
620
+ throw new Error(`A2A HTTP response exceeds the ${maxBytes}-byte limit`);
621
+ }
622
+ if (response.body === null) return response;
623
+ let received = 0;
624
+ const limited = response.body.pipeThrough(new TransformStream({ transform(chunk, controller) {
625
+ received += chunk.byteLength;
626
+ if (received > maxBytes) {
627
+ controller.error(/* @__PURE__ */ new Error(`A2A HTTP response exceeds the ${maxBytes}-byte limit`));
628
+ return;
629
+ }
630
+ controller.enqueue(chunk);
631
+ } }));
632
+ return new Response(limited, {
633
+ status: response.status,
634
+ statusText: response.statusText,
635
+ headers: response.headers
636
+ });
637
+ });
638
+ }
639
+ function isPrivateHostname(value) {
640
+ const hostname = value.toLowerCase().replace(/^\[|\]$/g, "");
641
+ if (hostname === "localhost" || hostname.endsWith(".localhost") || hostname.endsWith(".local") || hostname.endsWith(".internal") || hostname.endsWith(".home.arpa") || !hostname.includes(".")) return true;
642
+ if (hostname.includes(":")) return true;
643
+ const octets = hostname.split(".").map(Number);
644
+ if (octets.length !== 4 || octets.some((octet) => !Number.isInteger(octet) || octet < 0 || octet > 255)) return false;
645
+ const [first = 0, second = 0] = octets;
646
+ return first === 0 || first === 10 || first === 127 || first >= 224 || first === 100 && second >= 64 && second <= 127 || first === 169 && second === 254 || first === 172 && second >= 16 && second <= 31 || first === 192 && second === 168 || first === 198 && (second === 18 || second === 19);
647
+ }
648
+
649
+ //#endregion
650
+ export { RestTransportFactory$1 as a, JsonRpcTransportFactory$1 as i, ClientFactory$1 as n, createA2AAgentLink as o, DefaultAgentCardResolver$1 as r, linkA2AAgent as s, A2AAgentLink as t };
651
+ //# sourceMappingURL=client-2bhYYreC.mjs.map