@cogitator-ai/a2a 0.3.10 → 0.4.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/LICENSE +21 -0
- package/README.md +64 -7
- package/dist/adapters/express.d.ts.map +1 -1
- package/dist/adapters/express.js +22 -22
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/fastify.d.ts.map +1 -1
- package/dist/adapters/fastify.js +23 -24
- package/dist/adapters/fastify.js.map +1 -1
- package/dist/adapters/hono.d.ts.map +1 -1
- package/dist/adapters/hono.js +25 -8
- package/dist/adapters/hono.js.map +1 -1
- package/dist/adapters/koa.d.ts.map +1 -1
- package/dist/adapters/koa.js +16 -23
- package/dist/adapters/koa.js.map +1 -1
- package/dist/adapters/next.d.ts.map +1 -1
- package/dist/adapters/next.js +22 -19
- package/dist/adapters/next.js.map +1 -1
- package/dist/adapters/shared.d.ts +18 -0
- package/dist/adapters/shared.d.ts.map +1 -0
- package/dist/adapters/shared.js +52 -0
- package/dist/adapters/shared.js.map +1 -0
- package/dist/adapters/sse-error-event.d.ts +3 -0
- package/dist/adapters/sse-error-event.d.ts.map +1 -0
- package/dist/adapters/sse-error-event.js +11 -0
- package/dist/adapters/sse-error-event.js.map +1 -0
- package/dist/agent-card.d.ts.map +1 -1
- package/dist/agent-card.js +15 -2
- package/dist/agent-card.js.map +1 -1
- package/dist/client.d.ts +24 -4
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +163 -83
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +3 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/json-rpc.d.ts +2 -2
- package/dist/json-rpc.d.ts.map +1 -1
- package/dist/json-rpc.js +11 -7
- package/dist/json-rpc.js.map +1 -1
- package/dist/push-notifications.d.ts +8 -1
- package/dist/push-notifications.d.ts.map +1 -1
- package/dist/push-notifications.js +178 -25
- package/dist/push-notifications.js.map +1 -1
- package/dist/redis-task-store.d.ts +1 -0
- package/dist/redis-task-store.d.ts.map +1 -1
- package/dist/redis-task-store.js +44 -4
- package/dist/redis-task-store.js.map +1 -1
- package/dist/server.d.ts +19 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +287 -135
- package/dist/server.js.map +1 -1
- package/dist/task-manager.d.ts +27 -2
- package/dist/task-manager.d.ts.map +1 -1
- package/dist/task-manager.js +126 -36
- package/dist/task-manager.js.map +1 -1
- package/dist/task-store.d.ts +10 -0
- package/dist/task-store.d.ts.map +1 -1
- package/dist/task-store.js +28 -0
- package/dist/task-store.js.map +1 -1
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -1
- package/package.json +14 -14
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Cogitator Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -14,7 +14,8 @@ pnpm add @cogitator-ai/a2a
|
|
|
14
14
|
- **A2AClient** - Connect to remote A2A agents with discovery and streaming
|
|
15
15
|
- **asTool() Bridge** - Wrap remote A2A agents as local Cogitator tools
|
|
16
16
|
- **Agent Card** - Auto-generate A2A Agent Cards from agent metadata
|
|
17
|
-
- **Task Management** - Full task lifecycle (working, completed, failed, canceled)
|
|
17
|
+
- **Task Management** - Full task lifecycle (working, input-required, completed, failed, canceled)
|
|
18
|
+
- **Authentication** - Bearer / API-key auth enforced by every framework adapter and advertised on the Agent Card
|
|
18
19
|
- **Multi-Turn Conversations** - Stateful conversations with contextId and continueTask
|
|
19
20
|
- **SSE Streaming** - Real-time streaming with token-level events
|
|
20
21
|
- **Push Notifications** - Webhook-based task event notifications
|
|
@@ -102,6 +103,54 @@ const result = await cogitator.run(orchestrator, {
|
|
|
102
103
|
});
|
|
103
104
|
```
|
|
104
105
|
|
|
106
|
+
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.
|
|
107
|
+
|
|
108
|
+
## Authentication
|
|
109
|
+
|
|
110
|
+
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`.
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
const a2aServer = new A2AServer({
|
|
114
|
+
agents: { researcher },
|
|
115
|
+
cogitator,
|
|
116
|
+
auth: {
|
|
117
|
+
type: 'bearer', // or 'apiKey' (header: 'x-api-key' unless headerName is set)
|
|
118
|
+
validate: async (token) => token === process.env.A2A_TOKEN,
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const client = new A2AClient('https://remote-agent.example.com', {
|
|
123
|
+
headers: { Authorization: `Bearer ${process.env.A2A_TOKEN}` },
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Custom integrations can call `a2aServer.getAuthToken((name) => headers.get(name))` and pass the result to `handleJsonRpc(body, token)` / `handleJsonRpcStream(body, token, signal)`.
|
|
128
|
+
|
|
129
|
+
## Send Configuration
|
|
130
|
+
|
|
131
|
+
`sendMessage(message, configuration)` supports the A2A `SendMessageConfiguration`:
|
|
132
|
+
|
|
133
|
+
| Option | Effect |
|
|
134
|
+
| ------------------------ | ------------------------------------------------------------------------------ |
|
|
135
|
+
| `blocking: false` | Return the task immediately in `working` state; poll `getTask` or use webhooks |
|
|
136
|
+
| `historyLength` | Return only the last N history messages (`0` = none); also on `getTask` |
|
|
137
|
+
| `acceptedOutputModes` | Only return artifacts with these MIME types |
|
|
138
|
+
| `timeout` | Maximum agent run time in ms |
|
|
139
|
+
| `pushNotificationConfig` | Register a webhook before the task starts executing |
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
const task = await client.sendMessage(
|
|
143
|
+
{ role: 'user', parts: [{ type: 'text', text: 'Long running analysis' }] },
|
|
144
|
+
{
|
|
145
|
+
blocking: false,
|
|
146
|
+
pushNotificationConfig: { webhookUrl: 'https://my-app.com/webhooks/a2a' },
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
// task.status.state === 'working'; completion arrives at the webhook
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Client calls also accept request options: `client.sendMessage(message, config, { signal, timeout })`.
|
|
153
|
+
|
|
105
154
|
## Multi-Turn Conversations
|
|
106
155
|
|
|
107
156
|
Continue tasks within the same conversation context using `contextId` and `continueTask`:
|
|
@@ -122,7 +171,7 @@ const updated = await client.continueTask(task.id, 'Now compare it with classica
|
|
|
122
171
|
console.log(task.contextId === updated.contextId); // true
|
|
123
172
|
```
|
|
124
173
|
|
|
125
|
-
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
|
|
174
|
+
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.
|
|
126
175
|
|
|
127
176
|
## Listing Tasks
|
|
128
177
|
|
|
@@ -172,6 +221,8 @@ for await (const event of client.sendMessageStream({
|
|
|
172
221
|
|
|
173
222
|
The server emits `TokenStreamEvent` for each token generated by the LLM via the `onToken` callback, giving clients real-time character-by-character output.
|
|
174
223
|
|
|
224
|
+
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.
|
|
225
|
+
|
|
175
226
|
## RedisTaskStore
|
|
176
227
|
|
|
177
228
|
For production deployments, use `RedisTaskStore` instead of the default in-memory store:
|
|
@@ -193,7 +244,9 @@ const a2aServer = new A2AServer({
|
|
|
193
244
|
});
|
|
194
245
|
```
|
|
195
246
|
|
|
196
|
-
`RedisTaskStore` implements the full `TaskStore` interface including `list()` with filtering by `contextId`, `state`, pagination, and timestamp-based sorting.
|
|
247
|
+
`RedisTaskStore` implements the full `TaskStore` interface including `list()` with filtering by `contextId`, `state`, pagination, and timestamp-based sorting. Updates are atomic when the client supports `eval` (Lua).
|
|
248
|
+
|
|
249
|
+
`InMemoryTaskStore` keeps at most `maxSize` tasks (default 10 000) and evicts the oldest finished tasks first.
|
|
197
250
|
|
|
198
251
|
## Push Notifications
|
|
199
252
|
|
|
@@ -236,7 +289,9 @@ const a2aServer = new A2AServer({
|
|
|
236
289
|
});
|
|
237
290
|
```
|
|
238
291
|
|
|
239
|
-
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`.
|
|
292
|
+
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`. Configs can only be created for existing tasks (unknown ids return `TaskNotFound`); use `configuration.pushNotificationConfig` with `blocking: false` to subscribe before execution starts.
|
|
293
|
+
|
|
294
|
+
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.
|
|
240
295
|
|
|
241
296
|
## Agent Card Signing
|
|
242
297
|
|
|
@@ -307,9 +362,9 @@ The `agent/extendedCard` method is only available when `extendedCardGenerator` i
|
|
|
307
362
|
import { a2aExpress } from '@cogitator-ai/a2a/express';
|
|
308
363
|
app.use(a2aExpress(server));
|
|
309
364
|
|
|
310
|
-
// Hono
|
|
365
|
+
// Hono (routes are /.well-known/agent.json and /a2a)
|
|
311
366
|
import { a2aHono } from '@cogitator-ai/a2a/hono';
|
|
312
|
-
app.route('/
|
|
367
|
+
app.route('/', a2aHono(server));
|
|
313
368
|
|
|
314
369
|
// Fastify
|
|
315
370
|
import { a2aFastify } from '@cogitator-ai/a2a/fastify';
|
|
@@ -324,11 +379,13 @@ import { a2aNext } from '@cogitator-ai/a2a/next';
|
|
|
324
379
|
export const { GET, POST } = a2aNext(server);
|
|
325
380
|
```
|
|
326
381
|
|
|
382
|
+
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`.
|
|
383
|
+
|
|
327
384
|
## A2A Protocol
|
|
328
385
|
|
|
329
386
|
| Method | Description |
|
|
330
387
|
| ------------------------------- | ----------------------------------------- |
|
|
331
|
-
| `message/send` | Send a message
|
|
388
|
+
| `message/send` | Send a message (blocking or background) |
|
|
332
389
|
| `message/stream` | Send with SSE streaming (incl. tokens) |
|
|
333
390
|
| `tasks/get` | Retrieve task by ID |
|
|
334
391
|
| `tasks/cancel` | Cancel a running task |
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,
|
|
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"}
|
package/dist/adapters/express.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Router, json } from 'express';
|
|
2
2
|
import { createErrorResponse } from '../json-rpc.js';
|
|
3
3
|
import * as errors from '../errors.js';
|
|
4
|
+
import { isStreamRequest, pipeJsonRpcStream, SSE_HEADERS } from './shared.js';
|
|
4
5
|
export function a2aExpress(server) {
|
|
5
6
|
const router = Router();
|
|
6
7
|
router.get('/.well-known/agent.json', (_req, res) => {
|
|
@@ -9,44 +10,43 @@ export function a2aExpress(server) {
|
|
|
9
10
|
});
|
|
10
11
|
router.post('/a2a', json(), async (req, res) => {
|
|
11
12
|
const contentType = req.headers['content-type'];
|
|
12
|
-
if (contentType && !contentType.
|
|
13
|
+
if (contentType && !contentType.startsWith('application/json')) {
|
|
13
14
|
res.json(createErrorResponse(null, errors.contentTypeNotSupported(contentType)));
|
|
14
15
|
return;
|
|
15
16
|
}
|
|
16
17
|
const body = req.body;
|
|
17
|
-
const
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
const authToken = server.getAuthToken((name) => req.get(name));
|
|
19
|
+
if (isStreamRequest(body)) {
|
|
20
|
+
const controller = new AbortController();
|
|
21
|
+
res.on('close', () => controller.abort());
|
|
22
|
+
res.writeHead(200, SSE_HEADERS);
|
|
23
|
+
await pipeJsonRpcStream(server, body, authToken, controller.signal, (frame) => {
|
|
24
|
+
if (!res.writableEnded && !res.destroyed)
|
|
25
|
+
res.write(frame);
|
|
23
26
|
});
|
|
24
|
-
try {
|
|
25
|
-
for await (const event of server.handleJsonRpcStream(body)) {
|
|
26
|
-
if (res.writableEnded)
|
|
27
|
-
break;
|
|
28
|
-
res.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
29
|
-
}
|
|
30
|
-
if (!res.writableEnded)
|
|
31
|
-
res.write('data: [DONE]\n\n');
|
|
32
|
-
}
|
|
33
|
-
catch (error) {
|
|
34
|
-
if (!res.writableEnded) {
|
|
35
|
-
res.write(`data: ${JSON.stringify({ error: String(error) })}\n\n`);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
27
|
if (!res.writableEnded)
|
|
39
28
|
res.end();
|
|
40
29
|
return;
|
|
41
30
|
}
|
|
42
31
|
try {
|
|
43
|
-
const response = await server.handleJsonRpc(body);
|
|
32
|
+
const response = await server.handleJsonRpc(body, authToken);
|
|
33
|
+
if (response === null) {
|
|
34
|
+
res.status(204).end();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
44
37
|
res.json(response);
|
|
45
38
|
}
|
|
46
39
|
catch (error) {
|
|
47
40
|
res.json(createErrorResponse(null, errors.internalError(String(error))));
|
|
48
41
|
}
|
|
49
42
|
});
|
|
43
|
+
router.use((err, _req, res, next) => {
|
|
44
|
+
if (err instanceof SyntaxError) {
|
|
45
|
+
res.json(createErrorResponse(null, errors.parseError('Invalid JSON body')));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
next(err);
|
|
49
|
+
});
|
|
50
50
|
return router;
|
|
51
51
|
}
|
|
52
52
|
//# sourceMappingURL=express.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,IAAI,
|
|
1
|
+
{"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fastify.d.ts","sourceRoot":"","sources":["../../src/adapters/fastify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,kBAAkB,EAAE,MAAM,SAAS,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"fastify.d.ts","sourceRoot":"","sources":["../../src/adapters/fastify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,kBAAkB,EAAE,MAAM,SAAS,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAK9C,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,GAAG,kBAAkB,CAkDhE"}
|
package/dist/adapters/fastify.js
CHANGED
|
@@ -1,45 +1,44 @@
|
|
|
1
1
|
import { createErrorResponse } from '../json-rpc.js';
|
|
2
2
|
import * as errors from '../errors.js';
|
|
3
|
+
import { firstHeader, isStreamRequest, pipeJsonRpcStream, SSE_HEADERS } from './shared.js';
|
|
3
4
|
export function a2aFastify(server) {
|
|
4
5
|
return async (fastify) => {
|
|
6
|
+
fastify.setErrorHandler((error, _request, reply) => {
|
|
7
|
+
if (error instanceof SyntaxError || error.statusCode === 400) {
|
|
8
|
+
return reply.send(createErrorResponse(null, errors.parseError('Invalid JSON body')));
|
|
9
|
+
}
|
|
10
|
+
return reply.send(createErrorResponse(null, errors.internalError(error.message)));
|
|
11
|
+
});
|
|
5
12
|
fastify.get('/.well-known/agent.json', async (_request, reply) => {
|
|
6
13
|
const cards = server.getAgentCards();
|
|
7
14
|
return reply.send(cards.length === 1 ? cards[0] : cards);
|
|
8
15
|
});
|
|
9
16
|
fastify.post('/a2a', async (request, reply) => {
|
|
10
17
|
const contentType = request.headers['content-type'];
|
|
11
|
-
if (contentType && !contentType.
|
|
18
|
+
if (contentType && !contentType.startsWith('application/json')) {
|
|
12
19
|
return reply.send(createErrorResponse(null, errors.contentTypeNotSupported(contentType)));
|
|
13
20
|
}
|
|
14
21
|
const body = request.body;
|
|
15
|
-
const
|
|
16
|
-
if (
|
|
22
|
+
const authToken = server.getAuthToken((name) => firstHeader(request.headers[name.toLowerCase()]));
|
|
23
|
+
if (isStreamRequest(body)) {
|
|
17
24
|
void reply.hijack();
|
|
18
|
-
reply.raw
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
const raw = reply.raw;
|
|
26
|
+
const controller = new AbortController();
|
|
27
|
+
raw.on('close', () => controller.abort());
|
|
28
|
+
raw.writeHead(200, SSE_HEADERS);
|
|
29
|
+
await pipeJsonRpcStream(server, body, authToken, controller.signal, (frame) => {
|
|
30
|
+
if (!raw.writableEnded && !raw.destroyed)
|
|
31
|
+
raw.write(frame);
|
|
22
32
|
});
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (reply.raw.writableEnded)
|
|
26
|
-
break;
|
|
27
|
-
reply.raw.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
28
|
-
}
|
|
29
|
-
if (!reply.raw.writableEnded)
|
|
30
|
-
reply.raw.write('data: [DONE]\n\n');
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
if (!reply.raw.writableEnded) {
|
|
34
|
-
reply.raw.write(`data: ${JSON.stringify({ error: String(error) })}\n\n`);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
if (!reply.raw.writableEnded)
|
|
38
|
-
reply.raw.end();
|
|
33
|
+
if (!raw.writableEnded)
|
|
34
|
+
raw.end();
|
|
39
35
|
return;
|
|
40
36
|
}
|
|
41
37
|
try {
|
|
42
|
-
const response = await server.handleJsonRpc(body);
|
|
38
|
+
const response = await server.handleJsonRpc(body, authToken);
|
|
39
|
+
if (response === null) {
|
|
40
|
+
return reply.code(204).send();
|
|
41
|
+
}
|
|
43
42
|
return reply.send(response);
|
|
44
43
|
}
|
|
45
44
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fastify.js","sourceRoot":"","sources":["../../src/adapters/fastify.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,MAAM,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"fastify.js","sourceRoot":"","sources":["../../src/adapters/fastify.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../../src/adapters/hono.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../../src/adapters/hono.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAM9C,wBAAgB,OAAO,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA6D/C"}
|
package/dist/adapters/hono.js
CHANGED
|
@@ -2,6 +2,8 @@ import { Hono } from 'hono';
|
|
|
2
2
|
import { streamSSE } from 'hono/streaming';
|
|
3
3
|
import { createErrorResponse } from '../json-rpc.js';
|
|
4
4
|
import * as errors from '../errors.js';
|
|
5
|
+
import { buildSseErrorEvent } from './sse-error-event.js';
|
|
6
|
+
import { isStreamRequest } from './shared.js';
|
|
5
7
|
export function a2aHono(server) {
|
|
6
8
|
const app = new Hono();
|
|
7
9
|
app.get('/.well-known/agent.json', (c) => {
|
|
@@ -10,7 +12,7 @@ export function a2aHono(server) {
|
|
|
10
12
|
});
|
|
11
13
|
app.post('/a2a', async (c) => {
|
|
12
14
|
const contentType = c.req.header('content-type');
|
|
13
|
-
if (contentType && !contentType.
|
|
15
|
+
if (contentType && !contentType.startsWith('application/json')) {
|
|
14
16
|
return c.json(createErrorResponse(null, errors.contentTypeNotSupported(contentType)));
|
|
15
17
|
}
|
|
16
18
|
let body;
|
|
@@ -20,23 +22,38 @@ export function a2aHono(server) {
|
|
|
20
22
|
catch {
|
|
21
23
|
return c.json(createErrorResponse(null, errors.parseError('Invalid JSON body')));
|
|
22
24
|
}
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
const authToken = server.getAuthToken((name) => c.req.header(name));
|
|
26
|
+
if (isStreamRequest(body)) {
|
|
27
|
+
c.header('X-Accel-Buffering', 'no');
|
|
26
28
|
return streamSSE(c, async (stream) => {
|
|
29
|
+
const controller = new AbortController();
|
|
30
|
+
stream.onAbort(() => controller.abort());
|
|
27
31
|
try {
|
|
28
|
-
for await (const event of server.handleJsonRpcStream(body)) {
|
|
32
|
+
for await (const event of server.handleJsonRpcStream(body, authToken, controller.signal)) {
|
|
33
|
+
if (controller.signal.aborted)
|
|
34
|
+
return;
|
|
29
35
|
await stream.writeSSE({ data: JSON.stringify(event) });
|
|
30
36
|
}
|
|
31
|
-
|
|
37
|
+
if (!controller.signal.aborted)
|
|
38
|
+
await stream.writeSSE({ data: '[DONE]' });
|
|
32
39
|
}
|
|
33
40
|
catch (error) {
|
|
34
|
-
|
|
41
|
+
if (controller.signal.aborted)
|
|
42
|
+
return;
|
|
43
|
+
try {
|
|
44
|
+
await stream.writeSSE({ data: JSON.stringify(buildSseErrorEvent(error)) });
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
35
49
|
}
|
|
36
50
|
});
|
|
37
51
|
}
|
|
38
52
|
try {
|
|
39
|
-
const response = await server.handleJsonRpc(body);
|
|
53
|
+
const response = await server.handleJsonRpc(body, authToken);
|
|
54
|
+
if (response === null) {
|
|
55
|
+
return c.body(null, 204);
|
|
56
|
+
}
|
|
40
57
|
return c.json(response);
|
|
41
58
|
}
|
|
42
59
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hono.js","sourceRoot":"","sources":["../../src/adapters/hono.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,MAAM,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"hono.js","sourceRoot":"","sources":["../../src/adapters/hono.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"koa.d.ts","sourceRoot":"","sources":["../../src/adapters/koa.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAW,MAAM,KAAK,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"koa.d.ts","sourceRoot":"","sources":["../../src/adapters/koa.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAW,MAAM,KAAK,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAK9C,wBAAgB,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,UAAU,CAsDpD"}
|
package/dist/adapters/koa.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createErrorResponse } from '../json-rpc.js';
|
|
2
2
|
import * as errors from '../errors.js';
|
|
3
|
+
import { isStreamRequest, pipeJsonRpcStream, SSE_HEADERS } from './shared.js';
|
|
3
4
|
export function a2aKoa(server) {
|
|
4
5
|
return async (ctx, next) => {
|
|
5
6
|
if (ctx.path === '/.well-known/agent.json' && ctx.method === 'GET') {
|
|
@@ -9,44 +10,36 @@ export function a2aKoa(server) {
|
|
|
9
10
|
}
|
|
10
11
|
if (ctx.path === '/a2a' && ctx.method === 'POST') {
|
|
11
12
|
const contentType = ctx.headers['content-type'];
|
|
12
|
-
if (contentType && !contentType.
|
|
13
|
+
if (contentType && !contentType.startsWith('application/json')) {
|
|
13
14
|
ctx.body = createErrorResponse(null, errors.contentTypeNotSupported(contentType));
|
|
14
15
|
return;
|
|
15
16
|
}
|
|
16
17
|
const body = ctx.request.body;
|
|
17
|
-
if (
|
|
18
|
+
if (body === undefined) {
|
|
18
19
|
ctx.body = createErrorResponse(null, errors.parseError('Request body not parsed. Ensure body-parsing middleware is applied.'));
|
|
19
20
|
return;
|
|
20
21
|
}
|
|
21
|
-
const
|
|
22
|
-
if (
|
|
22
|
+
const authToken = server.getAuthToken((name) => ctx.get(name) || undefined);
|
|
23
|
+
if (isStreamRequest(body)) {
|
|
23
24
|
ctx.respond = false;
|
|
24
25
|
const res = ctx.res;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const controller = new AbortController();
|
|
27
|
+
res.on('close', () => controller.abort());
|
|
28
|
+
res.writeHead(200, SSE_HEADERS);
|
|
29
|
+
await pipeJsonRpcStream(server, body, authToken, controller.signal, (frame) => {
|
|
30
|
+
if (!res.writableEnded && !res.destroyed)
|
|
31
|
+
res.write(frame);
|
|
29
32
|
});
|
|
30
|
-
try {
|
|
31
|
-
for await (const event of server.handleJsonRpcStream(body)) {
|
|
32
|
-
if (res.writableEnded)
|
|
33
|
-
break;
|
|
34
|
-
res.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
35
|
-
}
|
|
36
|
-
if (!res.writableEnded)
|
|
37
|
-
res.write('data: [DONE]\n\n');
|
|
38
|
-
}
|
|
39
|
-
catch (error) {
|
|
40
|
-
if (!res.writableEnded) {
|
|
41
|
-
res.write(`data: ${JSON.stringify({ error: String(error) })}\n\n`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
33
|
if (!res.writableEnded)
|
|
45
34
|
res.end();
|
|
46
35
|
return;
|
|
47
36
|
}
|
|
48
37
|
try {
|
|
49
|
-
const response = await server.handleJsonRpc(body);
|
|
38
|
+
const response = await server.handleJsonRpc(body, authToken);
|
|
39
|
+
if (response === null) {
|
|
40
|
+
ctx.status = 204;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
50
43
|
ctx.body = response;
|
|
51
44
|
}
|
|
52
45
|
catch (error) {
|
package/dist/adapters/koa.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"koa.js","sourceRoot":"","sources":["../../src/adapters/koa.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,MAAM,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"koa.js","sourceRoot":"","sources":["../../src/adapters/koa.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"next.d.ts","sourceRoot":"","sources":["../../src/adapters/next.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"next.d.ts","sourceRoot":"","sources":["../../src/adapters/next.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAK9C,wBAAgB,OAAO,CAAC,MAAM,EAAE,SAAS;WAExB,OAAO,CAAC,QAAQ,CAAC;kBAKV,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;EAsDlD"}
|
package/dist/adapters/next.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createErrorResponse } from '../json-rpc.js';
|
|
2
2
|
import * as errors from '../errors.js';
|
|
3
|
+
import { isStreamRequest, pipeJsonRpcStream, SSE_HEADERS } from './shared.js';
|
|
3
4
|
export function a2aNext(server) {
|
|
4
5
|
return {
|
|
5
6
|
async GET() {
|
|
@@ -8,7 +9,7 @@ export function a2aNext(server) {
|
|
|
8
9
|
},
|
|
9
10
|
async POST(request) {
|
|
10
11
|
const contentType = request.headers.get('content-type');
|
|
11
|
-
if (contentType && !contentType.
|
|
12
|
+
if (contentType && !contentType.startsWith('application/json')) {
|
|
12
13
|
return Response.json(createErrorResponse(null, errors.contentTypeNotSupported(contentType)));
|
|
13
14
|
}
|
|
14
15
|
let body;
|
|
@@ -18,34 +19,36 @@ export function a2aNext(server) {
|
|
|
18
19
|
catch {
|
|
19
20
|
return Response.json(createErrorResponse(null, errors.parseError('Invalid JSON body')));
|
|
20
21
|
}
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
if (isStreaming) {
|
|
22
|
+
const authToken = server.getAuthToken((name) => request.headers.get(name));
|
|
23
|
+
if (isStreamRequest(body)) {
|
|
24
24
|
const encoder = new TextEncoder();
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
const abort = () => controller.abort();
|
|
27
|
+
request.signal?.addEventListener('abort', abort, { once: true });
|
|
25
28
|
const stream = new ReadableStream({
|
|
26
|
-
async start(
|
|
29
|
+
async start(streamController) {
|
|
30
|
+
await pipeJsonRpcStream(server, body, authToken, controller.signal, (frame) => {
|
|
31
|
+
streamController.enqueue(encoder.encode(frame));
|
|
32
|
+
});
|
|
33
|
+
request.signal?.removeEventListener('abort', abort);
|
|
27
34
|
try {
|
|
28
|
-
|
|
29
|
-
controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
|
|
30
|
-
}
|
|
31
|
-
controller.enqueue(encoder.encode('data: [DONE]\n\n'));
|
|
35
|
+
streamController.close();
|
|
32
36
|
}
|
|
33
|
-
catch
|
|
34
|
-
|
|
37
|
+
catch {
|
|
38
|
+
return;
|
|
35
39
|
}
|
|
36
|
-
controller.close();
|
|
37
40
|
},
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
headers: {
|
|
41
|
-
'Content-Type': 'text/event-stream',
|
|
42
|
-
'Cache-Control': 'no-cache',
|
|
43
|
-
Connection: 'keep-alive',
|
|
41
|
+
cancel() {
|
|
42
|
+
controller.abort();
|
|
44
43
|
},
|
|
45
44
|
});
|
|
45
|
+
return new Response(stream, { headers: SSE_HEADERS });
|
|
46
46
|
}
|
|
47
47
|
try {
|
|
48
|
-
const response = await server.handleJsonRpc(body);
|
|
48
|
+
const response = await server.handleJsonRpc(body, authToken);
|
|
49
|
+
if (response === null) {
|
|
50
|
+
return new Response(null, { status: 204 });
|
|
51
|
+
}
|
|
49
52
|
return Response.json(response);
|
|
50
53
|
}
|
|
51
54
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"next.js","sourceRoot":"","sources":["../../src/adapters/next.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,MAAM,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"next.js","sourceRoot":"","sources":["../../src/adapters/next.ts"],"names":[],"mappings":"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"}
|