@cogitator-ai/a2a 0.7.8 → 0.8.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 (82) hide show
  1. package/README.md +173 -151
  2. package/dist/adapters/express.d.ts +5 -0
  3. package/dist/adapters/express.d.ts.map +1 -1
  4. package/dist/adapters/express.js +53 -30
  5. package/dist/adapters/express.js.map +1 -1
  6. package/dist/adapters/fastify.d.ts +5 -0
  7. package/dist/adapters/fastify.d.ts.map +1 -1
  8. package/dist/adapters/fastify.js +51 -35
  9. package/dist/adapters/fastify.js.map +1 -1
  10. package/dist/adapters/hono.d.ts +5 -0
  11. package/dist/adapters/hono.d.ts.map +1 -1
  12. package/dist/adapters/hono.js +41 -49
  13. package/dist/adapters/hono.js.map +1 -1
  14. package/dist/adapters/koa.d.ts +5 -0
  15. package/dist/adapters/koa.d.ts.map +1 -1
  16. package/dist/adapters/koa.js +39 -32
  17. package/dist/adapters/koa.js.map +1 -1
  18. package/dist/adapters/next.d.ts +13 -2
  19. package/dist/adapters/next.d.ts.map +1 -1
  20. package/dist/adapters/next.js +75 -51
  21. package/dist/adapters/next.js.map +1 -1
  22. package/dist/adapters/shared.d.ts +43 -10
  23. package/dist/adapters/shared.d.ts.map +1 -1
  24. package/dist/adapters/shared.js +156 -23
  25. package/dist/adapters/shared.js.map +1 -1
  26. package/dist/agent-card.d.ts +22 -7
  27. package/dist/agent-card.d.ts.map +1 -1
  28. package/dist/agent-card.js +71 -34
  29. package/dist/agent-card.js.map +1 -1
  30. package/dist/approvals.d.ts +46 -0
  31. package/dist/approvals.d.ts.map +1 -0
  32. package/dist/approvals.js +105 -0
  33. package/dist/approvals.js.map +1 -0
  34. package/dist/client.d.ts +59 -17
  35. package/dist/client.d.ts.map +1 -1
  36. package/dist/client.js +237 -142
  37. package/dist/client.js.map +1 -1
  38. package/dist/errors.d.ts +24 -1
  39. package/dist/errors.d.ts.map +1 -1
  40. package/dist/errors.js +90 -19
  41. package/dist/errors.js.map +1 -1
  42. package/dist/index.d.ts +11 -7
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +7 -5
  45. package/dist/index.js.map +1 -1
  46. package/dist/ownership.d.ts +7 -1
  47. package/dist/ownership.d.ts.map +1 -1
  48. package/dist/ownership.js +24 -3
  49. package/dist/ownership.js.map +1 -1
  50. package/dist/protocol.d.ts +70 -0
  51. package/dist/protocol.d.ts.map +1 -0
  52. package/dist/protocol.js +187 -0
  53. package/dist/protocol.js.map +1 -0
  54. package/dist/push-notifications.d.ts +5 -2
  55. package/dist/push-notifications.d.ts.map +1 -1
  56. package/dist/push-notifications.js +33 -23
  57. package/dist/push-notifications.js.map +1 -1
  58. package/dist/redis-task-store.js +3 -3
  59. package/dist/redis-task-store.js.map +1 -1
  60. package/dist/server.d.ts +75 -12
  61. package/dist/server.d.ts.map +1 -1
  62. package/dist/server.js +324 -270
  63. package/dist/server.js.map +1 -1
  64. package/dist/task-manager.d.ts +57 -11
  65. package/dist/task-manager.d.ts.map +1 -1
  66. package/dist/task-manager.js +264 -116
  67. package/dist/task-manager.js.map +1 -1
  68. package/dist/task-store.js +4 -4
  69. package/dist/task-store.js.map +1 -1
  70. package/dist/types.d.ts +226 -85
  71. package/dist/types.d.ts.map +1 -1
  72. package/dist/types.js +10 -3
  73. package/dist/types.js.map +1 -1
  74. package/dist/url.d.ts +7 -0
  75. package/dist/url.d.ts.map +1 -0
  76. package/dist/url.js +12 -0
  77. package/dist/url.js.map +1 -0
  78. package/package.json +19 -9
  79. package/dist/adapters/sse-error-event.d.ts +0 -3
  80. package/dist/adapters/sse-error-event.d.ts.map +0 -1
  81. package/dist/adapters/sse-error-event.js +0 -12
  82. package/dist/adapters/sse-error-event.js.map +0 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @cogitator-ai/a2a
2
2
 
