@cogitator-ai/a2a 0.7.7 → 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.
- package/README.md +173 -151
- package/dist/adapters/express.d.ts +5 -0
- package/dist/adapters/express.d.ts.map +1 -1
- package/dist/adapters/express.js +53 -30
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/fastify.d.ts +5 -0
- package/dist/adapters/fastify.d.ts.map +1 -1
- package/dist/adapters/fastify.js +51 -35
- package/dist/adapters/fastify.js.map +1 -1
- package/dist/adapters/hono.d.ts +5 -0
- package/dist/adapters/hono.d.ts.map +1 -1
- package/dist/adapters/hono.js +41 -49
- package/dist/adapters/hono.js.map +1 -1
- package/dist/adapters/koa.d.ts +5 -0
- package/dist/adapters/koa.d.ts.map +1 -1
- package/dist/adapters/koa.js +39 -32
- package/dist/adapters/koa.js.map +1 -1
- package/dist/adapters/next.d.ts +13 -2
- package/dist/adapters/next.d.ts.map +1 -1
- package/dist/adapters/next.js +75 -51
- package/dist/adapters/next.js.map +1 -1
- package/dist/adapters/shared.d.ts +43 -10
- package/dist/adapters/shared.d.ts.map +1 -1
- package/dist/adapters/shared.js +156 -23
- package/dist/adapters/shared.js.map +1 -1
- package/dist/agent-card.d.ts +22 -7
- package/dist/agent-card.d.ts.map +1 -1
- package/dist/agent-card.js +71 -34
- package/dist/agent-card.js.map +1 -1
- package/dist/approvals.d.ts +46 -0
- package/dist/approvals.d.ts.map +1 -0
- package/dist/approvals.js +105 -0
- package/dist/approvals.js.map +1 -0
- package/dist/client.d.ts +59 -17
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +237 -142
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +24 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +90 -19
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +11 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/ownership.d.ts +7 -1
- package/dist/ownership.d.ts.map +1 -1
- package/dist/ownership.js +24 -3
- package/dist/ownership.js.map +1 -1
- package/dist/protocol.d.ts +70 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +187 -0
- package/dist/protocol.js.map +1 -0
- package/dist/push-notifications.d.ts +5 -2
- package/dist/push-notifications.d.ts.map +1 -1
- package/dist/push-notifications.js +33 -23
- package/dist/push-notifications.js.map +1 -1
- package/dist/redis-task-store.js +3 -3
- package/dist/redis-task-store.js.map +1 -1
- package/dist/server.d.ts +75 -12
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +324 -270
- package/dist/server.js.map +1 -1
- package/dist/task-manager.d.ts +57 -11
- package/dist/task-manager.d.ts.map +1 -1
- package/dist/task-manager.js +264 -116
- package/dist/task-manager.js.map +1 -1
- package/dist/task-store.js +4 -4
- package/dist/task-store.js.map +1 -1
- package/dist/types.d.ts +226 -85
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +10 -3
- package/dist/types.js.map +1 -1
- package/dist/url.d.ts +7 -0
- package/dist/url.d.ts.map +1 -0
- package/dist/url.js +12 -0
- package/dist/url.js.map +1 -0
- package/package.json +19 -9
- package/dist/adapters/sse-error-event.d.ts +0 -3
- package/dist/adapters/sse-error-event.d.ts.map +0 -1
- package/dist/adapters/sse-error-event.js +0 -12
- 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 [
|
|
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`)
|
|
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-
|
|
16
|
-
- **A2AClient** -
|
|
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** -
|
|
19
|
-
- **Task
|
|
20
|
-
- **Authentication** - Bearer
|
|
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-
|
|
23
|
-
- **SSE
|
|
24
|
-
- **Push
|
|
25
|
-
- **Agent Card
|
|
26
|
-
- **
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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: [{
|
|
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: [{
|
|
110
|
+
parts: [{ kind: 'text', text: 'Analyze market trends' }],
|
|
87
111
|
})) {
|
|
88
|
-
console.log(event.
|
|
112
|
+
console.log(event.kind, event);
|
|
89
113
|
}
|
|
90
114
|
```
|
|
91
115
|
|
|
92
|
-
`
|
|
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
|
-
|
|
95
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
163
|
-
- `tasks/list` returns only the caller's own tasks and tasks without an owner
|
|
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
|
|
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)`
|
|
195
|
+
`sendMessage(message, configuration)` takes the A2A `MessageSendConfiguration`:
|
|
172
196
|
|
|
173
|
-
| Option | Effect
|
|
174
|
-
| ------------------------ |
|
|
175
|
-
| `blocking: false` | Return the task
|
|
176
|
-
| `historyLength` | Return only the last N history messages (`0` = none)
|
|
177
|
-
| `acceptedOutputModes` | Only return artifacts with these MIME types
|
|
178
|
-
| `
|
|
179
|
-
| `
|
|
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: [{
|
|
207
|
+
{ role: 'user', parts: [{ kind: 'text', text: 'Long running analysis' }] },
|
|
184
208
|
{
|
|
185
209
|
blocking: false,
|
|
186
|
-
pushNotificationConfig: {
|
|
210
|
+
pushNotificationConfig: { url: 'https://my-app.com/webhooks/a2a', token: 'my-secret' },
|
|
187
211
|
}
|
|
188
212
|
);
|
|
189
|
-
// task.status.state === '
|
|
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
|
-
|
|
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: [{
|
|
227
|
+
parts: [{ kind: 'text', text: 'Book a flight to Paris' }],
|
|
205
228
|
});
|
|
206
229
|
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
211
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
249
|
+
## Tool Approvals
|
|
217
250
|
|
|
218
|
-
|
|
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
|
-
|
|
254
|
+
import { readToolApprovalRequest } from '@cogitator-ai/a2a';
|
|
222
255
|
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: [{
|
|
289
|
+
parts: [{ kind: 'text', text: 'Write a poem' }],
|
|
247
290
|
})) {
|
|
248
|
-
switch (event.
|
|
249
|
-
case '
|
|
250
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
305
|
-
const
|
|
306
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
384
|
+
The signature is in the card's `signatures` array (`{ protected, signature }`, both base64url).
|
|
385
|
+
|
|
386
|
+
## Authenticated Extended Agent Card
|
|
363
387
|
|
|
364
|
-
|
|
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
|
-
|
|
397
|
+
skills: [
|
|
374
398
|
{
|
|
375
399
|
id: 'deep_research',
|
|
376
|
-
name: '
|
|
400
|
+
name: 'Deep research',
|
|
377
401
|
description: 'Multi-source deep research',
|
|
378
|
-
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
429
|
-
|
|
|
430
|
-
| `message/send`
|
|
431
|
-
| `message/stream`
|
|
432
|
-
| `tasks/get`
|
|
433
|
-
| `tasks/cancel`
|
|
434
|
-
| `tasks/
|
|
435
|
-
| `tasks/
|
|
436
|
-
| `tasks/
|
|
437
|
-
| `tasks/
|
|
438
|
-
| `tasks/
|
|
439
|
-
| `agent/
|
|
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
|
|
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;
|
|
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"}
|