@ap3x/a2a 1.0.0 → 2.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.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +27 -0
  3. package/dist/backend.d.ts +89 -0
  4. package/dist/backend.d.ts.map +1 -0
  5. package/dist/bindings/grpc.d.ts +121 -0
  6. package/dist/bindings/grpc.d.ts.map +1 -0
  7. package/dist/bindings/jsonrpc.d.ts +135 -0
  8. package/dist/bindings/jsonrpc.d.ts.map +1 -0
  9. package/dist/bindings/rest.d.ts +75 -0
  10. package/dist/bindings/rest.d.ts.map +1 -0
  11. package/dist/chunk-PYM7LV6R.js +1590 -0
  12. package/dist/client.d.ts +116 -0
  13. package/dist/client.d.ts.map +1 -0
  14. package/dist/executor.d.ts +91 -0
  15. package/dist/executor.d.ts.map +1 -0
  16. package/dist/grpc-5DAU6EYQ.js +591 -0
  17. package/dist/index.d.ts +9 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +454 -0
  20. package/dist/model.d.ts +480 -0
  21. package/dist/model.d.ts.map +1 -0
  22. package/dist/server.d.ts +60 -0
  23. package/dist/server.d.ts.map +1 -0
  24. package/dist/task-store.d.ts +61 -0
  25. package/dist/task-store.d.ts.map +1 -0
  26. package/package.json +26 -15
  27. package/proto/a2a.proto +813 -0
  28. package/proto/google/api/annotations.proto +6 -0
  29. package/proto/google/api/client.proto +4 -0
  30. package/proto/google/api/field_behavior.proto +4 -0
  31. package/src/__tests__/a2a-auth-expired.test.ts +0 -84
  32. package/src/__tests__/a2a.test.ts +0 -87
  33. package/src/__tests__/handler-sendsubscribe-error.test.ts +0 -32
  34. package/src/__tests__/sendsubscribe-rejected-promise.test.ts +0 -89
  35. package/src/__tests__/server-apikey-warning.test.ts +0 -71
  36. package/src/__tests__/server-body-limit.test.ts +0 -54
  37. package/src/__tests__/server-security-headers.test.ts +0 -112
  38. package/src/__tests__/server-timing-safe-auth.test.ts +0 -45
  39. package/src/__tests__/sse-handler-error-propagation.test.ts +0 -69
  40. package/src/card/generator.ts +0 -18
  41. package/src/client/index.ts +0 -172
  42. package/src/errors.ts +0 -21
  43. package/src/index.ts +0 -15
  44. package/src/server/handler.ts +0 -90
  45. package/src/server/index.ts +0 -208
  46. package/src/server/sse.ts +0 -49
  47. package/src/swarm/a2a-agent.ts +0 -34
  48. package/src/types.ts +0 -50
  49. package/tsconfig.json +0 -20
  50. package/vitest.config.ts +0 -4