3
- Native implementation of [Google's A2A Protocol v0.3](https://a2a-protocol.org) for Cogitator. Expose agents as A2A services or connect to any A2A-compatible agent across frameworks.
3
+ Native implementation of the [A2A (Agent2Agent) protocol v0.3](https://a2a-protocol.org/v0.3.0/specification/) for Cogitator. Expose agents as A2A services or connect to any A2A v0.3 agent, whatever framework it is built with. Every response is checked against the official v0.3.0 JSON schema in the test suite, and the official A2A JavaScript SDK talks to `A2AServer` (and `A2AClient` to an SDK server) in it.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,25 +8,26 @@ Native implementation of [Google's A2A Protocol v0.3](https://a2a-protocol.org)
8
8
  pnpm add @cogitator-ai/a2a @cogitator-ai/core
9
9
  ```
10
10
 
11
- `@cogitator-ai/core` is needed for `A2AServer` (it runs your agents) and for `asTool()`. Install the framework you mount the server on (`express`, `hono`, `fastify`, `koa` or `next`); they are optional peer dependencies.
11
+ `@cogitator-ai/core` is needed for `A2AServer` (it runs your agents) and for `asTool()`. Install the framework you mount the server on (`express`, `hono`, `fastify`, `koa` or `next`), they are optional peer dependencies.
12
12
 
13
13
  ## Features
14
14
 
15
- - **A2AServer** - Expose any Cogitator agent as an A2A-compliant service
16
- - **A2AClient** - Connect to remote A2A agents with discovery and streaming
15
+ - **A2AServer** - Expose any Cogitator agent as an A2A v0.3 JSON-RPC service
16
+ - **A2AClient** - Talk to any A2A v0.3 agent: card discovery, messages, streaming, tasks
17
17
  - **asTool() Bridge** - Wrap remote A2A agents as local Cogitator tools
18
- - **Agent Card** - Auto-generate A2A Agent Cards from agent metadata
19
- - **Task Management** - Full task lifecycle (working, input-required, completed, failed, canceled)
20
- - **Authentication** - Bearer / API-key auth enforced by every framework adapter and advertised on the Agent Card
18
+ - **Agent Card** - Generated from agent metadata at `/.well-known/agent-card.json`, one card and endpoint per agent
19
+ - **Task lifecycle** - `submitted`, `working`, `input-required`, `completed`, `failed`, `canceled`
20
+ - **Authentication** - Bearer or API-key auth on every adapter, declared in the card's `securitySchemes`, answered with HTTP 401
21
21
  - **Per-user tasks** - `auth.validate` can return `{ userId }` to keep each caller's tasks, contexts and memory apart
22
- - **Multi-Turn Conversations** - Stateful conversations with contextId and continueTask
23
- - **SSE Streaming** - Real-time streaming with token-level events
24
- - **Push Notifications** - Webhook-based task event notifications
25
- - **Agent Card Signing** - HMAC-SHA256 signing and verification
26
- - **Extended Agent Card** - Authenticated endpoint with extra details
22
+ - **Multi-turn** - `input-required` tasks take the client's answer, new tasks in a `contextId` carry the conversation on
23
+ - **SSE streaming** - Every event is a JSON-RPC response, the reply streams token by token as artifact chunks
24
+ - **Push notifications** - The task is POSTed to the client's webhook on every status change
25
+ - **Agent Card signing** - JWS (HS256) signatures in `signatures`
26
+ - **Authenticated extended card** - `agent/getAuthenticatedExtendedCard`
27
+ - **Tool approvals** - Paused runs wait in `input-required` with the calls in a data part
27
28
  - **RedisTaskStore** - Production-grade task persistence
28
- - **Framework Adapters** - Express, Hono, Fastify, Koa, Next.js
29
- - **No A2A SDK dependency** - Own implementation of the spec (runtime deps: `@cogitator-ai/types`, `zod`)
29
+ - **Framework adapters** - Express, Hono, Fastify, Koa, Next.js
30
+ - **No A2A SDK dependency** - Own implementation of the spec (runtime deps: `@cogitator-ai/types`, `@cogitator-ai/server-shared`, `zod`)
30
31
 
31
32
  ---
32
33
 
@@ -51,17 +52,39 @@ const agent = new Agent({
51
52
  const a2aServer = new A2AServer({
52
53
  agents: { researcher: agent },
53
54
  cogitator,
54
- cardUrl: 'https://my-server.com',
55
55
  });
56
56
 
57
57
  const app = express();
58
58
  app.use(a2aExpress(a2aServer));
59
59
  app.listen(3000);
60
- // Agent Card: GET /.well-known/agent.json
60
+ // Agent Card: GET /.well-known/agent-card.json
61
61
  // JSON-RPC: POST /a2a
62
62
  ```
63
63
 
64
- `basePath` (default `/a2a`, must start with `/`) is the JSON-RPC path the adapters serve, relative to where they are mounted, and the card URL when `cardUrl` is unset; it is readable as `a2aServer.basePath`. For Next.js, put the `POST` route file at that path. `cardUrl` is the `url` the cards advertise.
64
+ `basePath` (default `/a2a`, must start with `/`) is the JSON-RPC path the adapters serve, relative to where they are mounted. It is readable as `a2aServer.basePath`.
65
+
66
+ The card's `url` is the absolute address of the agent's endpoint, as the specification requires. Without `cardUrl` the adapters derive it from the request that fetches the card (scheme, host and mount path). Behind a proxy that changes the host or the path, set `cardUrl` to the public URL of the endpoint, e.g. `https://agents.example.com/a2a`.
67
+
68
+ Cards also declare `protocolVersion: '0.3.0'`, `preferredTransport: 'JSONRPC'`, the agent `version` (`agentVersion`, default `1.0.0`) and, with `provider: { organization, url }`, who runs it. The description is the agent's `description` (its name when unset), never its instructions.
69
+
70
+ ### Several agents on one server
71
+
72
+ Every agent gets its own card and endpoint, so any A2A client reaches it by URL:
73
+
74
+ | Route | Serves |
75
+ | ---------------------------------------------------- | ------------------------------------------------------- |
76
+ | `GET /.well-known/agent-card.json` | Card of the first agent |
77
+ | `POST <basePath>` | First agent (or the one named by an `agentName` param) |
78
+ | `GET <basePath>/<agent>/.well-known/agent-card.json` | Card of `<agent>` |
79
+ | `POST <basePath>/<agent>` | `<agent>` |
80
+ | `GET /.well-known/agent.json` | Pre-v0.3 card path: the card, or every card in an array |
81
+
82
+ ```typescript
83
+ // any A2A v0.3 client
84
+ const writer = await new ClientFactory().createFromUrl('https://my-server.com/a2a/writer/');
85
+ // this package
86
+ const writerClient = new A2AClient('https://my-server.com', { agentName: 'writer' });
87
+ ```
65
88
 
66
89
  ### Connect to a Remote A2A Agent
67
90
 
@@ -74,26 +97,27 @@ const client = new A2AClient('https://remote-agent.example.com');
74
97
  const card = await client.agentCard();
75
98
  console.log(card.name, card.skills);
76
99
 
77
- // Send message
78
- const task = await client.sendMessage({
100
+ // Send a message: the server answers with a task (or a direct reply message)
101
+ const result = await client.sendMessage({
79
102
  role: 'user',
80
- parts: [{ type: 'text', text: 'Research quantum computing' }],
103
+ parts: [{ kind: 'text', text: 'Research quantum computing' }],
81
104
  });
105
+ if (result.kind === 'task') console.log(result.status.state);
82
106
 
83
107
  // Stream
84
108
  for await (const event of client.sendMessageStream({
85
109
  role: 'user',
86
- parts: [{ type: 'text', text: 'Analyze market trends' }],
110
+ parts: [{ kind: 'text', text: 'Analyze market trends' }],
87
111
  })) {
88
- console.log(event.type, event);
112
+ console.log(event.kind, event);
89
113
  }
90
114
  ```
91
115
 
92
- `A2AClient` options: `headers` (sent with every request, e.g. `Authorization`), `timeout` (ms, default `30000`), `agentCardPath` (default `/.well-known/agent.json`), `rpcPath` (default `/a2a`; match the server's `basePath`) and `agentName`. On a server that hosts several agents, `agentName` picks one by its registered name: the client sends it with `message/send`, `message/stream` and `agent/extendedCard`, and `agentCard()` returns that agent's card. Without it the server answers with its first agent.
116
+ The client fills in `kind: 'message'` and a fresh `messageId` when a message leaves them out. It reads the card at `/.well-known/agent-card.json` (falling back to `/.well-known/agent.json` for servers before v0.3) and sends every request to the `url` the card names.
93
117
 
94
- ```typescript
95
- const writer = new A2AClient('https://my-server.com', { agentName: 'writer' });
96
- ```
118
+ `A2AClient` options: `headers` (sent with every request, e.g. `Authorization`), `timeout` (ms, default `30000`), `agentCardPath` (a card path relative to the base URL, no fallback), `rpcPath` (send requests to this path at the base URL instead of the card's `url`, without reading the card) and `agentName` (an agent of a Cogitator server hosting several: the client uses that agent's own card and endpoint).
119
+
120
+ Other calls: `getTask(id, historyLength?)`, `cancelTask(id)`, `resubscribeTask(id)` (reconnect to a task's stream), `continueTask(id, text)`, `answerApprovals(id, decisions)`, `listTasks(filter?)` and the push notification calls below.
97
121
 
98
122
  ### Use Remote Agent as a Tool
99
123
 
@@ -114,11 +138,11 @@ const result = await cogitator.run(orchestrator, {
114
138
  });
115
139
  ```
116
140
 
117
- The tool returns `{ output, success, error?, taskId?, state? }`. `output` is the remote agent's latest answer. When the remote task needs more input (`state: 'input-required'`), `success` is `false`, `output` carries the remote agent's question and `taskId` lets you continue the task with `client.continueTask()`. The run's abort signal and the tool timeout are forwarded to the HTTP request.
141
+ The tool returns `{ output, success, error?, taskId?, state?, pendingApprovals? }`. `output` is the remote agent's answer: the text of the task's status message, else of its last agent message, else of its artifacts. When the remote task waits on the client (`state: 'input-required'`), `success` is `false`, `output` carries the remote agent's question and `taskId` lets you answer with `client.continueTask()`. A remote task waiting for [tool approvals](#tool-approvals) lists the calls in `pendingApprovals`. The run's abort signal and the tool timeout are forwarded to the HTTP request.
118
142
 
119
143
  ## Authentication
120
144
 
121
- Protect the JSON-RPC endpoint with a bearer token or an API key. Every framework adapter extracts the credential from the request headers (`Authorization: Bearer <token>` or the API-key header) and the server validates it before routing; the public Agent Card advertises the scheme in `securitySchemes`.
145
+ Protect the JSON-RPC endpoints with a bearer token or an API key. Every framework adapter extracts the credential from the request headers (`Authorization: Bearer <token>` or the API-key header) and the server validates it before routing. The Agent Card declares the scheme in `securitySchemes` and `security`.
122
146
 
123
147
  ```typescript
124
148
  const a2aServer = new A2AServer({
@@ -135,9 +159,9 @@ const client = new A2AClient('https://remote-agent.example.com', {
135
159
  });
136
160
  ```
137
161
 
138
- Custom integrations can call `a2aServer.getAuthToken((name) => headers.get(name))` and pass the result to `handleJsonRpc(body, token)` / `handleJsonRpcStream(body, token, signal)`.
162
+ A missing credential or a `validate` that returns `false` is answered with HTTP `401` (with `WWW-Authenticate: Bearer` for bearer auth) and the JSON-RPC error `-32000 Unauthorized`, for streaming methods too.
139
163
 
140
- A missing credential or a `validate` that returns `false` answers JSON-RPC error `-32000 Unauthorized` (a `failed` status event on a stream).
164
+ Custom integrations can call `a2aServer.getAuthToken((name) => headers.get(name))` and pass the result to `handleJsonRpc(body, token)` or `handleJsonRpcStream(body, token, signal)`.
141
165
 
142
166
  ### One caller per user
143
167
 
@@ -159,109 +183,132 @@ const a2aServer = new A2AServer({
159
183
 
160
184
  Each task then belongs to the user who created it:
161
185
 
162
- - `tasks/get`, `tasks/cancel`, the `tasks/pushNotification/*` methods and a message that continues the task (`taskId`) answer another user's task with `-32001 Task not found`.
163
- - `tasks/list` returns only the caller's own tasks and tasks without an owner; the server sets `TaskFilter.visibleTo` itself, whatever the client sends.
186
+ - `tasks/get`, `tasks/cancel`, `tasks/resubscribe`, the `tasks/pushNotificationConfig/*` methods and a message that continues the task (`taskId`) answer another user's task with `-32001 Task not found`.
187
+ - `tasks/list` returns only the caller's own tasks and tasks without an owner. The server sets `TaskFilter.visibleTo` itself, whatever the client sends.
164
188
  - A new message with a `contextId` that holds another user's tasks is refused with `-32602 Invalid params`.
165
189
  - Runs carry the `userId`, so the agent's threads and memory are scoped to the user too.
166
190
 
167
- Returning `true` admits the caller without a user: every such caller shares one space, as before, and sees only tasks without an owner. The owner is kept in the task's metadata and never sent to clients. A custom `TaskStore` must honour `filter.visibleTo` in `list()` (`InMemoryTaskStore` and `RedisTaskStore` do).
191
+ Returning `true` admits the caller without a user: every such caller shares one space and sees only tasks without an owner. The owner is kept in the task's metadata and never sent to clients. A custom `TaskStore` must honour `filter.visibleTo` in `list()` (`InMemoryTaskStore` and `RedisTaskStore` do).
168
192
 
169
193
  ## Send Configuration
170
194
 
171
- `sendMessage(message, configuration)` supports the A2A `SendMessageConfiguration`:
195
+ `sendMessage(message, configuration)` takes the A2A `MessageSendConfiguration`:
172
196
 
173
- | Option | Effect |
174
- | ------------------------ | ------------------------------------------------------------------------------ |
175
- | `blocking: false` | Return the task immediately in `working` state; poll `getTask` or use webhooks |
176
- | `historyLength` | Return only the last N history messages (`0` = none); also on `getTask` |
177
- | `acceptedOutputModes` | Only return artifacts with these MIME types |
178
- | `timeout` | Maximum agent run time in ms |
179
- | `pushNotificationConfig` | Register a webhook before the task starts executing |
197
+ | Option | Effect |
198
+ | ------------------------ | ----------------------------------------------------------------------- |
199
+ | `blocking: false` | Return the task at once (`submitted`), poll `getTask` or use a webhook |
200
+ | `historyLength` | Return only the last N history messages (`0` = none), also on `getTask` |
201
+ | `acceptedOutputModes` | Only return artifacts with a part of these MIME types |
202
+ | `pushNotificationConfig` | Register a webhook before the task starts executing |
203
+ | `timeout` | Cogitator extension: a shorter run time limit in ms (see below) |
180
204
 
181
205
  ```typescript
182
206
  const task = await client.sendMessage(
183
- { role: 'user', parts: [{ type: 'text', text: 'Long running analysis' }] },
207
+ { role: 'user', parts: [{ kind: 'text', text: 'Long running analysis' }] },
184
208
  {
185
209
  blocking: false,
186
- pushNotificationConfig: { webhookUrl: 'https://my-app.com/webhooks/a2a' },
210
+ pushNotificationConfig: { url: 'https://my-app.com/webhooks/a2a', token: 'my-secret' },
187
211
  }
188
212
  );
189
- // task.status.state === 'working'; completion arrives at the webhook
213
+ // task.status.state === 'submitted', the finished task arrives at the webhook
190
214
  ```
191
215
 
216
+ `timeout` lets a client shorten a run, never lift or remove the operator's limit. It must be a positive integer, and the server caps it at the agent's own `timeout`, or at `maxRunTimeoutMs` (an `A2AServer` option, default `120000`, the Cogitator run default) for an agent without one. Without a client `timeout` the agent's own limit applies.
217
+
192
218
  Client calls also accept request options: `client.sendMessage(message, config, { signal, timeout })`.
193
219
 
194
220
  ## Multi-Turn Conversations
195
221
 
196
- Continue tasks within the same conversation context using `contextId` and `continueTask`:
222
+ A task that waits on the client (`input-required`) takes the answer as a message with its `taskId`. The server appends it to the task and runs the agent again with the task transcript:
197
223
 
198
224
  ```typescript
199
- const client = new A2AClient('https://remote-agent.example.com');
200
-
201
- // Start a conversation
202
225
  const task = await client.sendMessage({
203
226
  role: 'user',
204
- parts: [{ type: 'text', text: 'Research quantum computing' }],
227
+ parts: [{ kind: 'text', text: 'Book a flight to Paris' }],
205
228
  });
206
229
 
207
- // Continue the same task with follow-up
208
- const updated = await client.continueTask(task.id, 'Now compare it with classical computing');
230
+ if (task.kind === 'task' && task.status.state === 'input-required') {
231
+ // the question is in task.status.message
232
+ const answered = await client.continueTask(task.id, 'Next Friday');
233
+ }
234
+ ```
209
235
 
210
- // Both tasks share the same contextId
211
- console.log(task.contextId === updated.contextId); // true
236
+ A task in a terminal state (`completed`, `failed`, `canceled`, `rejected`) can't be restarted, as the specification says: a message for it is refused with `-32600 Invalid request`. Carry the conversation on with a new message in the same `contextId` instead:
237
+
238
+ ```typescript
239
+ const first = await client.sendMessage({ role: 'user', parts: [{ kind: 'text', text: 'Hi' }] });
240
+ const next = await client.sendMessage({
241
+ role: 'user',
242
+ contextId: first.kind === 'task' ? first.contextId : undefined,
243
+ parts: [{ kind: 'text', text: 'Tell me more' }],
244
+ });
212
245
  ```
213
246
 
214
- On the server side, multi-turn is handled automatically. When a message includes a `taskId`, the server calls `continueTask` which appends to the existing task history and re-runs the agent with the task transcript as context, so follow-ups work even without a memory adapter. Runs are threaded by `contextId` (`threadId`), and artifacts accumulate across turns. A task that is still running cannot be continued concurrently.
247
+ Runs are threaded by `contextId` (`threadId`), and the agent gets the transcript of the earlier tasks of the context, so follow-ups work even without a memory adapter. Artifacts accumulate across the turns of a task. A task that is still running takes no second message.
215
248
 
216
- ## Listing Tasks
249
+ ## Tool Approvals
217
250
 
218
- Query tasks with filtering and pagination via `tasks/list`:
251
+ When the agent calls a tool that needs approval (`requiresApproval`), its run pauses and the task waits in `input-required`, never `completed`. Its status message says `Waiting for approval of <tools>` and carries the waiting calls in a data part (`{ kind: 'tool-approval-request', approvals: [{ toolCallId, toolName, arguments, description }] }`). The client answers with a `tool-approval-response` data part in the message that continues the task, and the server resumes the run with those decisions:
219
252
 
220
253
  ```typescript
221
- const client = new A2AClient('https://remote-agent.example.com');
254
+ import { readToolApprovalRequest } from '@cogitator-ai/a2a';
222
255
 
223
- // List all tasks
224
- const tasks = await client.listTasks();
225
-
226
- // Filter by context (conversation)
227
- const conversationTasks = await client.listTasks({
228
- contextId: 'ctx_abc123',
256
+ const task = await client.sendMessage({
257
+ role: 'user',
258
+ parts: [{ kind: 'text', text: 'Refund A-1' }],
229
259
  });
230
260
 
231
- // Filter by state with pagination
232
- const completedTasks = await client.listTasks({
233
- state: 'completed',
234
- limit: 10,
235
- offset: 0,
236
- });
261
+ const waiting = task.kind === 'task' ? readToolApprovalRequest(task) : undefined;
262
+ if (waiting) {
263
+ const done = await client.answerApprovals(task.id, {
264
+ decisions: { [waiting[0].toolCallId]: { approved: true } },
265
+ // or defaultDecision: { approved: false, reason: 'not eligible' }
266
+ });
267
+ }
237
268
  ```
238
269
 
239
- ## Token Streaming
270
+ `toolApprovalResponsePart({ decisions?, defaultDecision? })` builds the part for a hand-written message. Calls left without a decision keep the task waiting. A text reply instead of decisions moves on: the waiting calls are declined and the agent answers the new message. The server resumes through `cogitator.resume()` by the task's `contextId`, so the paused run has to be where the server Cogitator keeps paused runs (its memory, or `runCheckpoints` shared by every server instance). `asTool()` reports such a remote task with `success: false`, the `taskId` and the calls in `pendingApprovals`.
240
271
 
241
- SSE streaming includes token-level events alongside status and artifact updates:
272
+ ## Listing Tasks
273
+
274
+ `tasks/list` is a Cogitator extension method (A2A v0.3 defines no JSON-RPC method for it):
275
+
276
+ ```typescript
277
+ const tasks = await client.listTasks();
278
+ const conversation = await client.listTasks({ contextId: 'ctx_abc123' });
279
+ const completed = await client.listTasks({ state: 'completed', limit: 10, offset: 0 });
280
+ ```
281
+
282
+ ## Streaming
283
+
284
+ `message/stream` answers with Server-Sent Events. Every event is a JSON-RPC response with the request `id` whose `result` is the task, a `status-update` or an `artifact-update`. The stream ends after the status update with `final: true` (a terminal state, `input-required` or `auth-required`):
242
285
 
243
286
  ```typescript
244
287
  for await (const event of client.sendMessageStream({
245
288
  role: 'user',
246
- parts: [{ type: 'text', text: 'Write a poem' }],
289
+ parts: [{ kind: 'text', text: 'Write a poem' }],
247
290
  })) {
248
- switch (event.type) {
249
- case 'token':
250
- process.stdout.write(event.token);
251
- break;
252
- case 'status-update':
253
- console.log(`\nStatus: ${event.status.state}`);
291
+ switch (event.kind) {
292
+ case 'task':
293
+ console.log(`Task ${event.id}`);
254
294
  break;
255
295
  case 'artifact-update':
256
- console.log(`\nArtifact: ${event.artifact.id}`);
296
+ for (const part of event.artifact.parts) {
297
+ if (part.kind === 'text') process.stdout.write(part.text);
298
+ }
299
+ break;
300
+ case 'status-update':
301
+ console.log(`\nStatus: ${event.status.state}${event.final ? ' (final)' : ''}`);
257
302
  break;
258
303
  }
259
304
  }
260
305
  ```
261
306
 
262
- The server emits `TokenStreamEvent` for each token generated by the LLM via the `onToken` callback, giving clients real-time character-by-character output.
307
+ The reply streams token by token as chunks of one artifact: the first chunk starts it, the next ones have `append: true`, the last one `lastChunk: true`. When the final reply differs from the streamed text (text the model wrote before a tool call, for instance), the last chunk replaces the artifact (`append: false`) with the reply, so the artifact a client assembles is always the one `tasks/get` returns.
308
+
309
+ A request that fails before the stream starts (bad params, unknown task, missing credentials) is answered with a plain JSON-RPC error instead of a stream. A failure during the stream ends it with a JSON-RPC error response. `tasks/resubscribe` reconnects to the stream of a task: the task as it is now, then its updates while it still runs in this process.
263
310
 
264
- Artifact events are sent before the final status event, and the stream closes on a terminal state or on `input-required`. The client `timeout` is an idle timeout between events (long runs are fine as long as events keep flowing); pass `{ signal }` as the third argument to cancel. When the client disconnects, the adapters abort the agent run.
311
+ The client `timeout` is an idle timeout between events (long runs are fine as long as events keep flowing), pass `{ signal }` as the third argument to cancel. When the client disconnects, the adapters abort the agent run.
265
312
 
266
313
  ## RedisTaskStore
267
314
 
@@ -290,67 +337,42 @@ const a2aServer = new A2AServer({
290
337
 
291
338
  ## Push Notifications
292
339
 
293
- Register webhooks to receive task event notifications:
340
+ Register a webhook and the server POSTs the task (as `tasks/get` returns it) to it on every status change:
294
341
 
295
342
  ```typescript
296
343
  const client = new A2AClient('https://remote-agent.example.com');
297
344
 
298
- // Start a task
299
- const task = await client.sendMessage({
300
- role: 'user',
301
- parts: [{ type: 'text', text: 'Long running analysis' }],
345
+ const config = await client.setPushNotificationConfig(task.id, {
346
+ url: 'https://my-app.com/webhooks/a2a',
347
+ token: 'per-task-secret', // sent back in X-A2A-Notification-Token
348
+ authentication: { schemes: ['Bearer'], credentials: 'my-webhook-token' },
302
349
  });
303
350
 
304
- // Register a webhook for this task
305
- const config = await client.createPushNotification(task.id, {
306
- webhookUrl: 'https://my-app.com/webhooks/a2a',
307
- authenticationInfo: {
308
- scheme: 'bearer',
309
- credentials: { token: 'my-webhook-secret' },
310
- },
311
- });
312
-
313
- // List registered webhooks
314
- const configs = await client.listPushNotifications(task.id);
315
-
316
- // Remove a webhook
317
- await client.deletePushNotification(task.id, config.id!);
351
+ const one = await client.getPushNotificationConfig(task.id, config.pushNotificationConfig.id);
352
+ const all = await client.listPushNotificationConfigs(task.id);
353
+ await client.deletePushNotificationConfig(task.id, config.pushNotificationConfig.id!);
318
354
  ```
319
355
 
320
- Server-side setup requires a `PushNotificationStore`:
321
-
322
- ```typescript
323
- import { A2AServer, InMemoryPushNotificationStore } from '@cogitator-ai/a2a';
324
-
325
- const a2aServer = new A2AServer({
326
- agents: { researcher },
327
- cogitator,
328
- pushNotificationStore: new InMemoryPushNotificationStore(),
329
- });
330
- ```
356
+ The webhook gets `X-A2A-Notification-Token` with the config's `token`, and `Authorization: Bearer <credentials>` (or `Basic <credentials>`) for the first of `authentication.schemes` the server knows. Every config method requires an existing task the caller may see (otherwise `-32001 Task not found`). Use `configuration.pushNotificationConfig` with `blocking: false` to subscribe before execution starts.
331
357
 
332
- When configured, the server automatically sends POST requests with `A2AStreamEvent` payloads to registered webhook URLs on task status and artifact updates. The Agent Card will advertise `pushNotifications: true`. Every push-notification method (`create`, `get`, `list`, `delete`) requires an existing task the caller may see (otherwise `TaskNotFound`); use `configuration.pushNotificationConfig` with `blocking: false` to subscribe before execution starts.
358
+ Configs live in `InMemoryPushNotificationStore` unless you pass a `pushNotificationStore`, and cards declare `capabilities.pushNotifications: true`.
333
359
 
334
360
  Webhook delivery is SSRF-hardened unless `allowPrivateUrls: true`: loopback, private, link-local, CGNAT, multicast and IPv4-mapped IPv6 targets are rejected, the check is applied to the address the socket actually connects to (no DNS-rebinding window), and redirects are not followed. `isPrivateAddress()` is exported for reuse.
335
361
 
336
362
  ## Agent Card Signing
337
363
 
338
- Sign Agent Cards with HMAC-SHA256 for integrity verification:
364
+ Sign Agent Cards with a JWS (RFC 7515, `HS256`, detached payload) over the card's canonical JSON (RFC 8785, without `signatures`), as A2A v0.3 specifies:
339
365
 
340
366
  ```typescript
341
367
  import { signAgentCard, verifyAgentCardSignature } from '@cogitator-ai/a2a';
342
368
 
343
- // Server-side: sign cards automatically
344
369
  const a2aServer = new A2AServer({
345
370
  agents: { researcher },
346
371
  cogitator,
347
- cardSigning: {
348
- algorithm: 'hmac-sha256',
349
- secret: process.env.CARD_SIGNING_SECRET!,
350
- },
372
+ cardSigning: { secret: process.env.CARD_SIGNING_SECRET! },
351
373
  });
352
374
 
353
- // Client-side: verify a card's signature
375
+ // Client-side
354
376
  const client = new A2AClient('https://remote-agent.example.com');
355
377
  const isValid = await client.verifyAgentCard(process.env.CARD_SIGNING_SECRET!);
356
378
 
@@ -359,41 +381,37 @@ const card = await client.agentCard();
359
381
  const valid = verifyAgentCardSignature(card, 'shared-secret');
360
382
  ```
361
383
 
362
- ## Extended Agent Card
384
+ The signature is in the card's `signatures` array (`{ protected, signature }`, both base64url).
385
+
386
+ ## Authenticated Extended Agent Card
363
387
 
364
- Provide an authenticated endpoint with additional agent details (rate limits, pricing, extended skills):
388
+ Serve a more detailed card to authenticated clients with `agent/getAuthenticatedExtendedCard`:
365
389
 
366
390
  ```typescript
367
- // Server-side: configure extended card generator
368
391
  const a2aServer = new A2AServer({
369
392
  agents: { researcher },
370
393
  cogitator,
394
+ auth: { type: 'bearer', validate: async (token) => token === process.env.A2A_TOKEN },
371
395
  extendedCardGenerator: (agentName) => ({
372
396
  ...a2aServer.getAgentCard(agentName),
373
- extendedSkills: [
397
+ skills: [
374
398
  {
375
399
  id: 'deep_research',
376
- name: 'deep_research',
400
+ name: 'Deep research',
377
401
  description: 'Multi-source deep research',
378
- inputModes: ['text/plain'],
379
- outputModes: ['text/plain', 'application/json'],
402
+ tags: ['research'],
380
403
  },
381
404
  ],
382
- rateLimit: { requestsPerMinute: 60 },
383
- pricing: { model: 'per-request', details: '$0.01 per task' },
384
- metadata: { version: '2.1.0', region: 'us-east-1' },
385
405
  }),
386
406
  });
387
407
 
388
- // Client-side: fetch the extended card (requires auth)
389
408
  const client = new A2AClient('https://remote-agent.example.com', {
390
- headers: { Authorization: 'Bearer my-token' },
409
+ headers: { Authorization: `Bearer ${process.env.A2A_TOKEN}` },
391
410
  });
392
411
  const extendedCard = await client.extendedAgentCard();
393
- console.log(extendedCard.rateLimit, extendedCard.pricing);
394
412
  ```
395
413
 
396
- The `agent/extendedCard` method is only available when `extendedCardGenerator` is configured on the server. The Agent Card advertises this via `capabilities.extendedAgentCard: true`.
414
+ The public card declares `supportsAuthenticatedExtendedCard: true` when `extendedCardGenerator` is set. Without it the method answers `-32007 Authenticated Extended Card not configured`.
397
415
 
398
416
  ## Framework Adapters
399
417
 
@@ -402,7 +420,7 @@ The `agent/extendedCard` method is only available when `extendedCardGenerator` i
402
420
  import { a2aExpress } from '@cogitator-ai/a2a/express';
403
421
  app.use(a2aExpress(server));
404
422
 
405
- // Hono (routes are /.well-known/agent.json and server.basePath)
423
+ // Hono
406
424
  import { a2aHono } from '@cogitator-ai/a2a/hono';
407
425
  app.route('/', a2aHono(server));
408
426
 
@@ -410,33 +428,37 @@ app.route('/', a2aHono(server));
410
428
  import { a2aFastify } from '@cogitator-ai/a2a/fastify';
411
429
  fastify.register(a2aFastify(server));
412
430
 
413
- // Koa
431
+ // Koa (after a JSON body parser)
414
432
  import { a2aKoa } from '@cogitator-ai/a2a/koa';
415
433
  app.use(a2aKoa(server));
416
434
 
417
- // Next.js
435
+ // Next.js: app/.well-known/agent-card.json/route.ts exports GET, app/a2a/route.ts exports POST
436
+ // (and app/a2a/[agent]/route.ts serves each agent's own endpoint)
418
437
  import { a2aNext } from '@cogitator-ai/a2a/next';
419
438
  export const { GET, POST } = a2aNext(server);
420
439
  ```
421
440
 
422
- All adapters stream only for `message/stream` (an `Accept: text/event-stream` header alone does not switch `message/send` to SSE), answer JSON-RPC notifications with `204`, and pass the request credentials to the server. Structurally invalid JSON-RPC requests get `-32600 Invalid Request`; only unparseable JSON gets `-32700 Parse error`.
441
+ All adapters serve the routes listed [above](#several-agents-on-one-server), stream only for `message/stream` and `tasks/resubscribe` (an `Accept: text/event-stream` header alone does not switch `message/send` to SSE), answer JSON-RPC notifications with `204`, a request that is not `application/json` with `415`, and pass the request credentials to the server. While a stream is open they write a `: keep-alive` comment every `sseHeartbeatMs` (an `A2AServer` option, 5 seconds by default, `0` turns it off), so a proxy or Bun's idle timeout does not cut a run that waits on a slow tool. Structurally invalid JSON-RPC requests get `-32600 Invalid Request`, only unparseable JSON gets `-32700 Parse error`.
423
442
 
424
- Errors that are neither A2A errors nor `CogitatorError`s (in parsing, authentication, routing, streams or agent runs) are logged on the server and answered as `-32603 Internal error`, without their text; a `CogitatorError` is answered as `-32603 Internal error: <message>`. A task that fails that way gets the status message `Internal error`, and so does the `failed` status event of a stream.
443
+ Errors that are neither A2A errors nor `CogitatorError`s (in parsing, authentication, routing, streams or agent runs) are logged on the server and answered as `-32603 Internal error`, without their text. A `CogitatorError` is answered as `-32603 Internal error: <message>`. A task that fails that way gets the status message `Internal error`.
425
444
 
426
445
  ## A2A Protocol
427
446
 
428
- | Method | Description |
429
- | ------------------------------- | ----------------------------------------- |
430
- | `message/send` | Send a message (blocking or background) |
431
- | `message/stream` | Send with SSE streaming (incl. tokens) |
432
- | `tasks/get` | Retrieve task by ID |
433
- | `tasks/cancel` | Cancel a running task |
434
- | `tasks/list` | List tasks with filtering and pagination |
435
- | `tasks/pushNotification/create` | Register a webhook for task events |
436
- | `tasks/pushNotification/get` | Get a push notification config |
437
- | `tasks/pushNotification/list` | List push notification configs for a task |
438
- | `tasks/pushNotification/delete` | Remove a push notification config |
439
- | `agent/extendedCard` | Fetch extended Agent Card (authenticated) |
447
+ | Method | Description |
448
+ | ------------------------------------- | ------------------------------------------------------- |
449
+ | `message/send` | Send a message (blocking or background) |
450
+ | `message/stream` | Send with SSE streaming |
451
+ | `tasks/get` | Retrieve a task by id |
452
+ | `tasks/cancel` | Cancel a running task |
453
+ | `tasks/resubscribe` | Reconnect to the stream of a task |
454
+ | `tasks/pushNotificationConfig/set` | Register a webhook for a task |
455
+ | `tasks/pushNotificationConfig/get` | Get a webhook config |
456
+ | `tasks/pushNotificationConfig/list` | List the webhook configs of a task |
457
+ | `tasks/pushNotificationConfig/delete` | Remove a webhook config |
458
+ | `agent/getAuthenticatedExtendedCard` | Fetch the authenticated extended Agent Card |
459
+ | `tasks/list` | Cogitator extension: list tasks with filters and paging |
460
+
461
+ Error codes follow section 8 of the specification (`-32001` task not found, `-32002` not cancelable, `-32004` unsupported operation, `-32007` extended card not configured), plus `-32000` for missing or rejected credentials. An unknown agent name is `-32602 Invalid params`.
440
462
 
441
463
  ## Documentation
442
464
 
@@ -444,7 +466,7 @@ Full guide: [cogitator.app/docs/integrations/a2a](https://cogitator.app/docs/int
444
466
 
445
467
  ## Part of Cogitator
446
468
 
447
- This package is part of the [Cogitator](https://github.com/cogitator-ai/Cogitator-AI) ecosystem — a self-hosted, production-grade AI agent runtime for TypeScript.
469
+ This package is part of the [Cogitator](https://github.com/cogitator-ai/Cogitator-AI) ecosystem, a self-hosted, production-grade AI agent runtime for TypeScript.
448
470
 
449
471
  ## License
450
472
 
@@ -1,4 +1,9 @@
1
1
  import { Router } from 'express';
2
2
  import type { A2AServer } from '../server.js';
3
+ /**
4
+ * Express router serving an A2A server: the Agent Card at `/.well-known/agent-card.json` (and the
5
+ * pre-v0.3 `/.well-known/agent.json`), JSON-RPC at `basePath`, and every agent's own card and
6
+ * endpoint under `<basePath>/<agent name>`.
7
+ */
3
8
  export declare function a2aExpress(server: A2AServer): Router;
4
9
  //# sourceMappingURL=express.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAwD,MAAM,SAAS,CAAC;AACvF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAK9C,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAmDpD"}
1
+ {"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAwD,MAAM,SAAS,CAAC;AACvF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAqC9C;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CA8BpD"}