@ap3x/a2a 1.0.0 → 2.0.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
package/dist/index.js ADDED
@@ -0,0 +1,454 @@
1
+ import {
2
+ A2A_CARD_PROTOCOL_VERSION,
3
+ A2A_ERROR_CODES,
4
+ A2A_PROTOCOL_VERSION,
5
+ A2A_REST_ROUTES,
6
+ A2A_RPC_METHODS,
7
+ A2A_RPC_METHOD_ALIASES,
8
+ A2A_VERSION_HEADER,
9
+ A2aClient,
10
+ A2aClientError,
11
+ A2aExecutor,
12
+ A2aServer,
13
+ AGENT_CARD_PATH,
14
+ AgentCapabilitiesSchema,
15
+ AgentCardSchema,
16
+ AgentInterfaceSchema,
17
+ AgentProviderSchema,
18
+ AgentSkillSchema,
19
+ ArtifactSchema,
20
+ BearerSecuritySchemeSchema,
21
+ DEFAULT_TIMEOUT_MS,
22
+ DataPartSchema,
23
+ IllegalTaskTransitionError,
24
+ JsonRpcRequestSchema,
25
+ MessageSchema,
26
+ MessageSendParamsSchema,
27
+ PartSchema,
28
+ RawPartSchema,
29
+ RemoteAgentCardSchema,
30
+ RemoteAgentInterfaceSchema,
31
+ RestErrorBodySchema,
32
+ TASK_STATES,
33
+ TERMINAL_TASK_STATES,
34
+ TaskArtifactUpdateEventSchema,
35
+ TaskIdParamsSchema,
36
+ TaskListParamsSchema,
37
+ TaskNotFoundError,
38
+ TaskSchema,
39
+ TaskStateSchema,
40
+ TaskStatusSchema,
41
+ TaskStatusUpdateEventSchema,
42
+ TaskStore,
43
+ TextPartSchema,
44
+ UrlPartSchema,
45
+ agentMessageText,
46
+ constantTimeEqual,
47
+ handleJsonRpc,
48
+ handleRest,
49
+ isMessageFrame,
50
+ isStatusUpdateFrame,
51
+ isTaskFrame,
52
+ isTerminal,
53
+ messageText,
54
+ normalizeRemoteCard,
55
+ normalizeTaskState,
56
+ normalizeWireTaskStates,
57
+ resolveBlocking,
58
+ restStatusFor,
59
+ serveA2a,
60
+ startSse
61
+ } from "./chunk-PYM7LV6R.js";
62
+
63
+ // src/backend.ts
64
+ import { uuidv7 } from "@ap3x/agent-core";
65
+ function zeroUsage() {
66
+ return {
67
+ input: 0,
68
+ output: 0,
69
+ cacheRead: 0,
70
+ cacheWrite: 0,
71
+ totalTokens: 0,
72
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
73
+ };
74
+ }
75
+ var ABORTED_MESSAGE = "A2A run canceled: abort() requested";
76
+ var DEFAULT_POLL_INITIAL_MS = 250;
77
+ var DEFAULT_POLL_MAX_MS = 5e3;
78
+ var DEFAULT_RUN_TIMEOUT_MS = 10 * 60 * 1e3;
79
+ function defaultSleep(ms) {
80
+ return new Promise((resolve) => setTimeout(resolve, ms));
81
+ }
82
+ function makeEventStream(run) {
83
+ const queue = [];
84
+ const waiters = [];
85
+ let done = false;
86
+ const emit = (event) => {
87
+ const waiter = waiters.shift();
88
+ if (waiter) waiter({ value: event, done: false });
89
+ else queue.push(event);
90
+ };
91
+ const resultPromise = run(emit).finally(() => {
92
+ done = true;
93
+ while (waiters.length > 0) {
94
+ waiters.shift()?.({ value: void 0, done: true });
95
+ }
96
+ });
97
+ return {
98
+ result: () => resultPromise,
99
+ async *[Symbol.asyncIterator]() {
100
+ while (true) {
101
+ if (queue.length > 0) {
102
+ yield queue.shift();
103
+ } else if (done) {
104
+ return;
105
+ } else {
106
+ const next = await new Promise((resolve) => {
107
+ waiters.push(resolve);
108
+ });
109
+ if (next.done) return;
110
+ yield next.value;
111
+ }
112
+ }
113
+ }
114
+ };
115
+ }
116
+ var A2aBackend = class _A2aBackend {
117
+ card;
118
+ /** Present only when the card declares `capabilities.streaming`. */
119
+ runStream;
120
+ client;
121
+ options;
122
+ pollInitialDelayMs;
123
+ pollMaxDelayMs;
124
+ timeoutMs;
125
+ sleep;
126
+ now;
127
+ /** One conversational thread per instance; adopted from the first answer. */
128
+ contextId;
129
+ currentTaskId;
130
+ /** Aborts the in-flight HTTP request of the current run/stream. */
131
+ runAbort;
132
+ closed = false;
133
+ /** Clones share the client (one grpc channel); the channel closes at zero. */
134
+ clientRefs;
135
+ constructor(client, options = {}, refs) {
136
+ this.client = client;
137
+ this.card = client.card;
138
+ this.options = options;
139
+ this.clientRefs = refs ?? { count: 1 };
140
+ this.pollInitialDelayMs = Math.max(1, options.pollInitialDelayMs ?? DEFAULT_POLL_INITIAL_MS);
141
+ this.pollMaxDelayMs = Math.max(
142
+ this.pollInitialDelayMs,
143
+ options.pollMaxDelayMs ?? DEFAULT_POLL_MAX_MS
144
+ );
145
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_RUN_TIMEOUT_MS;
146
+ this.sleep = options.sleep ?? defaultSleep;
147
+ this.now = options.now ?? (() => Date.now());
148
+ if (this.card.capabilities.streaming) {
149
+ this.runStream = (task, opts) => this.streamRun(task, opts);
150
+ }
151
+ }
152
+ async run(task, opts) {
153
+ const controller = new AbortController();
154
+ this.runAbort = controller;
155
+ const onAbort = () => this.abort();
156
+ opts?.signal?.addEventListener("abort", onAbort, { once: true });
157
+ if (opts?.signal?.aborted) this.abort();
158
+ try {
159
+ const message = this.outbound(task);
160
+ const answer = await this.client.sendMessage(message, void 0, controller.signal);
161
+ if (!("status" in answer)) {
162
+ if (answer.contextId !== void 0) this.contextId ??= answer.contextId;
163
+ const finalText = messageText(answer);
164
+ return {
165
+ messages: [this.assistant(finalText, "stop")],
166
+ finalText,
167
+ usage: zeroUsage(),
168
+ stopReason: "stop"
169
+ };
170
+ }
171
+ let remote = answer;
172
+ this.adopt(remote.id, remote.contextId);
173
+ const deadline = this.now() + this.timeoutMs;
174
+ let delay = this.pollInitialDelayMs;
175
+ while (!isTerminal(remote.status.state)) {
176
+ if (this.now() >= deadline) {
177
+ void this.cancelRemote();
178
+ return this.failure(`A2A task ${remote.id} timed out after ${this.timeoutMs}ms`);
179
+ }
180
+ await this.sleep(delay);
181
+ delay = Math.min(delay * 2, this.pollMaxDelayMs);
182
+ remote = await this.client.getTask(remote.id, controller.signal);
183
+ }
184
+ return this.toRunResult(remote, message.messageId);
185
+ } catch (error) {
186
+ if (controller.signal.aborted) return this.failure(ABORTED_MESSAGE);
187
+ return this.failure(error instanceof Error ? error.message : String(error));
188
+ } finally {
189
+ this.currentTaskId = void 0;
190
+ this.runAbort = void 0;
191
+ opts?.signal?.removeEventListener("abort", onAbort);
192
+ }
193
+ }
194
+ /**
195
+ * Cancel the current run: aborts the in-flight HTTP request (even a blocking
196
+ * send with no task id yet) and, when a task id is known, issues a
197
+ * best-effort remote CancelTask.
198
+ */
199
+ abort() {
200
+ void this.cancelRemote();
201
+ this.runAbort?.abort();
202
+ }
203
+ /** A new backend over the SAME client/card (a remote agent is one endpoint). */
204
+ clone() {
205
+ this.clientRefs.count += 1;
206
+ return new _A2aBackend(this.client, this.options, this.clientRefs);
207
+ }
208
+ /**
209
+ * Release the shared client once EVERY clone has closed (refcounted): the
210
+ * underlying transport (the grpc channel, when negotiated) is released only
211
+ * when the last backend over it closes. Idempotent per backend.
212
+ */
213
+ close() {
214
+ if (this.closed) return;
215
+ this.closed = true;
216
+ this.clientRefs.count -= 1;
217
+ if (this.clientRefs.count === 0) this.client.close();
218
+ }
219
+ [Symbol.dispose]() {
220
+ this.close();
221
+ }
222
+ async [Symbol.asyncDispose]() {
223
+ this.close();
224
+ }
225
+ supports(capability) {
226
+ switch (capability) {
227
+ case "stream":
228
+ return this.card.capabilities.streaming;
229
+ case "abort":
230
+ case "clone":
231
+ return true;
232
+ default:
233
+ return false;
234
+ }
235
+ }
236
+ async cancelRemote() {
237
+ const id = this.currentTaskId;
238
+ if (id === void 0) return;
239
+ try {
240
+ await this.client.cancelTask(id);
241
+ } catch {
242
+ }
243
+ }
244
+ adopt(taskId, contextId) {
245
+ this.currentTaskId = taskId;
246
+ this.contextId ??= contextId;
247
+ }
248
+ outbound(task) {
249
+ const text = typeof task === "string" ? task : task.map((message) => agentMessageText(message)).filter((chunk) => chunk.length > 0).join("\n");
250
+ return {
251
+ messageId: uuidv7(),
252
+ role: "user",
253
+ parts: [{ kind: "text", text }],
254
+ ...this.contextId !== void 0 ? { contextId: this.contextId } : {}
255
+ };
256
+ }
257
+ assistant(text, stopReason, errorMessage) {
258
+ return {
259
+ role: "assistant",
260
+ content: [{ type: "text", text }],
261
+ api: "a2a",
262
+ provider: "a2a",
263
+ model: this.card.name,
264
+ usage: zeroUsage(),
265
+ stopReason,
266
+ ...errorMessage !== void 0 ? { errorMessage } : {},
267
+ timestamp: this.now()
268
+ };
269
+ }
270
+ inbound(message) {
271
+ if (message.role === "user") {
272
+ return { role: "user", content: messageText(message), timestamp: this.now() };
273
+ }
274
+ return this.assistant(messageText(message), "stop");
275
+ }
276
+ failure(errorMessage) {
277
+ return {
278
+ messages: [this.assistant("", "error", errorMessage)],
279
+ finalText: "",
280
+ usage: zeroUsage(),
281
+ stopReason: "error",
282
+ errorMessage
283
+ };
284
+ }
285
+ /** Map a TERMINAL remote task back to the uniform backend result. */
286
+ toRunResult(remote, sentMessageId) {
287
+ const statusText = remote.status.message ? messageText(remote.status.message) : "";
288
+ if (remote.status.state !== "completed") {
289
+ return this.failure(statusText || `A2A task ${remote.id} ${remote.status.state}`);
290
+ }
291
+ const messages = (remote.history ?? []).filter((message) => message.messageId !== sentMessageId).map((message) => this.inbound(message));
292
+ const lastAssistant = [...messages].reverse().find((message) => message.role === "assistant");
293
+ const finalText = statusText || (lastAssistant?.content ?? []).filter((block) => block.type === "text").map((block) => block.text).join("\n");
294
+ if (!lastAssistant) messages.push(this.assistant(finalText, "stop"));
295
+ return { messages, finalText, usage: zeroUsage(), stopReason: "stop" };
296
+ }
297
+ streamRun(task, opts) {
298
+ return makeEventStream(async (emit) => {
299
+ const controller = new AbortController();
300
+ this.runAbort = controller;
301
+ const onAbort = () => this.abort();
302
+ opts?.signal?.addEventListener("abort", onAbort, { once: true });
303
+ if (opts?.signal?.aborted) this.abort();
304
+ try {
305
+ emit({ type: "agent_start" });
306
+ const message = this.outbound(task);
307
+ let terminal;
308
+ let direct = false;
309
+ let accumulated = "";
310
+ for await (const frame of this.client.sendStreamingMessage(message, controller.signal)) {
311
+ if (isMessageFrame(frame)) {
312
+ if (frame.contextId !== void 0) this.contextId ??= frame.contextId;
313
+ const delta2 = messageText(frame);
314
+ if (delta2.length > 0) {
315
+ accumulated += delta2;
316
+ const partial = this.assistant(accumulated, "stop");
317
+ emit({
318
+ type: "message_update",
319
+ message: partial,
320
+ assistantMessageEvent: { type: "text_delta", contentIndex: 0, delta: delta2, partial }
321
+ });
322
+ }
323
+ direct = true;
324
+ break;
325
+ }
326
+ if (isTaskFrame(frame)) {
327
+ this.adopt(frame.id, frame.contextId);
328
+ if (isTerminal(frame.status.state)) {
329
+ terminal = { taskId: frame.id, status: frame.status };
330
+ break;
331
+ }
332
+ continue;
333
+ }
334
+ if (!isStatusUpdateFrame(frame)) continue;
335
+ this.adopt(frame.taskId, frame.contextId);
336
+ if (frame.final) {
337
+ terminal = { taskId: frame.taskId, status: frame.status };
338
+ break;
339
+ }
340
+ const delta = frame.status.message ? messageText(frame.status.message) : "";
341
+ if (frame.status.state === "working" && delta.length > 0) {
342
+ accumulated += delta;
343
+ const partial = this.assistant(accumulated, "stop");
344
+ emit({
345
+ type: "message_update",
346
+ message: partial,
347
+ assistantMessageEvent: { type: "text_delta", contentIndex: 0, delta, partial }
348
+ });
349
+ }
350
+ }
351
+ let final;
352
+ if (direct) {
353
+ final = this.assistant(accumulated, "stop");
354
+ } else if (!terminal) {
355
+ final = this.assistant("", "error", "A2A stream ended without a terminal event");
356
+ } else if (terminal.status.state === "completed") {
357
+ const text = terminal.status.message ? messageText(terminal.status.message) : accumulated;
358
+ final = this.assistant(text, "stop");
359
+ } else {
360
+ const text = terminal.status.message ? messageText(terminal.status.message) : "";
361
+ final = this.assistant(
362
+ "",
363
+ "error",
364
+ text || `A2A task ${terminal.taskId} ${terminal.status.state}`
365
+ );
366
+ }
367
+ emit({ type: "message_end", message: final });
368
+ const messages = [final];
369
+ emit({ type: "agent_end", messages });
370
+ return messages;
371
+ } catch (error) {
372
+ const final = this.assistant(
373
+ "",
374
+ "error",
375
+ controller.signal.aborted ? ABORTED_MESSAGE : error instanceof Error ? error.message : String(error)
376
+ );
377
+ const messages = [final];
378
+ emit({ type: "agent_end", messages });
379
+ return messages;
380
+ } finally {
381
+ this.currentTaskId = void 0;
382
+ this.runAbort = void 0;
383
+ opts?.signal?.removeEventListener("abort", onAbort);
384
+ }
385
+ });
386
+ }
387
+ };
388
+ async function connectA2a(baseUrl, options = {}) {
389
+ const client = await A2aClient.connect(baseUrl, options);
390
+ return new A2aBackend(client, options);
391
+ }
392
+ export {
393
+ A2A_CARD_PROTOCOL_VERSION,
394
+ A2A_ERROR_CODES,
395
+ A2A_PROTOCOL_VERSION,
396
+ A2A_REST_ROUTES,
397
+ A2A_RPC_METHODS,
398
+ A2A_RPC_METHOD_ALIASES,
399
+ A2A_VERSION_HEADER,
400
+ A2aBackend,
401
+ A2aClient,
402
+ A2aClientError,
403
+ A2aExecutor,
404
+ A2aServer,
405
+ AGENT_CARD_PATH,
406
+ AgentCapabilitiesSchema,
407
+ AgentCardSchema,
408
+ AgentInterfaceSchema,
409
+ AgentProviderSchema,
410
+ AgentSkillSchema,
411
+ ArtifactSchema,
412
+ BearerSecuritySchemeSchema,
413
+ DEFAULT_TIMEOUT_MS,
414
+ DataPartSchema,
415
+ IllegalTaskTransitionError,
416
+ JsonRpcRequestSchema,
417
+ MessageSchema,
418
+ MessageSendParamsSchema,
419
+ PartSchema,
420
+ RawPartSchema,
421
+ RemoteAgentCardSchema,
422
+ RemoteAgentInterfaceSchema,
423
+ RestErrorBodySchema,
424
+ TASK_STATES,
425
+ TERMINAL_TASK_STATES,
426
+ TaskArtifactUpdateEventSchema,
427
+ TaskIdParamsSchema,
428
+ TaskListParamsSchema,
429
+ TaskNotFoundError,
430
+ TaskSchema,
431
+ TaskStateSchema,
432
+ TaskStatusSchema,
433
+ TaskStatusUpdateEventSchema,
434
+ TaskStore,
435
+ TextPartSchema,
436
+ UrlPartSchema,
437
+ agentMessageText,
438
+ connectA2a,
439
+ constantTimeEqual,
440
+ handleJsonRpc,
441
+ handleRest,
442
+ isMessageFrame,
443
+ isStatusUpdateFrame,
444
+ isTaskFrame,
445
+ isTerminal,
446
+ messageText,
447
+ normalizeRemoteCard,
448
+ normalizeTaskState,
449
+ normalizeWireTaskStates,
450
+ resolveBlocking,
451
+ restStatusFor,
452
+ serveA2a,
453
+ startSse
454
+ };