@@ -0,0 +1,1590 @@
1
+ // src/model.ts
2
+ import { Type } from "@sinclair/typebox";
3
+ var A2A_PROTOCOL_VERSION = "1.0.0";
4
+ var A2A_CARD_PROTOCOL_VERSION = "0.3.0";
5
+ var TASK_STATES = [
6
+ "submitted",
7
+ "working",
8
+ "input_required",
9
+ "auth_required",
10
+ "completed",
11
+ "failed",
12
+ "canceled",
13
+ "rejected"
14
+ ];
15
+ var TERMINAL_TASK_STATES = /* @__PURE__ */ new Set([
16
+ "completed",
17
+ "failed",
18
+ "canceled",
19
+ "rejected"
20
+ ]);
21
+ function isTerminal(state) {
22
+ return TERMINAL_TASK_STATES.has(state);
23
+ }
24
+ var TaskStateSchema = Type.Union(TASK_STATES.map((state) => Type.Literal(state)));
25
+ var WIRE_TASK_STATE_ALIASES = {
26
+ "input-required": "input_required",
27
+ "auth-required": "auth_required",
28
+ unknown: "submitted"
29
+ };
30
+ function normalizeTaskState(state) {
31
+ return typeof state === "string" ? WIRE_TASK_STATE_ALIASES[state] ?? state : state;
32
+ }
33
+ var OPAQUE_WIRE_KEYS = /* @__PURE__ */ new Set(["metadata", "data"]);
34
+ function normalizeWireTaskStates(value) {
35
+ if (Array.isArray(value)) return value.map((item) => normalizeWireTaskStates(item));
36
+ if (value === null || typeof value !== "object") return value;
37
+ const out = {};
38
+ for (const [key, entry] of Object.entries(value)) {
39
+ if (OPAQUE_WIRE_KEYS.has(key)) out[key] = entry;
40
+ else if (key === "state") out[key] = normalizeTaskState(entry);
41
+ else out[key] = normalizeWireTaskStates(entry);
42
+ }
43
+ return out;
44
+ }
45
+ var Metadata = Type.Record(Type.String(), Type.Unknown());
46
+ var partExtras = {
47
+ mediaType: Type.Optional(Type.String()),
48
+ metadata: Type.Optional(Metadata)
49
+ };
50
+ var TextPartSchema = Type.Object({
51
+ kind: Type.Literal("text"),
52
+ text: Type.String(),
53
+ ...partExtras
54
+ });
55
+ var DataPartSchema = Type.Object({
56
+ kind: Type.Literal("data"),
57
+ data: Type.Record(Type.String(), Type.Unknown()),
58
+ ...partExtras
59
+ });
60
+ var UrlPartSchema = Type.Object({
61
+ kind: Type.Literal("url"),
62
+ url: Type.String(),
63
+ ...partExtras
64
+ });
65
+ var RawPartSchema = Type.Object({
66
+ kind: Type.Literal("raw"),
67
+ data: Type.String(),
68
+ ...partExtras
69
+ });
70
+ var PartSchema = Type.Union([
71
+ TextPartSchema,
72
+ DataPartSchema,
73
+ UrlPartSchema,
74
+ RawPartSchema
75
+ ]);
76
+ var MessageSchema = Type.Object({
77
+ messageId: Type.String(),
78
+ contextId: Type.Optional(Type.String()),
79
+ taskId: Type.Optional(Type.String()),
80
+ role: Type.Union([Type.Literal("user"), Type.Literal("agent")]),
81
+ parts: Type.Array(PartSchema),
82
+ metadata: Type.Optional(Metadata)
83
+ });
84
+ var TaskStatusSchema = Type.Object({
85
+ state: TaskStateSchema,
86
+ /** Agent message accompanying the state (final text, error, or a streamed chunk). */
87
+ message: Type.Optional(MessageSchema),
88
+ /** ISO-8601 timestamp of when the state was entered. */
89
+ timestamp: Type.Optional(Type.String())
90
+ });
91
+ var ArtifactSchema = Type.Object({
92
+ artifactId: Type.String(),
93
+ name: Type.Optional(Type.String()),
94
+ description: Type.Optional(Type.String()),
95
+ parts: Type.Array(PartSchema),
96
+ metadata: Type.Optional(Metadata)
97
+ });
98
+ var TaskSchema = Type.Object({
99
+ id: Type.String(),
100
+ contextId: Type.String(),
101
+ status: TaskStatusSchema,
102
+ artifacts: Type.Optional(Type.Array(ArtifactSchema)),
103
+ history: Type.Optional(Type.Array(MessageSchema))
104
+ });
105
+ var TaskStatusUpdateEventSchema = Type.Object({
106
+ kind: Type.Literal("status-update"),
107
+ taskId: Type.String(),
108
+ contextId: Type.String(),
109
+ status: TaskStatusSchema,
110
+ /** True exactly when `status.state` is terminal — the stream ends here. */
111
+ final: Type.Boolean(),
112
+ metadata: Type.Optional(Metadata)
113
+ });
114
+ var TaskArtifactUpdateEventSchema = Type.Object({
115
+ kind: Type.Literal("artifact-update"),
116
+ taskId: Type.String(),
117
+ contextId: Type.String(),
118
+ artifact: ArtifactSchema,
119
+ append: Type.Optional(Type.Boolean()),
120
+ lastChunk: Type.Optional(Type.Boolean()),
121
+ metadata: Type.Optional(Metadata)
122
+ });
123
+ var AgentSkillSchema = Type.Object({
124
+ name: Type.String(),
125
+ description: Type.String(),
126
+ tags: Type.Optional(Type.Array(Type.String())),
127
+ examples: Type.Optional(Type.Array(Type.String()))
128
+ });
129
+ var AgentInterfaceSchema = Type.Object({
130
+ transport: Type.Union([Type.Literal("JSONRPC"), Type.Literal("HTTP+JSON"), Type.Literal("GRPC")]),
131
+ url: Type.String()
132
+ });
133
+ var BearerSecuritySchemeSchema = Type.Object({
134
+ type: Type.Literal("http"),
135
+ scheme: Type.Literal("bearer"),
136
+ description: Type.Optional(Type.String())
137
+ });
138
+ var AgentCapabilitiesSchema = Type.Object({
139
+ streaming: Type.Boolean(),
140
+ // Schema-only in v1: declared on the card, but webhook plumbing is deferred.
141
+ pushNotifications: Type.Boolean({ default: false })
142
+ });
143
+ var AgentProviderSchema = Type.Object({
144
+ name: Type.String(),
145
+ url: Type.Optional(Type.String())
146
+ });
147
+ var AgentCardSchema = Type.Object({
148
+ /** AP3X-assigned card id. Optional: reference-SDK cards carry no id. */
149
+ id: Type.Optional(Type.String()),
150
+ name: Type.String(),
151
+ description: Type.String(),
152
+ capabilities: AgentCapabilitiesSchema,
153
+ skills: Type.Array(AgentSkillSchema),
154
+ interfaces: Type.Array(AgentInterfaceSchema),
155
+ securitySchemes: Type.Optional(Type.Record(Type.String(), BearerSecuritySchemeSchema)),
156
+ provider: Type.Optional(AgentProviderSchema),
157
+ // Reference-SDK (0.3.x) card surface: the preferred endpoint lives at the
158
+ // TOP LEVEL (`url` + `preferredTransport`); `interfaces` above is kept as
159
+ // the richer AP3X view of the same endpoints.
160
+ /** The preferred endpoint URL (mirrors the first/preferred interface). */
161
+ url: Type.Optional(Type.String()),
162
+ /** Transport served at `url` (e.g. "JSONRPC"). */
163
+ preferredTransport: Type.Optional(Type.String()),
164
+ /** A2A protocol version, e.g. {@link A2A_CARD_PROTOCOL_VERSION}. */
165
+ protocolVersion: Type.Optional(Type.String()),
166
+ /** The agent's own version string. */
167
+ version: Type.Optional(Type.String()),
168
+ defaultInputModes: Type.Optional(Type.Array(Type.String())),
169
+ defaultOutputModes: Type.Optional(Type.Array(Type.String()))
170
+ });
171
+ var RemoteAgentInterfaceSchema = Type.Object({
172
+ transport: Type.String(),
173
+ url: Type.String()
174
+ });
175
+ var RemoteAgentCardSchema = Type.Object({
176
+ name: Type.String(),
177
+ description: Type.Optional(Type.String()),
178
+ id: Type.Optional(Type.String()),
179
+ capabilities: Type.Optional(
180
+ Type.Object({
181
+ streaming: Type.Optional(Type.Boolean()),
182
+ pushNotifications: Type.Optional(Type.Boolean())
183
+ })
184
+ ),
185
+ skills: Type.Optional(Type.Array(Type.Unknown())),
186
+ interfaces: Type.Optional(Type.Array(RemoteAgentInterfaceSchema)),
187
+ /** Reference-SDK name for extra transport/URL combinations. */
188
+ additionalInterfaces: Type.Optional(Type.Array(RemoteAgentInterfaceSchema)),
189
+ url: Type.Optional(Type.String()),
190
+ preferredTransport: Type.Optional(Type.String()),
191
+ securitySchemes: Type.Optional(Type.Unknown()),
192
+ provider: Type.Optional(Type.Unknown())
193
+ });
194
+ var A2A_ERROR_CODES = {
195
+ parseError: -32700,
196
+ invalidRequest: -32600,
197
+ methodNotFound: -32601,
198
+ invalidParams: -32602,
199
+ internalError: -32603,
200
+ taskNotFound: -32001,
201
+ taskNotCancelable: -32002,
202
+ pushNotificationNotSupported: -32003,
203
+ unsupportedOperation: -32004,
204
+ contentTypeNotSupported: -32005,
205
+ invalidAgentResponse: -32006
206
+ };
207
+
208
+ // src/task-store.ts
209
+ import { uuidv7 } from "@ap3x/agent-core";
210
+ var TaskNotFoundError = class extends Error {
211
+ taskId;
212
+ constructor(taskId) {
213
+ super(`A2A task not found: ${taskId}`);
214
+ this.name = "TaskNotFoundError";
215
+ this.taskId = taskId;
216
+ }
217
+ };
218
+ var IllegalTaskTransitionError = class extends Error {
219
+ taskId;
220
+ from;
221
+ to;
222
+ constructor(taskId, from, to) {
223
+ super(`illegal A2A task transition on ${taskId}: ${from} -> ${to} (terminal states are final)`);
224
+ this.name = "IllegalTaskTransitionError";
225
+ this.taskId = taskId;
226
+ this.from = from;
227
+ this.to = to;
228
+ }
229
+ };
230
+ var TaskStore = class {
231
+ now;
232
+ maxRetainedTasks;
233
+ tasks = /* @__PURE__ */ new Map();
234
+ subscribers = /* @__PURE__ */ new Map();
235
+ constructor(options) {
236
+ this.now = options.now;
237
+ if (options.maxRetainedTasks !== void 0) {
238
+ this.maxRetainedTasks = Math.max(0, Math.floor(options.maxRetainedTasks));
239
+ }
240
+ }
241
+ /** Create a task in the `submitted` state. */
242
+ create(options = {}) {
243
+ const task = {
244
+ id: uuidv7(),
245
+ contextId: options.contextId ?? uuidv7(),
246
+ status: { state: "submitted", timestamp: this.timestamp() },
247
+ ...options.history ? { history: [...options.history] } : {}
248
+ };
249
+ this.tasks.set(task.id, task);
250
+ return task;
251
+ }
252
+ get(id) {
253
+ return this.tasks.get(id);
254
+ }
255
+ list(filter = {}) {
256
+ const out = [];
257
+ for (const task of this.tasks.values()) {
258
+ if (filter.contextId !== void 0 && task.contextId !== filter.contextId) continue;
259
+ if (filter.state !== void 0 && task.status.state !== filter.state) continue;
260
+ out.push(task);
261
+ }
262
+ return out;
263
+ }
264
+ /**
265
+ * Transition a task to `state`, notify its subscribers, and — when the new
266
+ * state is terminal — clear those subscribers. Leaving a terminal state
267
+ * throws {@link IllegalTaskTransitionError}.
268
+ */
269
+ updateState(id, state, message) {
270
+ const task = this.tasks.get(id);
271
+ if (!task) throw new TaskNotFoundError(id);
272
+ if (isTerminal(task.status.state)) {
273
+ throw new IllegalTaskTransitionError(id, task.status.state, state);
274
+ }
275
+ task.status = { state, ...message ? { message } : {}, timestamp: this.timestamp() };
276
+ const final = isTerminal(state);
277
+ const event = {
278
+ kind: "status-update",
279
+ taskId: id,
280
+ contextId: task.contextId,
281
+ status: task.status,
282
+ final
283
+ };
284
+ const subs = this.subscribers.get(id);
285
+ if (subs) {
286
+ for (const fn of [...subs]) fn(event);
287
+ if (final) this.subscribers.delete(id);
288
+ }
289
+ if (final) this.evictBeyondRetention();
290
+ return task;
291
+ }
292
+ /** Drop the oldest TERMINAL tasks beyond `maxRetainedTasks` (no-op when unset). */
293
+ evictBeyondRetention() {
294
+ const cap = this.maxRetainedTasks;
295
+ if (cap === void 0) return;
296
+ const terminal = [];
297
+ for (const task of this.tasks.values()) {
298
+ if (isTerminal(task.status.state)) terminal.push(task.id);
299
+ }
300
+ for (let i = 0; i < terminal.length - cap; i++) {
301
+ const id = terminal[i];
302
+ this.tasks.delete(id);
303
+ this.subscribers.delete(id);
304
+ }
305
+ }
306
+ /** Cancel a task (illegal from a terminal state, like any other exit). */
307
+ cancel(id) {
308
+ return this.updateState(id, "canceled");
309
+ }
310
+ /** Subscribe to a task's status updates; returns an unsubscribe function. */
311
+ subscribe(taskId, fn) {
312
+ if (!this.tasks.has(taskId)) throw new TaskNotFoundError(taskId);
313
+ let subs = this.subscribers.get(taskId);
314
+ if (!subs) {
315
+ subs = /* @__PURE__ */ new Set();
316
+ this.subscribers.set(taskId, subs);
317
+ }
318
+ subs.add(fn);
319
+ return () => {
320
+ subs.delete(fn);
321
+ if (subs.size === 0) this.subscribers.delete(taskId);
322
+ };
323
+ }
324
+ /** Live subscriber count for a task (0 after a terminal transition). */
325
+ subscriberCount(taskId) {
326
+ return this.subscribers.get(taskId)?.size ?? 0;
327
+ }
328
+ timestamp() {
329
+ return new Date(this.now()).toISOString();
330
+ }
331
+ };
332
+
333
+ // src/executor.ts
334
+ import {
335
+ availableParallelism,
336
+ createLimiter,
337
+ finalTextOf,
338
+ lastAssistant,
339
+ uuidv7 as uuidv72
340
+ } from "@ap3x/agent-core";
341
+ function messageText(message) {
342
+ const chunks = [];
343
+ for (const part of message.parts) {
344
+ if (part.kind === "text") chunks.push(part.text);
345
+ else if (part.kind === "data") chunks.push(JSON.stringify(part.data));
346
+ else if (part.kind === "url") chunks.push(part.url);
347
+ }
348
+ return chunks.join("\n");
349
+ }
350
+ function agentMessageText(message) {
351
+ if (message.role === "user") {
352
+ if (typeof message.content === "string") return message.content;
353
+ return message.content.filter((block) => block.type === "text").map((block) => block.text).join("\n");
354
+ }
355
+ if (message.role === "assistant" || message.role === "toolResult") {
356
+ return message.content.filter((block) => block.type === "text").map((block) => block.text).join("\n");
357
+ }
358
+ return "";
359
+ }
360
+ var A2aExecutor = class {
361
+ backend;
362
+ store;
363
+ now;
364
+ limit;
365
+ /**
366
+ * contextId -> transcript so a follow-up message keeps its conversational
367
+ * thread. Access-ordered (delete + re-set on touch) so Map insertion order
368
+ * doubles as LRU order for {@link maxRetainedContexts} eviction.
369
+ */
370
+ transcripts = /* @__PURE__ */ new Map();
371
+ maxRetainedContexts;
372
+ aborts = /* @__PURE__ */ new Map();
373
+ constructor(backend, options) {
374
+ this.backend = backend;
375
+ this.now = options.now;
376
+ if (options.maxRetainedContexts !== void 0) {
377
+ this.maxRetainedContexts = Math.max(0, Math.floor(options.maxRetainedContexts));
378
+ }
379
+ this.store = new TaskStore({
380
+ now: options.now,
381
+ ...options.maxRetainedTasks !== void 0 ? { maxRetainedTasks: options.maxRetainedTasks } : {}
382
+ });
383
+ this.limit = createLimiter(options.maxConcurrentTasks ?? Math.max(4, availableParallelism()));
384
+ }
385
+ /** Whether the wrapped backend supports incremental streaming. */
386
+ get streaming() {
387
+ return typeof this.backend.runStream === "function";
388
+ }
389
+ /** Accept an inbound message: create a task and run the backend against it. */
390
+ async sendMessage(message, options = {}) {
391
+ const contextId = message.contextId ?? uuidv72();
392
+ const task = this.store.create({ contextId });
393
+ task.history = [{ ...message, taskId: task.id, contextId }];
394
+ if (options.onEvent) this.store.subscribe(task.id, options.onEvent);
395
+ options.onTask?.(structuredClone(task));
396
+ const controller = new AbortController();
397
+ this.aborts.set(task.id, controller);
398
+ const running = this.limit(
399
+ () => this.execute(task.id, contextId, message, controller.signal)
400
+ ).finally(() => this.aborts.delete(task.id));
401
+ if (options.blocking) await running;
402
+ return this.snapshot(task.id);
403
+ }
404
+ /** Deep-copied task lookup (undefined when unknown). */
405
+ getTask(id) {
406
+ return this.snapshot(id);
407
+ }
408
+ /** Deep-copied task listing. */
409
+ listTasks(filter = {}) {
410
+ return this.store.list(filter).map((task) => structuredClone(task));
411
+ }
412
+ /**
413
+ * Cancel a task: best-effort abort of the underlying run + store cancel.
414
+ * A backend without `abort` still cancels — the guarded terminal write in
415
+ * {@link execute} discards the late result. Throws TaskNotFoundError /
416
+ * IllegalTaskTransitionError for unknown / already-terminal tasks.
417
+ */
418
+ cancel(taskId) {
419
+ this.store.cancel(taskId);
420
+ this.aborts.get(taskId)?.abort();
421
+ this.backend.abort?.();
422
+ return this.snapshot(taskId);
423
+ }
424
+ /** Subscribe to a task's status updates; returns an unsubscribe function. */
425
+ subscribe(taskId, fn) {
426
+ return this.store.subscribe(taskId, fn);
427
+ }
428
+ /** LRU read: touching a context moves it to the back of the eviction order. */
429
+ recallTranscript(contextId) {
430
+ const transcript = this.transcripts.get(contextId);
431
+ if (transcript !== void 0) {
432
+ this.transcripts.delete(contextId);
433
+ this.transcripts.set(contextId, transcript);
434
+ }
435
+ return transcript;
436
+ }
437
+ /** LRU write: (re-)insert at the back, then evict the oldest beyond the cap. */
438
+ retainTranscript(contextId, transcript) {
439
+ this.transcripts.delete(contextId);
440
+ this.transcripts.set(contextId, transcript);
441
+ const cap = this.maxRetainedContexts;
442
+ if (cap === void 0) return;
443
+ while (this.transcripts.size > cap) {
444
+ const oldest = this.transcripts.keys().next().value;
445
+ if (oldest === void 0) break;
446
+ this.transcripts.delete(oldest);
447
+ }
448
+ }
449
+ snapshot(id) {
450
+ const task = this.store.get(id);
451
+ return task ? structuredClone(task) : void 0;
452
+ }
453
+ agentMessage(taskId, contextId, text) {
454
+ const parts = [{ kind: "text", text }];
455
+ return { messageId: uuidv72(), taskId, contextId, role: "agent", parts };
456
+ }
457
+ /**
458
+ * Terminal write, guarded: a task canceled mid-run stays canceled — the
459
+ * store's terminal-state rejection is caught and the late result discarded.
460
+ */
461
+ finish(taskId, contextId, state, text) {
462
+ try {
463
+ this.store.updateState(
464
+ taskId,
465
+ state,
466
+ text === void 0 ? void 0 : this.agentMessage(taskId, contextId, text)
467
+ );
468
+ } catch (error) {
469
+ if (!(error instanceof IllegalTaskTransitionError)) throw error;
470
+ }
471
+ }
472
+ async execute(taskId, contextId, message, signal) {
473
+ const current = this.store.get(taskId);
474
+ if (!current || isTerminal(current.status.state)) return;
475
+ try {
476
+ this.store.updateState(taskId, "working");
477
+ } catch (error) {
478
+ if (error instanceof IllegalTaskTransitionError) return;
479
+ throw error;
480
+ }
481
+ const prior = this.recallTranscript(contextId) ?? [];
482
+ const userMessage = {
483
+ role: "user",
484
+ content: messageText(message),
485
+ timestamp: this.now()
486
+ };
487
+ const input = [...prior, userMessage];
488
+ let messages;
489
+ let finalText;
490
+ let stopReason;
491
+ let errorMessage;
492
+ try {
493
+ if (this.backend.runStream) {
494
+ const stream = this.backend.runStream(input, { signal });
495
+ for await (const event of stream) {
496
+ if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
497
+ try {
498
+ this.store.updateState(
499
+ taskId,
500
+ "working",
501
+ this.agentMessage(taskId, contextId, event.assistantMessageEvent.delta)
502
+ );
503
+ } catch (error) {
504
+ if (!(error instanceof IllegalTaskTransitionError)) throw error;
505
+ }
506
+ }
507
+ }
508
+ messages = await stream.result();
509
+ const assistant = lastAssistant(messages);
510
+ finalText = finalTextOf(assistant);
511
+ stopReason = assistant?.stopReason ?? "error";
512
+ errorMessage = assistant?.errorMessage;
513
+ } else {
514
+ const result = await this.backend.run(input, { signal });
515
+ ({ messages, finalText, stopReason, errorMessage } = result);
516
+ }
517
+ } catch (error) {
518
+ this.finish(
519
+ taskId,
520
+ contextId,
521
+ "failed",
522
+ error instanceof Error ? error.message : String(error)
523
+ );
524
+ return;
525
+ }
526
+ if (stopReason === "aborted") {
527
+ this.finish(taskId, contextId, "canceled");
528
+ return;
529
+ }
530
+ if (stopReason === "error") {
531
+ this.finish(taskId, contextId, "failed", errorMessage ?? "agent run failed");
532
+ return;
533
+ }
534
+ this.retainTranscript(contextId, [...input, ...messages]);
535
+ const task = this.store.get(taskId);
536
+ if (task && !isTerminal(task.status.state)) {
537
+ const mapped = messages.map((m) => ({
538
+ messageId: uuidv72(),
539
+ taskId,
540
+ contextId,
541
+ role: m.role === "user" ? "user" : "agent",
542
+ parts: [{ kind: "text", text: agentMessageText(m) }]
543
+ }));
544
+ task.history = [...task.history ?? [], ...mapped];
545
+ }
546
+ this.finish(taskId, contextId, "completed", finalText);
547
+ }
548
+ };
549
+
550
+ // src/bindings/jsonrpc.ts
551
+ import { Type as Type2 } from "@sinclair/typebox";
552
+ import { Value } from "@sinclair/typebox/value";
553
+ var A2A_RPC_METHODS = {
554
+ sendMessage: "message/send",
555
+ sendStreamingMessage: "message/stream",
556
+ getTask: "tasks/get",
557
+ listTasks: "tasks/list",
558
+ cancelTask: "tasks/cancel",
559
+ subscribeToTask: "tasks/resubscribe"
560
+ };
561
+ var A2A_RPC_METHOD_ALIASES = {
562
+ ...Object.fromEntries(
563
+ Object.entries(A2A_RPC_METHODS).map(([operation, method]) => [method, operation])
564
+ ),
565
+ SendMessage: "sendMessage",
566
+ SendStreamingMessage: "sendStreamingMessage",
567
+ GetTask: "getTask",
568
+ ListTasks: "listTasks",
569
+ CancelTask: "cancelTask",
570
+ SubscribeToTask: "subscribeToTask"
571
+ };
572
+ var JsonRpcRequestSchema = Type2.Object({
573
+ jsonrpc: Type2.Literal("2.0"),
574
+ id: Type2.Union([Type2.String(), Type2.Number(), Type2.Null()]),
575
+ method: Type2.String(),
576
+ params: Type2.Optional(Type2.Unknown())
577
+ });
578
+ var MessageSendParamsSchema = Type2.Object({
579
+ message: MessageSchema,
580
+ configuration: Type2.Optional(
581
+ Type2.Object({
582
+ /**
583
+ * When true, `SendMessage` returns the Task snapshot immediately instead
584
+ * of waiting for the terminal state. Default false — blocking is the
585
+ * spec default.
586
+ */
587
+ returnImmediately: Type2.Optional(Type2.Boolean()),
588
+ /**
589
+ * The reference SDK's (0.3.x) knob for the same choice, inverted:
590
+ * `blocking: true` waits for the terminal state. Accepted for interop;
591
+ * AP3X clients emit `returnImmediately`.
592
+ */
593
+ blocking: Type2.Optional(Type2.Boolean())
594
+ })
595
+ )
596
+ });
597
+ function resolveBlocking(configuration) {
598
+ if (configuration?.returnImmediately !== void 0) return !configuration.returnImmediately;
599
+ return configuration?.blocking ?? true;
600
+ }
601
+ var TaskIdParamsSchema = Type2.Object({ id: Type2.String() });
602
+ var TaskListParamsSchema = Type2.Object({
603
+ contextId: Type2.Optional(Type2.String()),
604
+ state: Type2.Optional(TaskStateSchema)
605
+ });
606
+ function respond(res, id, body) {
607
+ res.writeHead(200, { "content-type": "application/json" });
608
+ res.end(JSON.stringify({ jsonrpc: "2.0", id, ...body }));
609
+ }
610
+ function rpcError(code, message) {
611
+ return { error: { code, message } };
612
+ }
613
+ function mapThrown(error) {
614
+ if (error instanceof TaskNotFoundError) {
615
+ return rpcError(A2A_ERROR_CODES.taskNotFound, error.message);
616
+ }
617
+ if (error instanceof IllegalTaskTransitionError) {
618
+ return rpcError(A2A_ERROR_CODES.taskNotCancelable, error.message);
619
+ }
620
+ return rpcError(
621
+ A2A_ERROR_CODES.internalError,
622
+ error instanceof Error ? error.message : String(error)
623
+ );
624
+ }
625
+ function startSse(res, sseStreams) {
626
+ res.writeHead(200, {
627
+ "content-type": "text/event-stream",
628
+ "cache-control": "no-cache",
629
+ connection: "keep-alive"
630
+ });
631
+ sseStreams.add(res);
632
+ res.on("close", () => sseStreams.delete(res));
633
+ }
634
+ function sseWrite(res, id, result) {
635
+ res.write(`data: ${JSON.stringify({ jsonrpc: "2.0", id, result })}
636
+
637
+ `);
638
+ }
639
+ async function handleJsonRpc(executor, rawBody, res, sseStreams) {
640
+ let parsed;
641
+ try {
642
+ parsed = JSON.parse(rawBody);
643
+ } catch {
644
+ respond(res, null, rpcError(A2A_ERROR_CODES.parseError, "malformed JSON"));
645
+ return;
646
+ }
647
+ if (!Value.Check(JsonRpcRequestSchema, parsed)) {
648
+ respond(res, null, rpcError(A2A_ERROR_CODES.invalidRequest, "not a JSON-RPC 2.0 request"));
649
+ return;
650
+ }
651
+ const { id, method, params } = parsed;
652
+ try {
653
+ switch (A2A_RPC_METHOD_ALIASES[method]) {
654
+ case "sendMessage": {
655
+ if (!Value.Check(MessageSendParamsSchema, params)) {
656
+ respond(res, id, rpcError(A2A_ERROR_CODES.invalidParams, "invalid SendMessage params"));
657
+ return;
658
+ }
659
+ const task = await executor.sendMessage(params.message, {
660
+ blocking: resolveBlocking(params.configuration)
661
+ });
662
+ respond(res, id, { result: task });
663
+ return;
664
+ }
665
+ case "sendStreamingMessage": {
666
+ if (!Value.Check(MessageSendParamsSchema, params)) {
667
+ respond(
668
+ res,
669
+ id,
670
+ rpcError(A2A_ERROR_CODES.invalidParams, "invalid SendStreamingMessage params")
671
+ );
672
+ return;
673
+ }
674
+ startSse(res, sseStreams);
675
+ await executor.sendMessage(params.message, {
676
+ blocking: false,
677
+ // Spec: the stream MUST begin with the full Task object.
678
+ onTask: (task) => sseWrite(res, id, task),
679
+ onEvent: (event) => {
680
+ sseWrite(res, id, event);
681
+ if (event.final) res.end();
682
+ }
683
+ });
684
+ return;
685
+ }
686
+ case "getTask": {
687
+ if (!Value.Check(TaskIdParamsSchema, params)) {
688
+ respond(res, id, rpcError(A2A_ERROR_CODES.invalidParams, "invalid GetTask params"));
689
+ return;
690
+ }
691
+ const task = executor.getTask(params.id);
692
+ if (!task) {
693
+ respond(
694
+ res,
695
+ id,
696
+ rpcError(A2A_ERROR_CODES.taskNotFound, `A2A task not found: ${params.id}`)
697
+ );
698
+ return;
699
+ }
700
+ respond(res, id, { result: task });
701
+ return;
702
+ }
703
+ case "listTasks": {
704
+ const filter = normalizeWireTaskStates(params ?? {});
705
+ if (!Value.Check(TaskListParamsSchema, filter)) {
706
+ respond(res, id, rpcError(A2A_ERROR_CODES.invalidParams, "invalid ListTasks params"));
707
+ return;
708
+ }
709
+ respond(res, id, { result: { tasks: executor.listTasks(filter) } });
710
+ return;
711
+ }
712
+ case "cancelTask": {
713
+ if (!Value.Check(TaskIdParamsSchema, params)) {
714
+ respond(res, id, rpcError(A2A_ERROR_CODES.invalidParams, "invalid CancelTask params"));
715
+ return;
716
+ }
717
+ respond(res, id, { result: executor.cancel(params.id) });
718
+ return;
719
+ }
720
+ case "subscribeToTask": {
721
+ if (!Value.Check(TaskIdParamsSchema, params)) {
722
+ respond(
723
+ res,
724
+ id,
725
+ rpcError(A2A_ERROR_CODES.invalidParams, "invalid SubscribeToTask params")
726
+ );
727
+ return;
728
+ }
729
+ const task = executor.getTask(params.id);
730
+ if (!task) {
731
+ respond(
732
+ res,
733
+ id,
734
+ rpcError(A2A_ERROR_CODES.taskNotFound, `A2A task not found: ${params.id}`)
735
+ );
736
+ return;
737
+ }
738
+ startSse(res, sseStreams);
739
+ sseWrite(res, id, task);
740
+ if (isTerminal(task.status.state)) {
741
+ const event = {
742
+ kind: "status-update",
743
+ taskId: task.id,
744
+ contextId: task.contextId,
745
+ status: task.status,
746
+ final: true
747
+ };
748
+ sseWrite(res, id, event);
749
+ res.end();
750
+ return;
751
+ }
752
+ const unsubscribe = executor.subscribe(task.id, (event) => {
753
+ sseWrite(res, id, event);
754
+ if (event.final) res.end();
755
+ });
756
+ res.on("close", unsubscribe);
757
+ return;
758
+ }
759
+ default:
760
+ respond(res, id, rpcError(A2A_ERROR_CODES.methodNotFound, `unknown method: ${method}`));
761
+ return;
762
+ }
763
+ } catch (error) {
764
+ if (res.headersSent) {
765
+ res.end();
766
+ return;
767
+ }
768
+ respond(res, id, mapThrown(error));
769
+ }
770
+ }
771
+
772
+ // src/bindings/rest.ts
773
+ import { Type as Type3 } from "@sinclair/typebox";
774
+ import { Value as Value2 } from "@sinclair/typebox/value";
775
+ var A2A_REST_ROUTES = {
776
+ sendMessage: { httpMethod: "POST", path: "/v1/message:send" },
777
+ sendStreamingMessage: { httpMethod: "POST", path: "/v1/message:stream" },
778
+ getTask: { httpMethod: "GET", path: "/v1/tasks/{id}" },
779
+ listTasks: { httpMethod: "GET", path: "/v1/tasks" },
780
+ cancelTask: { httpMethod: "POST", path: "/v1/tasks/{id}:cancel" },
781
+ subscribeToTask: { httpMethod: "POST", path: "/v1/tasks/{id}:subscribe" }
782
+ };
783
+ var RestErrorBodySchema = Type3.Object({
784
+ error: Type3.Object({ code: Type3.Number(), message: Type3.String() })
785
+ });
786
+ function restStatusFor(code) {
787
+ switch (code) {
788
+ case A2A_ERROR_CODES.taskNotFound:
789
+ case A2A_ERROR_CODES.methodNotFound:
790
+ return 404;
791
+ case A2A_ERROR_CODES.parseError:
792
+ case A2A_ERROR_CODES.invalidRequest:
793
+ case A2A_ERROR_CODES.invalidParams:
794
+ return 400;
795
+ case A2A_ERROR_CODES.taskNotCancelable:
796
+ return 409;
797
+ default:
798
+ return 500;
799
+ }
800
+ }
801
+ function sendJson(res, status, body) {
802
+ res.writeHead(status, { "content-type": "application/json" });
803
+ res.end(JSON.stringify(body));
804
+ }
805
+ function restError(res, code, message) {
806
+ sendJson(res, restStatusFor(code), { error: { code, message } });
807
+ }
808
+ function mapThrown2(res, error) {
809
+ if (error instanceof TaskNotFoundError) {
810
+ restError(res, A2A_ERROR_CODES.taskNotFound, error.message);
811
+ return;
812
+ }
813
+ if (error instanceof IllegalTaskTransitionError) {
814
+ restError(res, A2A_ERROR_CODES.taskNotCancelable, error.message);
815
+ return;
816
+ }
817
+ restError(
818
+ res,
819
+ A2A_ERROR_CODES.internalError,
820
+ error instanceof Error ? error.message : String(error)
821
+ );
822
+ }
823
+ function sseWrite2(res, frame) {
824
+ res.write(`data: ${JSON.stringify(frame)}
825
+
826
+ `);
827
+ }
828
+ function matchRoute(httpMethod, path) {
829
+ for (const op of Object.keys(A2A_REST_ROUTES)) {
830
+ const route = A2A_REST_ROUTES[op];
831
+ if (route.httpMethod !== httpMethod) continue;
832
+ const idAt = route.path.indexOf("{id}");
833
+ if (idAt === -1) {
834
+ if (path === route.path) return { op };
835
+ continue;
836
+ }
837
+ const prefix = route.path.slice(0, idAt);
838
+ const suffix = route.path.slice(idAt + "{id}".length);
839
+ if (!path.startsWith(prefix) || !path.endsWith(suffix)) continue;
840
+ const raw = path.slice(prefix.length, path.length - suffix.length);
841
+ if (raw.length === 0 || raw.includes("/") || raw.includes(":")) continue;
842
+ return { op, id: decodeURIComponent(raw) };
843
+ }
844
+ return void 0;
845
+ }
846
+ async function handleRest(executor, httpMethod, path, query, rawBody, res, sseStreams) {
847
+ const match = matchRoute(httpMethod, path);
848
+ if (!match) {
849
+ restError(res, A2A_ERROR_CODES.methodNotFound, `no A2A route: ${httpMethod} ${path}`);
850
+ return;
851
+ }
852
+ try {
853
+ switch (match.op) {
854
+ case "sendMessage":
855
+ case "sendStreamingMessage": {
856
+ let parsed;
857
+ try {
858
+ parsed = JSON.parse(rawBody);
859
+ } catch {
860
+ restError(res, A2A_ERROR_CODES.parseError, "malformed JSON");
861
+ return;
862
+ }
863
+ if (!Value2.Check(MessageSendParamsSchema, parsed)) {
864
+ restError(res, A2A_ERROR_CODES.invalidParams, `invalid ${match.op} body`);
865
+ return;
866
+ }
867
+ if (match.op === "sendMessage") {
868
+ const task = await executor.sendMessage(parsed.message, {
869
+ blocking: resolveBlocking(parsed.configuration)
870
+ });
871
+ sendJson(res, 200, task);
872
+ return;
873
+ }
874
+ startSse(res, sseStreams);
875
+ await executor.sendMessage(parsed.message, {
876
+ blocking: false,
877
+ // Spec: the stream MUST begin with the full Task object.
878
+ onTask: (task) => sseWrite2(res, task),
879
+ onEvent: (event) => {
880
+ sseWrite2(res, event);
881
+ if (event.final) res.end();
882
+ }
883
+ });
884
+ return;
885
+ }
886
+ case "getTask": {
887
+ const task = executor.getTask(match.id);
888
+ if (!task) {
889
+ restError(res, A2A_ERROR_CODES.taskNotFound, `A2A task not found: ${match.id}`);
890
+ return;
891
+ }
892
+ sendJson(res, 200, task);
893
+ return;
894
+ }
895
+ case "listTasks": {
896
+ const filter = {};
897
+ const contextId = query.get("contextId");
898
+ if (contextId !== null) filter.contextId = contextId;
899
+ const state = query.get("state");
900
+ if (state !== null) filter.state = normalizeTaskState(state);
901
+ if (!Value2.Check(TaskListParamsSchema, filter)) {
902
+ restError(res, A2A_ERROR_CODES.invalidParams, "invalid ListTasks query");
903
+ return;
904
+ }
905
+ sendJson(res, 200, { tasks: executor.listTasks(filter) });
906
+ return;
907
+ }
908
+ case "cancelTask": {
909
+ sendJson(res, 200, executor.cancel(match.id));
910
+ return;
911
+ }
912
+ case "subscribeToTask": {
913
+ const task = executor.getTask(match.id);
914
+ if (!task) {
915
+ restError(res, A2A_ERROR_CODES.taskNotFound, `A2A task not found: ${match.id}`);
916
+ return;
917
+ }
918
+ startSse(res, sseStreams);
919
+ sseWrite2(res, task);
920
+ if (isTerminal(task.status.state)) {
921
+ const event = {
922
+ kind: "status-update",
923
+ taskId: task.id,
924
+ contextId: task.contextId,
925
+ status: task.status,
926
+ final: true
927
+ };
928
+ sseWrite2(res, event);
929
+ res.end();
930
+ return;
931
+ }
932
+ const unsubscribe = executor.subscribe(task.id, (event) => {
933
+ sseWrite2(res, event);
934
+ if (event.final) res.end();
935
+ });
936
+ res.on("close", unsubscribe);
937
+ return;
938
+ }
939
+ }
940
+ } catch (error) {
941
+ if (res.headersSent) {
942
+ res.end();
943
+ return;
944
+ }
945
+ mapThrown2(res, error);
946
+ }
947
+ }
948
+
949
+ // src/client.ts
950
+ import { Type as Type4 } from "@sinclair/typebox";
951
+ import { Value as Value3 } from "@sinclair/typebox/value";
952
+ var AGENT_CARD_PATH = "/.well-known/agent-card.json";
953
+ var A2A_VERSION_HEADER = "A2A-Version";
954
+ var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
955
+ var A2aClientError = class extends Error {
956
+ /** JSON-RPC error code when the server answered with an error object. */
957
+ code;
958
+ constructor(message, code) {
959
+ super(message);
960
+ this.name = "A2aClientError";
961
+ if (code !== void 0) this.code = code;
962
+ }
963
+ };
964
+ var JsonRpcResponseSchema = Type4.Object({
965
+ jsonrpc: Type4.Literal("2.0"),
966
+ id: Type4.Union([Type4.String(), Type4.Number(), Type4.Null()]),
967
+ result: Type4.Optional(Type4.Unknown()),
968
+ error: Type4.Optional(Type4.Object({ code: Type4.Number(), message: Type4.String() }))
969
+ });
970
+ var TaskListResultSchema = Type4.Object({ tasks: Type4.Array(TaskSchema) });
971
+ var SendMessageResultSchema = Type4.Union([TaskSchema, MessageSchema]);
972
+ var StreamFrameSchema = Type4.Union([
973
+ TaskStatusUpdateEventSchema,
974
+ TaskArtifactUpdateEventSchema,
975
+ TaskSchema,
976
+ MessageSchema
977
+ ]);
978
+ function isTaskFrame(frame) {
979
+ if (!("kind" in frame)) return !("messageId" in frame);
980
+ return frame.kind === "task";
981
+ }
982
+ function isMessageFrame(frame) {
983
+ return "messageId" in frame;
984
+ }
985
+ function isStatusUpdateFrame(frame) {
986
+ return "kind" in frame && frame.kind === "status-update";
987
+ }
988
+ function describe(error) {
989
+ return error instanceof Error ? error.message : String(error);
990
+ }
991
+ var KNOWN_TRANSPORTS = /* @__PURE__ */ new Set(["JSONRPC", "HTTP+JSON", "GRPC"]);
992
+ var SecuritySchemesSchema = Type4.Record(Type4.String(), BearerSecuritySchemeSchema);
993
+ function normalizeRemoteCard(card) {
994
+ const declared = [
995
+ ...card.url !== void 0 ? [{ transport: card.preferredTransport ?? "JSONRPC", url: card.url }] : [],
996
+ ...card.interfaces ?? [],
997
+ ...card.additionalInterfaces ?? []
998
+ ];
999
+ const seen = /* @__PURE__ */ new Set();
1000
+ const interfaces = [];
1001
+ for (const iface of declared) {
1002
+ const key = `${iface.transport} ${iface.url}`;
1003
+ if (!KNOWN_TRANSPORTS.has(iface.transport) || seen.has(key)) continue;
1004
+ seen.add(key);
1005
+ interfaces.push(iface);
1006
+ }
1007
+ return {
1008
+ ...card.id !== void 0 ? { id: card.id } : {},
1009
+ name: card.name,
1010
+ description: card.description ?? "",
1011
+ capabilities: {
1012
+ streaming: card.capabilities?.streaming ?? false,
1013
+ pushNotifications: card.capabilities?.pushNotifications ?? false
1014
+ },
1015
+ skills: (card.skills ?? []).filter(
1016
+ (skill) => Value3.Check(AgentSkillSchema, skill)
1017
+ ),
1018
+ interfaces,
1019
+ ...Value3.Check(SecuritySchemesSchema, card.securitySchemes) ? { securitySchemes: card.securitySchemes } : {},
1020
+ ...Value3.Check(AgentProviderSchema, card.provider) ? { provider: card.provider } : {},
1021
+ ...card.url !== void 0 ? { url: card.url } : {},
1022
+ ...card.preferredTransport !== void 0 ? { preferredTransport: card.preferredTransport } : {}
1023
+ };
1024
+ }
1025
+ var A2aClient = class _A2aClient {
1026
+ /** The Agent Card fetched (once) at connect time. */
1027
+ card;
1028
+ /** The transport negotiated from the card (JSONRPC preferred). */
1029
+ transport;
1030
+ endpoint;
1031
+ fetchImpl;
1032
+ timeoutMs;
1033
+ bearerToken;
1034
+ nextId = 1;
1035
+ /** Present exactly when `transport` is GRPC (lazy-loaded at connect time). */
1036
+ grpcTransport;
1037
+ constructor(card, endpoint, transport, options) {
1038
+ this.card = card;
1039
+ this.endpoint = endpoint;
1040
+ this.transport = transport;
1041
+ this.fetchImpl = options.fetch ?? globalThis.fetch;
1042
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1043
+ if (options.bearerToken !== void 0) this.bearerToken = options.bearerToken;
1044
+ }
1045
+ /**
1046
+ * Fetch + validate the Agent Card from the well-known path, negotiate the
1047
+ * transport (JSONRPC preferred, then HTTP+JSON, then GRPC as a last resort),
1048
+ * and return a connected client.
1049
+ */
1050
+ static async connect(baseUrl, options = {}) {
1051
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1052
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1053
+ const url = new URL(AGENT_CARD_PATH, baseUrl).toString();
1054
+ let res;
1055
+ try {
1056
+ res = await fetchImpl(url, {
1057
+ headers: { [A2A_VERSION_HEADER]: A2A_PROTOCOL_VERSION },
1058
+ signal: AbortSignal.timeout(timeoutMs)
1059
+ });
1060
+ } catch (error) {
1061
+ throw new A2aClientError(`A2A agent card fetch failed: ${describe(error)}`);
1062
+ }
1063
+ if (!res.ok) throw new A2aClientError(`A2A agent card fetch failed: HTTP ${res.status}`);
1064
+ let raw;
1065
+ try {
1066
+ raw = await res.json();
1067
+ } catch {
1068
+ throw new A2aClientError("A2A agent card is not valid JSON");
1069
+ }
1070
+ if (!Value3.Check(RemoteAgentCardSchema, raw)) {
1071
+ throw new A2aClientError("A2A agent card failed validation");
1072
+ }
1073
+ const card = normalizeRemoteCard(raw);
1074
+ const iface = card.interfaces.find((candidate) => candidate.transport === "JSONRPC") ?? card.interfaces.find((candidate) => candidate.transport === "HTTP+JSON") ?? card.interfaces.find((candidate) => candidate.transport === "GRPC");
1075
+ if (!iface) {
1076
+ throw new A2aClientError(
1077
+ "A2A agent card advertises no JSONRPC, HTTP+JSON, or GRPC interface (this client speaks all three)"
1078
+ );
1079
+ }
1080
+ const client = new _A2aClient(card, iface.url, iface.transport, options);
1081
+ if (iface.transport === "GRPC") {
1082
+ const { GrpcClientTransport } = await import("./grpc-5DAU6EYQ.js");
1083
+ client.grpcTransport = new GrpcClientTransport(iface.url, {
1084
+ timeoutMs,
1085
+ ...options.bearerToken !== void 0 ? { bearerToken: options.bearerToken } : {}
1086
+ });
1087
+ }
1088
+ return client;
1089
+ }
1090
+ /** Release transport resources (the gRPC channel; HTTP needs nothing). */
1091
+ close() {
1092
+ this.grpcTransport?.close();
1093
+ }
1094
+ /**
1095
+ * `SendMessage` — blocking by default; returns the (validated) Task
1096
+ * snapshot, or a bare Message when the peer's executor answered directly
1097
+ * without creating a task (a Task has `status`, a Message does not).
1098
+ *
1099
+ * `signal` cancels the in-flight HTTP request (combined with the per-request
1100
+ * timeout). The gRPC transport relies on its own deadline — best-effort.
1101
+ */
1102
+ async sendMessage(message, config, signal) {
1103
+ if (this.grpcTransport) {
1104
+ return this.expect(
1105
+ SendMessageResultSchema,
1106
+ await this.grpcTransport.sendMessage(message, config),
1107
+ "SendMessage"
1108
+ );
1109
+ }
1110
+ const params = { message, ...config !== void 0 ? { configuration: config } : {} };
1111
+ if (this.transport === "JSONRPC") {
1112
+ const method = A2A_RPC_METHODS.sendMessage;
1113
+ return this.expect(SendMessageResultSchema, await this.rpc(method, params, signal), method);
1114
+ }
1115
+ const op = "sendMessage";
1116
+ return this.expect(
1117
+ SendMessageResultSchema,
1118
+ await this.restJson(op, { body: params, ...signal ? { signal } : {} }),
1119
+ op
1120
+ );
1121
+ }
1122
+ /** `SendStreamingMessage` — SSE: the Task object first, then update events. */
1123
+ sendStreamingMessage(message, signal) {
1124
+ if (this.grpcTransport) return this.grpcTransport.sendStreamingMessage(message);
1125
+ if (this.transport === "JSONRPC") {
1126
+ return this.rpcSse(A2A_RPC_METHODS.sendStreamingMessage, { message }, signal);
1127
+ }
1128
+ return this.restSse("sendStreamingMessage", {
1129
+ body: { message },
1130
+ ...signal ? { signal } : {}
1131
+ });
1132
+ }
1133
+ /** `GetTask`. */
1134
+ async getTask(id, signal) {
1135
+ if (this.grpcTransport) {
1136
+ return this.expect(TaskSchema, await this.grpcTransport.getTask(id), "GetTask");
1137
+ }
1138
+ if (this.transport === "JSONRPC") {
1139
+ const method = A2A_RPC_METHODS.getTask;
1140
+ return this.expect(TaskSchema, await this.rpc(method, { id }, signal), method);
1141
+ }
1142
+ const op = "getTask";
1143
+ return this.expect(
1144
+ TaskSchema,
1145
+ await this.restJson(op, { id, ...signal ? { signal } : {} }),
1146
+ op
1147
+ );
1148
+ }
1149
+ /** `ListTasks`. */
1150
+ async listTasks(filter = {}) {
1151
+ if (this.grpcTransport) {
1152
+ const tasks = await this.grpcTransport.listTasks(filter);
1153
+ return this.expect(TaskListResultSchema, { tasks }, "ListTasks").tasks;
1154
+ }
1155
+ if (this.transport === "JSONRPC") {
1156
+ const method = A2A_RPC_METHODS.listTasks;
1157
+ return this.expect(TaskListResultSchema, await this.rpc(method, filter), method).tasks;
1158
+ }
1159
+ const query = {
1160
+ ...filter.contextId !== void 0 ? { contextId: filter.contextId } : {},
1161
+ ...filter.state !== void 0 ? { state: filter.state } : {}
1162
+ };
1163
+ const op = "listTasks";
1164
+ return this.expect(TaskListResultSchema, await this.restJson(op, { query }), op).tasks;
1165
+ }
1166
+ /** `CancelTask`. */
1167
+ async cancelTask(id) {
1168
+ if (this.grpcTransport) {
1169
+ return this.expect(TaskSchema, await this.grpcTransport.cancelTask(id), "CancelTask");
1170
+ }
1171
+ if (this.transport === "JSONRPC") {
1172
+ const method = A2A_RPC_METHODS.cancelTask;
1173
+ return this.expect(TaskSchema, await this.rpc(method, { id }), method);
1174
+ }
1175
+ const op = "cancelTask";
1176
+ return this.expect(TaskSchema, await this.restJson(op, { id }), op);
1177
+ }
1178
+ /** `SubscribeToTask` — SSE: the Task snapshot first, then update events. */
1179
+ subscribeToTask(id) {
1180
+ if (this.grpcTransport) return this.grpcTransport.subscribeToTask(id);
1181
+ if (this.transport === "JSONRPC") {
1182
+ return this.rpcSse(A2A_RPC_METHODS.subscribeToTask, { id });
1183
+ }
1184
+ return this.restSse("subscribeToTask", { id });
1185
+ }
1186
+ headers() {
1187
+ return {
1188
+ "content-type": "application/json",
1189
+ [A2A_VERSION_HEADER]: A2A_PROTOCOL_VERSION,
1190
+ ...this.bearerToken !== void 0 ? { authorization: `Bearer ${this.bearerToken}` } : {}
1191
+ };
1192
+ }
1193
+ /** Per-request timeout signal, optionally combined with a caller's signal. */
1194
+ requestSignal(signal) {
1195
+ const timeout = AbortSignal.timeout(this.timeoutMs);
1196
+ return signal ? AbortSignal.any([timeout, signal]) : timeout;
1197
+ }
1198
+ async post(method, params, signal) {
1199
+ try {
1200
+ return await this.fetchImpl(this.endpoint, {
1201
+ method: "POST",
1202
+ headers: this.headers(),
1203
+ body: JSON.stringify({ jsonrpc: "2.0", id: this.nextId++, method, params }),
1204
+ signal: this.requestSignal(signal)
1205
+ });
1206
+ } catch (error) {
1207
+ throw new A2aClientError(`A2A ${method} request failed: ${describe(error)}`);
1208
+ }
1209
+ }
1210
+ async rpc(method, params, signal) {
1211
+ const res = await this.post(method, params, signal);
1212
+ if (!res.ok) throw new A2aClientError(`A2A ${method} failed: HTTP ${res.status}`);
1213
+ let body;
1214
+ try {
1215
+ body = await res.json();
1216
+ } catch {
1217
+ throw new A2aClientError(`A2A ${method} response is not valid JSON`);
1218
+ }
1219
+ return this.unwrap(body, method);
1220
+ }
1221
+ /** Validate a JSON-RPC response envelope; throw the server's error typed. */
1222
+ unwrap(body, method) {
1223
+ if (!Value3.Check(JsonRpcResponseSchema, body)) {
1224
+ throw new A2aClientError(`A2A ${method} returned an invalid JSON-RPC envelope`);
1225
+ }
1226
+ if (body.error !== void 0) {
1227
+ throw new A2aClientError(`A2A ${method} error: ${body.error.message}`, body.error.code);
1228
+ }
1229
+ return body.result;
1230
+ }
1231
+ expect(schema, value, method) {
1232
+ const normalized = normalizeWireTaskStates(value);
1233
+ if (!Value3.Check(schema, normalized)) {
1234
+ throw new A2aClientError(`A2A ${method} returned a malformed result`);
1235
+ }
1236
+ return normalized;
1237
+ }
1238
+ async *rpcSse(method, params, signal) {
1239
+ const res = await this.post(method, params, signal);
1240
+ if (!res.ok) throw new A2aClientError(`A2A ${method} failed: HTTP ${res.status}`);
1241
+ const contentType = res.headers.get("content-type") ?? "";
1242
+ if (!contentType.includes("text/event-stream")) {
1243
+ let body;
1244
+ try {
1245
+ body = await res.json();
1246
+ } catch {
1247
+ throw new A2aClientError(`A2A ${method} answered neither SSE nor valid JSON`);
1248
+ }
1249
+ this.unwrap(body, method);
1250
+ throw new A2aClientError(`A2A ${method} answered JSON instead of an SSE stream`);
1251
+ }
1252
+ for await (const data of this.sseData(res, method)) {
1253
+ yield this.parseSseFrame(data, method);
1254
+ }
1255
+ }
1256
+ parseSseFrame(data, method) {
1257
+ let body;
1258
+ try {
1259
+ body = JSON.parse(data);
1260
+ } catch {
1261
+ throw new A2aClientError(`A2A ${method} SSE frame is not valid JSON`);
1262
+ }
1263
+ const result = this.unwrap(body, method);
1264
+ return this.expect(StreamFrameSchema, result, method);
1265
+ }
1266
+ /** The raw `data:` payloads of an SSE response — shared by both transports. */
1267
+ async *sseData(res, label) {
1268
+ if (!res.body) throw new A2aClientError(`A2A ${label} SSE response has no body`);
1269
+ const decoder = new TextDecoder();
1270
+ let buffer = "";
1271
+ for await (const chunk of res.body) {
1272
+ buffer += decoder.decode(chunk, { stream: true });
1273
+ let frameEnd = buffer.indexOf("\n\n");
1274
+ while (frameEnd !== -1) {
1275
+ const frame = buffer.slice(0, frameEnd);
1276
+ buffer = buffer.slice(frameEnd + 2);
1277
+ const data = frame.split("\n").filter((line) => line.startsWith("data: ")).map((line) => line.slice("data: ".length)).join("\n");
1278
+ if (data.length > 0) yield data;
1279
+ frameEnd = buffer.indexOf("\n\n");
1280
+ }
1281
+ }
1282
+ }
1283
+ // -------------------------------------------------------------------------
1284
+ // HTTP+JSON (REST) transport — the same six operations over A2A_REST_ROUTES
1285
+ // -------------------------------------------------------------------------
1286
+ async restRequest(op, args) {
1287
+ const route = A2A_REST_ROUTES[op];
1288
+ const path = args.id !== void 0 ? route.path.replace("{id}", encodeURIComponent(args.id)) : route.path;
1289
+ const target = new URL(path, this.endpoint);
1290
+ if (args.query !== void 0) {
1291
+ for (const [key, value] of Object.entries(args.query)) {
1292
+ target.searchParams.set(key, value);
1293
+ }
1294
+ }
1295
+ try {
1296
+ return await this.fetchImpl(target.toString(), {
1297
+ method: route.httpMethod,
1298
+ headers: this.headers(),
1299
+ ...args.body !== void 0 ? { body: JSON.stringify(args.body) } : {},
1300
+ signal: this.requestSignal(args.signal)
1301
+ });
1302
+ } catch (error) {
1303
+ throw new A2aClientError(`A2A ${op} request failed: ${describe(error)}`);
1304
+ }
1305
+ }
1306
+ /** Map a non-2xx REST answer to a typed error carrying the A2A code. */
1307
+ async restErrorFrom(res, op) {
1308
+ let body;
1309
+ try {
1310
+ body = await res.json();
1311
+ } catch {
1312
+ return new A2aClientError(`A2A ${op} failed: HTTP ${res.status}`);
1313
+ }
1314
+ if (Value3.Check(RestErrorBodySchema, body)) {
1315
+ return new A2aClientError(`A2A ${op} error: ${body.error.message}`, body.error.code);
1316
+ }
1317
+ return new A2aClientError(`A2A ${op} failed: HTTP ${res.status}`);
1318
+ }
1319
+ async restJson(op, args) {
1320
+ const res = await this.restRequest(op, args);
1321
+ if (!res.ok) throw await this.restErrorFrom(res, op);
1322
+ try {
1323
+ return await res.json();
1324
+ } catch {
1325
+ throw new A2aClientError(`A2A ${op} response is not valid JSON`);
1326
+ }
1327
+ }
1328
+ async *restSse(op, args) {
1329
+ const res = await this.restRequest(op, args);
1330
+ if (!res.ok) throw await this.restErrorFrom(res, op);
1331
+ const contentType = res.headers.get("content-type") ?? "";
1332
+ if (!contentType.includes("text/event-stream")) {
1333
+ throw new A2aClientError(`A2A ${op} answered JSON instead of an SSE stream`);
1334
+ }
1335
+ for await (const data of this.sseData(res, op)) {
1336
+ let frame;
1337
+ try {
1338
+ frame = JSON.parse(data);
1339
+ } catch {
1340
+ throw new A2aClientError(`A2A ${op} SSE frame is not valid JSON`);
1341
+ }
1342
+ yield this.expect(StreamFrameSchema, frame, op);
1343
+ }
1344
+ }
1345
+ };
1346
+
1347
+ // src/server.ts
1348
+ import { createHash, timingSafeEqual } from "crypto";
1349
+ import { createServer } from "http";
1350
+ import { logger, uuidv7 as uuidv73 } from "@ap3x/agent-core";
1351
+ var AGENT_CARD_PATH2 = "/.well-known/agent-card.json";
1352
+ var MAX_BODY_BYTES = 10 * 1024 * 1024;
1353
+ var BODY_DRAIN_TIMEOUT_MS = 3e3;
1354
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "::1", "localhost"]);
1355
+ function constantTimeEqual(a, b) {
1356
+ const ha = createHash("sha256").update(a).digest();
1357
+ const hb = createHash("sha256").update(b).digest();
1358
+ return timingSafeEqual(ha, hb);
1359
+ }
1360
+ function sendJson2(res, status, body) {
1361
+ res.writeHead(status, { "content-type": "application/json" });
1362
+ res.end(JSON.stringify(body));
1363
+ }
1364
+ var A2aServer = class {
1365
+ card;
1366
+ /** Base URL of the bound server (ephemeral port resolved). */
1367
+ url;
1368
+ executor;
1369
+ httpServer;
1370
+ sseStreams;
1371
+ closeGrpc;
1372
+ constructor(card, url, executor, httpServer, sseStreams, closeGrpc) {
1373
+ this.card = card;
1374
+ this.url = url;
1375
+ this.executor = executor;
1376
+ this.httpServer = httpServer;
1377
+ this.sseStreams = sseStreams;
1378
+ if (closeGrpc !== void 0) this.closeGrpc = closeGrpc;
1379
+ }
1380
+ /** Stop listening and tear down any open SSE streams (+ the gRPC listener). */
1381
+ async close() {
1382
+ for (const res of this.sseStreams) res.end();
1383
+ this.sseStreams.clear();
1384
+ await this.closeGrpc?.();
1385
+ await new Promise((resolve, reject) => {
1386
+ this.httpServer.close((error) => error ? reject(error) : resolve());
1387
+ this.httpServer.closeAllConnections();
1388
+ });
1389
+ }
1390
+ };
1391
+ async function serveA2a(backend, options) {
1392
+ if (options.bindings !== void 0 && options.bindings.length === 0) {
1393
+ throw new Error("A2A server requires at least one binding (omit `bindings` for the default)");
1394
+ }
1395
+ const host = options.host ?? "127.0.0.1";
1396
+ const bearerToken = options.auth?.bearerToken;
1397
+ if (!LOOPBACK_HOSTS.has(host) && bearerToken === void 0) {
1398
+ logger.warn(`A2A server binding non-loopback host ${host} WITHOUT auth \u2014 set auth.bearerToken`);
1399
+ }
1400
+ const executor = new A2aExecutor(backend, {
1401
+ now: options.now ?? (() => Date.now()),
1402
+ ...options.maxConcurrentTasks !== void 0 ? { maxConcurrentTasks: options.maxConcurrentTasks } : {},
1403
+ ...options.maxRetainedTasks !== void 0 ? { maxRetainedTasks: options.maxRetainedTasks } : {},
1404
+ ...options.maxRetainedContexts !== void 0 ? { maxRetainedContexts: options.maxRetainedContexts } : {}
1405
+ });
1406
+ const sseStreams = /* @__PURE__ */ new Set();
1407
+ const authorized = (req) => {
1408
+ if (bearerToken === void 0) return true;
1409
+ const header = req.headers.authorization;
1410
+ if (typeof header !== "string" || !header.startsWith("Bearer ")) return false;
1411
+ return constantTimeEqual(header.slice("Bearer ".length), bearerToken);
1412
+ };
1413
+ const httpServer = createServer();
1414
+ await new Promise((resolve, reject) => {
1415
+ httpServer.once("error", reject);
1416
+ httpServer.listen(options.port ?? 0, host, () => resolve());
1417
+ });
1418
+ const address = httpServer.address();
1419
+ const port = typeof address === "object" && address !== null ? address.port : options.port;
1420
+ const url = `http://${host.includes(":") ? `[${host}]` : host}:${port}/`;
1421
+ const bindings = options.bindings ?? ["jsonrpc"];
1422
+ const interfaces = [];
1423
+ if (bindings.includes("jsonrpc")) interfaces.push({ transport: "JSONRPC", url });
1424
+ if (bindings.includes("rest")) interfaces.push({ transport: "HTTP+JSON", url });
1425
+ let grpcServer;
1426
+ if (bindings.includes("grpc")) {
1427
+ try {
1428
+ const { startGrpcServer } = await import("./grpc-5DAU6EYQ.js");
1429
+ grpcServer = await startGrpcServer(executor, {
1430
+ host,
1431
+ port: options.grpcPort ?? 0,
1432
+ ...bearerToken !== void 0 ? { bearerToken } : {}
1433
+ });
1434
+ } catch (error) {
1435
+ httpServer.close();
1436
+ httpServer.closeAllConnections();
1437
+ throw error;
1438
+ }
1439
+ interfaces.push({ transport: "GRPC", url: grpcServer.url });
1440
+ }
1441
+ const preferred = interfaces[0] ?? { transport: "JSONRPC", url };
1442
+ const card = {
1443
+ id: uuidv73(),
1444
+ name: options.card.name,
1445
+ description: options.card.description,
1446
+ url: preferred.url,
1447
+ preferredTransport: preferred.transport,
1448
+ protocolVersion: A2A_CARD_PROTOCOL_VERSION,
1449
+ version: "1.0.0",
1450
+ capabilities: {
1451
+ streaming: typeof backend.runStream === "function",
1452
+ pushNotifications: false
1453
+ // webhooks deferred in v1
1454
+ },
1455
+ defaultInputModes: ["text/plain"],
1456
+ defaultOutputModes: ["text/plain"],
1457
+ skills: options.card.skills ?? [],
1458
+ interfaces,
1459
+ ...bearerToken !== void 0 ? { securitySchemes: { bearer: { type: "http", scheme: "bearer" } } } : {},
1460
+ ...options.card.provider ? { provider: options.card.provider } : {}
1461
+ };
1462
+ const collectBody = (req, res, onBody) => {
1463
+ const chunks = [];
1464
+ let size = 0;
1465
+ req.on("data", (chunk) => {
1466
+ size += chunk.length;
1467
+ if (size > MAX_BODY_BYTES) {
1468
+ req.removeAllListeners("data");
1469
+ req.removeAllListeners("end");
1470
+ const drainTimer = setTimeout(() => req.destroy(), BODY_DRAIN_TIMEOUT_MS);
1471
+ drainTimer.unref();
1472
+ req.on("end", () => {
1473
+ clearTimeout(drainTimer);
1474
+ sendJson2(res, 413, { error: "payload too large" });
1475
+ });
1476
+ req.resume();
1477
+ return;
1478
+ }
1479
+ chunks.push(chunk);
1480
+ });
1481
+ req.on("end", () => {
1482
+ onBody(Buffer.concat(chunks).toString("utf8"));
1483
+ });
1484
+ };
1485
+ httpServer.on("request", (req, res) => {
1486
+ let requestUrl;
1487
+ try {
1488
+ requestUrl = new URL(req.url ?? "/", "http://a2a.internal");
1489
+ } catch {
1490
+ sendJson2(res, 400, { error: "malformed request path" });
1491
+ return;
1492
+ }
1493
+ const path = requestUrl.pathname;
1494
+ if (req.method === "GET" && path === AGENT_CARD_PATH2) {
1495
+ sendJson2(res, 200, card);
1496
+ return;
1497
+ }
1498
+ if (!authorized(req)) {
1499
+ sendJson2(res, 401, { error: "unauthorized" });
1500
+ return;
1501
+ }
1502
+ if (bindings.includes("jsonrpc") && req.method === "POST" && path === "/") {
1503
+ collectBody(req, res, (rawBody) => {
1504
+ void handleJsonRpc(executor, rawBody, res, sseStreams);
1505
+ });
1506
+ return;
1507
+ }
1508
+ if (bindings.includes("rest") && path.startsWith("/v1/")) {
1509
+ const httpMethod = req.method ?? "GET";
1510
+ const dispatch = (rawBody) => {
1511
+ void handleRest(
1512
+ executor,
1513
+ httpMethod,
1514
+ path,
1515
+ requestUrl.searchParams,
1516
+ rawBody,
1517
+ res,
1518
+ sseStreams
1519
+ );
1520
+ };
1521
+ if (httpMethod === "POST") collectBody(req, res, dispatch);
1522
+ else dispatch("");
1523
+ return;
1524
+ }
1525
+ sendJson2(res, 404, { error: "not found" });
1526
+ });
1527
+ return new A2aServer(card, url, executor, httpServer, sseStreams, grpcServer?.close);
1528
+ }
1529
+
1530
+ export {
1531
+ A2A_PROTOCOL_VERSION,
1532
+ A2A_CARD_PROTOCOL_VERSION,
1533
+ TASK_STATES,
1534
+ TERMINAL_TASK_STATES,
1535
+ isTerminal,
1536
+ TaskStateSchema,
1537
+ normalizeTaskState,
1538
+ normalizeWireTaskStates,
1539
+ TextPartSchema,
1540
+ DataPartSchema,
1541
+ UrlPartSchema,
1542
+ RawPartSchema,
1543
+ PartSchema,
1544
+ MessageSchema,
1545
+ TaskStatusSchema,
1546
+ ArtifactSchema,
1547
+ TaskSchema,
1548
+ TaskStatusUpdateEventSchema,
1549
+ TaskArtifactUpdateEventSchema,
1550
+ AgentSkillSchema,
1551
+ AgentInterfaceSchema,
1552
+ BearerSecuritySchemeSchema,
1553
+ AgentCapabilitiesSchema,
1554
+ AgentProviderSchema,
1555
+ AgentCardSchema,
1556
+ RemoteAgentInterfaceSchema,
1557
+ RemoteAgentCardSchema,
1558
+ A2A_ERROR_CODES,
1559
+ TaskNotFoundError,
1560
+ IllegalTaskTransitionError,
1561
+ TaskStore,
1562
+ messageText,
1563
+ agentMessageText,
1564
+ A2aExecutor,
1565
+ A2A_RPC_METHODS,
1566
+ A2A_RPC_METHOD_ALIASES,
1567
+ JsonRpcRequestSchema,
1568
+ MessageSendParamsSchema,
1569
+ resolveBlocking,
1570
+ TaskIdParamsSchema,
1571
+ TaskListParamsSchema,
1572
+ startSse,
1573
+ handleJsonRpc,
1574
+ A2A_REST_ROUTES,
1575
+ RestErrorBodySchema,
1576
+ restStatusFor,
1577
+ handleRest,
1578
+ A2A_VERSION_HEADER,
1579
+ DEFAULT_TIMEOUT_MS,
1580
+ A2aClientError,
1581
+ isTaskFrame,
1582
+ isMessageFrame,
1583
+ isStatusUpdateFrame,
1584
+ normalizeRemoteCard,
1585
+ A2aClient,
1586
+ AGENT_CARD_PATH2 as AGENT_CARD_PATH,
1587
+ constantTimeEqual,
1588
+ A2aServer,
1589
+ serveA2a
1590
+ };