@zhin.js/a2a 2.0.5 → 3.0.1
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/CHANGELOG.md +18 -0
- package/README.md +11 -5
- package/lib/http-handlers.d.ts.map +1 -1
- package/lib/http-handlers.js +34 -14
- package/lib/http-handlers.js.map +1 -1
- package/lib/index.d.ts +9 -22
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +5 -134
- package/lib/index.js.map +1 -1
- package/lib/runtime.d.ts +2 -2
- package/lib/runtime.d.ts.map +1 -1
- package/package.json +8 -7
- package/src/http-handlers.ts +38 -12
- package/src/index.ts +10 -193
- package/src/runtime.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @zhin.js/a2a
|
|
2
2
|
|
|
3
|
+
## 3.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- @zhin.js/agent@1.1.1
|
|
8
|
+
- @zhin.js/core@1.5.1
|
|
9
|
+
- zhin.js@6.0.1
|
|
10
|
+
|
|
11
|
+
## 3.0.0
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [4fbff5d]
|
|
16
|
+
- Updated dependencies [5b94d9c]
|
|
17
|
+
- @zhin.js/core@1.5.0
|
|
18
|
+
- @zhin.js/agent@1.1.0
|
|
19
|
+
- zhin.js@6.0.0
|
|
20
|
+
|
|
3
21
|
## 2.0.5
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @zhin.js/a2a
|
|
2
2
|
|
|
3
|
-
A2A v1.0
|
|
3
|
+
A2A v1.0 Runtime Host for Zhin.js — exposes one [Agent Card](https://a2a-protocol.org/v1.0.0/specification) per `ai.agents[]` entry.
|
|
4
4
|
|
|
5
5
|
## Routes
|
|
6
6
|
|
|
@@ -13,13 +13,14 @@ A2A v1.0 server plugin for Zhin.js — exposes one [Agent Card](https://a2a-prot
|
|
|
13
13
|
## Setup
|
|
14
14
|
|
|
15
15
|
```yaml
|
|
16
|
-
plugins:
|
|
17
|
-
- "@zhin.js/a2a"
|
|
18
|
-
|
|
19
16
|
http:
|
|
20
17
|
token: ${HTTP_TOKEN}
|
|
21
18
|
publicUrl: https://bot.example.com # recommended behind reverse proxy
|
|
22
19
|
|
|
20
|
+
a2a:
|
|
21
|
+
enabled: true
|
|
22
|
+
path: /a2a
|
|
23
|
+
|
|
23
24
|
ai:
|
|
24
25
|
agents:
|
|
25
26
|
zhin:
|
|
@@ -27,7 +28,12 @@ ai:
|
|
|
27
28
|
model: qwen3:8b
|
|
28
29
|
```
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
`@zhin.js/cli` is the composition root: it mounts the A2A Runtime Host when
|
|
32
|
+
`a2a.enabled` is enabled. Do not add `@zhin.js/a2a` to `plugins`; importing the
|
|
33
|
+
package is inert and only exposes the Runtime API.
|
|
34
|
+
|
|
35
|
+
Bearer `a2a.token` (or the fallback `http.token`) is required for inbound A2A
|
|
36
|
+
calls. Production requires one of these tokens.
|
|
31
37
|
|
|
32
38
|
## Related
|
|
33
39
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-handlers.d.ts","sourceRoot":"","sources":["../src/http-handlers.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAQjE,OAAO,
|
|
1
|
+
{"version":3,"file":"http-handlers.d.ts","sourceRoot":"","sources":["../src/http-handlers.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAQjE,OAAO,EAUL,KAAK,iBAAiB,EACvB,MAAM,oBAAoB,CAAC;AA0C5B,wBAAsB,eAAe,CACnC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,cAAc,EAAE,iBAAiB,GAChC,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED,wBAAsB,aAAa,CACjC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,cAAc,EAAE,iBAAiB,EACjC,aAAa,CAAC,EAAE,OAAO,GACtB,OAAO,CAAC,IAAI,CAAC,CA2Df;AAED,wBAAsB,UAAU,CAC9B,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,cAAc,EAAE,iBAAiB,EACjC,OAAO,EAAE,MAAM,EACf,aAAa,CAAC,EAAE,OAAO,GACtB,OAAO,CAAC,IAAI,CAAC,CA2Ef;AAoBD,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,OAAO,EACZ,SAAS,EAAE,OAAO,GACjB,OAAO,CAAC,IAAI,CAAC,CAWf"}
|
package/lib/http-handlers.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { A2A_CONTENT_TYPE, A2A_VERSION_HEADER, Extensions, HTTP_EXTENSION_HEADER, } from '@a2a-js/sdk';
|
|
3
|
-
import { JsonRpcTransportHandler, ServerCallContext, UnauthenticatedUser, validateVersion, } from '@a2a-js/sdk/server';
|
|
3
|
+
import { JsonRpcTransportHandler, InvalidAgentResponseError, RequestMalformedError, ServerCallContext, TaskNotFoundError, UnauthenticatedUser, UnsupportedOperationError, validateVersion, VersionNotSupportedError, } from '@a2a-js/sdk/server';
|
|
4
|
+
import { HttpBodyError, readJsonBody } from '@zhin.js/host-http-contract';
|
|
4
5
|
import { RestTransportHandler } from './rest-transport-handler.js';
|
|
5
6
|
const SSE_HEADERS = {
|
|
6
7
|
'Content-Type': 'text/event-stream',
|
|
@@ -13,15 +14,8 @@ function formatSSEEvent(data) {
|
|
|
13
14
|
function formatSSEErrorEvent(data) {
|
|
14
15
|
return `event: error\ndata: ${JSON.stringify(data)}\n\n`;
|
|
15
16
|
}
|
|
16
|
-
async function
|
|
17
|
-
|
|
18
|
-
for await (const chunk of req) {
|
|
19
|
-
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
|
|
20
|
-
}
|
|
21
|
-
const raw = Buffer.concat(chunks).toString('utf8');
|
|
22
|
-
if (!raw.trim())
|
|
23
|
-
return {};
|
|
24
|
-
return JSON.parse(raw);
|
|
17
|
+
async function readA2aJsonBody(req) {
|
|
18
|
+
return (await readJsonBody(req)) ?? {};
|
|
25
19
|
}
|
|
26
20
|
function buildServerContext(req) {
|
|
27
21
|
return new ServerCallContext({
|
|
@@ -67,7 +61,7 @@ export async function handleJsonRpc(req, res, requestHandler, preParsedBody) {
|
|
|
67
61
|
}
|
|
68
62
|
const transport = new JsonRpcTransportHandler(requestHandler);
|
|
69
63
|
try {
|
|
70
|
-
const body = preParsedBody !== undefined ? preParsedBody : await
|
|
64
|
+
const body = preParsedBody !== undefined ? preParsedBody : await readA2aJsonBody(req);
|
|
71
65
|
const context = buildServerContext(req);
|
|
72
66
|
const agentCard = await requestHandler.getAgentCard();
|
|
73
67
|
validateVersion(context.requestedVersion, agentCard, 'JSONRPC');
|
|
@@ -98,6 +92,14 @@ export async function handleJsonRpc(req, res, requestHandler, preParsedBody) {
|
|
|
98
92
|
sendJson(res, 200, rpcResponseOrStream);
|
|
99
93
|
}
|
|
100
94
|
catch (err) {
|
|
95
|
+
if (err instanceof HttpBodyError) {
|
|
96
|
+
sendJson(res, err.statusCode, {
|
|
97
|
+
jsonrpc: '2.0',
|
|
98
|
+
id: null,
|
|
99
|
+
error: { code: -32700, message: 'Parse error' },
|
|
100
|
+
});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
101
103
|
const errorResponse = {
|
|
102
104
|
jsonrpc: '2.0',
|
|
103
105
|
id: null,
|
|
@@ -119,13 +121,13 @@ export async function handleRest(req, res, requestHandler, subPath, preParsedBod
|
|
|
119
121
|
return;
|
|
120
122
|
}
|
|
121
123
|
if (method === 'POST' && path === 'v1/message:send') {
|
|
122
|
-
const params = preParsedBody !== undefined ? preParsedBody : await
|
|
124
|
+
const params = preParsedBody !== undefined ? preParsedBody : await readA2aJsonBody(req);
|
|
123
125
|
const result = await transport.sendMessage(params, context);
|
|
124
126
|
sendJson(res, 200, result, { 'Content-Type': A2A_CONTENT_TYPE });
|
|
125
127
|
return;
|
|
126
128
|
}
|
|
127
129
|
if (method === 'POST' && path === 'v1/message:stream') {
|
|
128
|
-
const params = preParsedBody !== undefined ? preParsedBody : await
|
|
130
|
+
const params = preParsedBody !== undefined ? preParsedBody : await readA2aJsonBody(req);
|
|
129
131
|
const stream = await transport.sendMessageStream(params, context);
|
|
130
132
|
for (const [key, value] of Object.entries(SSE_HEADERS)) {
|
|
131
133
|
res.setHeader(key, value);
|
|
@@ -173,8 +175,26 @@ export async function handleRest(req, res, requestHandler, subPath, preParsedBod
|
|
|
173
175
|
sendJson(res, 404, { error: 'Not found' });
|
|
174
176
|
}
|
|
175
177
|
catch (err) {
|
|
176
|
-
sendJson(res,
|
|
178
|
+
sendJson(res, restErrorStatus(err), { error: err instanceof Error ? err.message : String(err) });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* HTTP+JSON has HTTP status semantics. Keep its transport errors distinct from
|
|
183
|
+
* request validation, missing tasks, and failures inside an agent executor.
|
|
184
|
+
*/
|
|
185
|
+
function restErrorStatus(error) {
|
|
186
|
+
if (error instanceof HttpBodyError)
|
|
187
|
+
return error.statusCode;
|
|
188
|
+
if (error instanceof TaskNotFoundError)
|
|
189
|
+
return 404;
|
|
190
|
+
if (error instanceof RequestMalformedError
|
|
191
|
+
|| error instanceof UnsupportedOperationError
|
|
192
|
+
|| error instanceof VersionNotSupportedError) {
|
|
193
|
+
return 400;
|
|
177
194
|
}
|
|
195
|
+
if (error instanceof InvalidAgentResponseError)
|
|
196
|
+
return 500;
|
|
197
|
+
return 500;
|
|
178
198
|
}
|
|
179
199
|
export async function handleRestStreamError(res, err, requestId) {
|
|
180
200
|
if (!res.headersSent) {
|
package/lib/http-handlers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-handlers.js","sourceRoot":"","sources":["../src/http-handlers.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,UAAU,EACV,qBAAqB,GACtB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,
|
|
1
|
+
{"version":3,"file":"http-handlers.js","sourceRoot":"","sources":["../src/http-handlers.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,UAAU,EACV,qBAAqB,GACtB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,uBAAuB,EACvB,yBAAyB,EACzB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,yBAAyB,EACzB,eAAe,EACf,wBAAwB,GAEzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAEnE,MAAM,WAAW,GAA2B;IAC1C,cAAc,EAAE,mBAAmB;IACnC,eAAe,EAAE,UAAU;IAC3B,UAAU,EAAE,YAAY;CACzB,CAAC;AAEF,SAAS,cAAc,CAAC,IAAa;IACnC,OAAO,SAAS,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC;AAC7C,CAAC;AAED,SAAS,mBAAmB,CAAC,IAAa;IACxC,OAAO,uBAAuB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC;AAC3D,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,GAAoB;IACjD,OAAO,CAAC,MAAM,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AACzC,CAAC;AAED,SAAS,kBAAkB,CAAC,GAAoB;IAC9C,OAAO,IAAI,iBAAiB,CAAC;QAC3B,mBAAmB,EAAE,UAAU,CAAC,qBAAqB,CACnD,GAAG,CAAC,OAAO,CAAC,qBAAqB,CAAC,WAAW,EAAE,CAAuB,CACvE;QACD,IAAI,EAAE,IAAI,mBAAmB,EAAE;QAC/B,gBAAgB,EAAE,GAAG,CAAC,OAAO,CAAC,kBAAkB,CAAC,WAAW,EAAE,CAAuB;KACtF,CAAC,CAAC;AACL,CAAC;AAED,SAAS,QAAQ,CAAC,GAAmB,EAAE,MAAc,EAAE,IAAa,EAAE,YAAqC;IACzG,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,GAAG,YAAY,EAAE,CAAC,CAAC;IAC/E,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;AAChC,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;IAC/B,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC1E,OAAO,MAAM,IAAI,GAAG,CAAC;AACvB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,GAAoB,EACpB,GAAmB,EACnB,cAAiC;IAEjC,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;QACzB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC;QACpD,OAAO;IACT,CAAC;IACD,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,YAAY,EAAE,CAAC;QACtD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QACvC,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QAC/B,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC5B,GAAG,CAAC,SAAS,CAAC,eAAe,EAAE,sBAAsB,CAAC,CAAC;QACvD,IAAI,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,KAAK,IAAI,EAAE,CAAC;YAC1C,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YACnB,GAAG,CAAC,GAAG,EAAE,CAAC;YACV,OAAO;QACT,CAAC;QACD,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC;IAChC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClF,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,GAAoB,EACpB,GAAmB,EACnB,cAAiC,EACjC,aAAuB;IAEvB,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;QAC1B,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,oBAAoB,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QACnE,OAAO;IACT,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,uBAAuB,CAAC,cAAc,CAAC,CAAC;IAC9D,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,eAAe,CAAC,GAAG,CAAC,CAAC;QACtF,MAAM,OAAO,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;QACxC,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,YAAY,EAAE,CAAC;QACtD,eAAe,CAAC,OAAO,CAAC,gBAAgB,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;QAEhE,MAAM,mBAAmB,GAAG,MAAM,SAAS,CAAC,MAAM,CAChD,IAAwC,EACxC,OAAO,CACR,CAAC;QAEF,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;YAChC,GAAG,CAAC,SAAS,CAAC,qBAAqB,EAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3F,CAAC;QAED,IAAI,mBAAmB,IAAI,OAAQ,mBAA8C,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,UAAU,EAAE,CAAC;YACvH,MAAM,MAAM,GAAG,mBAA+D,CAAC;YAC/E,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC;YAChD,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YAE1C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;gBACvD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAC5B,CAAC;YACD,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;gBACtB,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;YAC/C,CAAC;YACD,SAAS,CAAC;gBACR,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;gBACnC,IAAI,IAAI,CAAC,IAAI;oBAAE,MAAM;gBACrB,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;YACxC,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,aAAa;gBAAE,GAAG,CAAC,GAAG,EAAE,CAAC;YAClC,OAAO;QACT,CAAC;QAED,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,mBAAmB,CAAC,CAAC;IAC1C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,aAAa,EAAE,CAAC;YACjC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,UAAU,EAAE;gBAC5B,OAAO,EAAE,KAAK;gBACd,EAAE,EAAE,IAAI;gBACR,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE;aAChD,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QACD,MAAM,aAAa,GAAG;YACpB,OAAO,EAAE,KAAK;YACd,EAAE,EAAE,IAAI;YACR,KAAK,EAAE,uBAAuB,CAAC,iBAAiB,CAAC,GAAG,CAAC;SACtD,CAAC;QACF,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,aAAa,CAAC,CAAC;IACpC,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,GAAoB,EACpB,GAAmB,EACnB,cAAiC,EACjC,OAAe,EACf,aAAuB;IAEvB,MAAM,SAAS,GAAG,IAAI,oBAAoB,CAAC,cAAc,CAAC,CAAC;IAC3D,MAAM,OAAO,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,KAAK,CAAC;IACnC,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAExC,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,YAAY,EAAE,CAAC;QACtD,eAAe,CAAC,OAAO,CAAC,gBAAgB,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;QAElE,IAAI,MAAM,KAAK,KAAK,IAAI,CAAC,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK,SAAS,CAAC,EAAE,CAAC;YAC5D,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,SAAS,CAAC,YAAY,EAAE,EAAE,EAAE,cAAc,EAAE,gBAAgB,EAAE,CAAC,CAAC;YACzF,OAAO;QACT,CAAC;QAED,IAAI,MAAM,KAAK,MAAM,IAAI,IAAI,KAAK,iBAAiB,EAAE,CAAC;YACpD,MAAM,MAAM,GAAG,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,eAAe,CAAC,GAAG,CAAC,CAAC;YACxF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,WAAW,CAAC,MAAe,EAAE,OAAO,CAAC,CAAC;YACrE,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,cAAc,EAAE,gBAAgB,EAAE,CAAC,CAAC;YACjE,OAAO;QACT,CAAC;QAED,IAAI,MAAM,KAAK,MAAM,IAAI,IAAI,KAAK,mBAAmB,EAAE,CAAC;YACtD,MAAM,MAAM,GAAG,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,eAAe,CAAC,GAAG,CAAC,CAAC;YACxF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,iBAAiB,CAAC,MAAe,EAAE,OAAO,CAAC,CAAC;YAC3E,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;gBACvD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAC5B,CAAC;YACD,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBACjC,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;YACnC,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,aAAa;gBAAE,GAAG,CAAC,GAAG,EAAE,CAAC;YAClC,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;QACnD,IAAI,MAAM,KAAK,KAAK,IAAI,OAAO,EAAE,CAAC;YAChC,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;YAC1B,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;gBACjD,OAAO;YACT,CAAC;YACD,MAAM,aAAa,GAAG,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,gBAAgB,CAAC;gBACvD,CAAC,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,SAAS;gBACrF,CAAC,CAAC,SAAS,CAAC;YACd,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,aAAa,IAAI,SAAS,CAAC,CAAC;YACpF,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,cAAc,EAAE,gBAAgB,EAAE,CAAC,CAAC;YACjE,OAAO;QACT,CAAC;QAED,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;QAC7D,IAAI,MAAM,KAAK,MAAM,IAAI,UAAU,EAAE,CAAC;YACpC,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YAC7B,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;gBACjD,OAAO;YACT,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAC3D,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,cAAc,EAAE,gBAAgB,EAAE,CAAC,CAAC;YACjE,OAAO;QACT,CAAC;QAED,IAAI,MAAM,KAAK,KAAK,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;YAC5C,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,kBAAkB,CAAC,CAAC;YACxD,MAAM,KAAK,GAAuC,EAAE,CAAC;YACrD,GAAG,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACtD,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;YACzD,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,cAAc,EAAE,gBAAgB,EAAE,CAAC,CAAC;YACjE,OAAO;QACT,CAAC;QAED,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IAC7C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,QAAQ,CAAC,GAAG,EAAE,eAAe,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACnG,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,eAAe,CAAC,KAAc;IACrC,IAAI,KAAK,YAAY,aAAa;QAAE,OAAO,KAAK,CAAC,UAAU,CAAC;IAC5D,IAAI,KAAK,YAAY,iBAAiB;QAAE,OAAO,GAAG,CAAC;IACnD,IACE,KAAK,YAAY,qBAAqB;WACnC,KAAK,YAAY,yBAAyB;WAC1C,KAAK,YAAY,wBAAwB,EAC5C,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IACD,IAAI,KAAK,YAAY,yBAAyB;QAAE,OAAO,GAAG,CAAC;IAC3D,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,GAAmB,EACnB,GAAY,EACZ,SAAkB;IAElB,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;QACrB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChF,OAAO;IACT,CAAC;IACD,GAAG,CAAC,KAAK,CAAC,mBAAmB,CAAC;QAC5B,OAAO,EAAE,KAAK;QACd,EAAE,EAAE,SAAS,IAAI,IAAI;QACrB,KAAK,EAAE,uBAAuB,CAAC,iBAAiB,CAAC,GAAG,CAAC;KACtD,CAAC,CAAC,CAAC;IACJ,IAAI,CAAC,GAAG,CAAC,aAAa;QAAE,GAAG,CAAC,GAAG,EAAE,CAAC;AACpC,CAAC"}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,23 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
respond: boolean;
|
|
11
|
-
}
|
|
12
|
-
interface Router {
|
|
13
|
-
all(path: string, handler: (ctx: RouterContext) => void | Promise<void>): void;
|
|
14
|
-
}
|
|
15
|
-
declare module '@zhin.js/core' {
|
|
16
|
-
namespace Plugin {
|
|
17
|
-
interface Contexts {
|
|
18
|
-
router: Router;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
export {};
|
|
1
|
+
/**
|
|
2
|
+
* Runtime-first public API for the A2A Host.
|
|
3
|
+
*
|
|
4
|
+
* A2A routes are installed by the CLI composition root. Importing this package
|
|
5
|
+
* is intentionally inert and cannot depend on the removed legacy router.
|
|
6
|
+
*/
|
|
7
|
+
export { installRuntimeA2a, type InstallRuntimeA2aOptions, type RuntimeA2aConfig, } from './runtime.js';
|
|
8
|
+
export type { RuntimeA2aConfig as A2aConfig } from './runtime.js';
|
|
9
|
+
export { verifyA2aBearer } from './auth.js';
|
|
23
10
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,iBAAiB,EACjB,KAAK,wBAAwB,EAC7B,KAAK,gBAAgB,GACtB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,gBAAgB,IAAI,SAAS,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC"}
|
package/lib/index.js
CHANGED
|
@@ -1,138 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Runtime-first public API for the A2A Host.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* POST /a2a/{agentName}/jsonrpc
|
|
7
|
-
* * /a2a/{agentName}/rest/*
|
|
4
|
+
* A2A routes are installed by the CLI composition root. Importing this package
|
|
5
|
+
* is intentionally inert and cannot depend on the removed legacy router.
|
|
8
6
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import { AGENT_CARD_PATH } from '@a2a-js/sdk';
|
|
12
|
-
import { getAgentRuntimeRegistry } from '@zhin.js/agent';
|
|
13
|
-
import { resolvePublicBaseUrl, a2aAgentBasePath } from './config.js';
|
|
14
|
-
import { verifyA2aBearer } from './auth.js';
|
|
15
|
-
import { buildAgentCardForBinding, listExposableAgentNames } from './card-builder.js';
|
|
16
|
-
import { ZhinA2AExecutor } from './agent-executor.js';
|
|
17
|
-
import { handleAgentCard, handleJsonRpc, handleRest } from './http-handlers.js';
|
|
18
|
-
/** 路径参数(含 `*name` 通配时可能返回 string[])。 */
|
|
19
|
-
function paramPath(ctx, key) {
|
|
20
|
-
const v = ctx.params[key];
|
|
21
|
-
if (v === undefined || v === null)
|
|
22
|
-
return '';
|
|
23
|
-
if (Array.isArray(v))
|
|
24
|
-
return v.map(String).join('/');
|
|
25
|
-
return String(v);
|
|
26
|
-
}
|
|
27
|
-
const A2A_ROUTE = '/a2a/:agentName/:tail+';
|
|
28
|
-
const plugin = usePlugin();
|
|
29
|
-
const { root, useContext, logger, onDispose } = plugin;
|
|
30
|
-
let agentStacks = new Map();
|
|
31
|
-
let httpToken = '';
|
|
32
|
-
let publicBaseUrl = 'http://127.0.0.1:8086';
|
|
33
|
-
function rebuildAgentStacks(ai) {
|
|
34
|
-
const registry = ai.getBindingRegistry();
|
|
35
|
-
publicBaseUrl = resolvePublicBaseUrl(root.inject('config')?.get('zhin.config.yml') ?? {});
|
|
36
|
-
const runtime = getAgentRuntimeRegistry();
|
|
37
|
-
const primaryAgent = runtime.getDefault();
|
|
38
|
-
const next = new Map();
|
|
39
|
-
for (const agentName of listExposableAgentNames(registry)) {
|
|
40
|
-
const card = buildAgentCardForBinding(agentName, registry, publicBaseUrl);
|
|
41
|
-
if (!card || !primaryAgent)
|
|
42
|
-
continue;
|
|
43
|
-
const executor = new ZhinA2AExecutor({
|
|
44
|
-
agentName,
|
|
45
|
-
getAgent: () => runtime.getDefault(),
|
|
46
|
-
resolveBinding: () => registry.getBinding(agentName),
|
|
47
|
-
});
|
|
48
|
-
const requestHandler = new DefaultRequestHandler(card, new InMemoryTaskStore(), executor);
|
|
49
|
-
next.set(agentName, { agentName, requestHandler, agentCard: card });
|
|
50
|
-
}
|
|
51
|
-
agentStacks = next;
|
|
52
|
-
logger.debug(formatCompact({ A2A: 'ready', agents: [...next.keys()].join(',') }));
|
|
53
|
-
}
|
|
54
|
-
function parseA2aTail(tail) {
|
|
55
|
-
const normalized = tail.replace(/^\/+/, '');
|
|
56
|
-
if (normalized === `.well-known/${AGENT_CARD_PATH}` || normalized === '.well-known/agent-card.json') {
|
|
57
|
-
return { kind: 'card' };
|
|
58
|
-
}
|
|
59
|
-
if (normalized === 'jsonrpc' || normalized.startsWith('jsonrpc/')) {
|
|
60
|
-
return { kind: 'jsonrpc' };
|
|
61
|
-
}
|
|
62
|
-
if (normalized === 'rest' || normalized.startsWith('rest/')) {
|
|
63
|
-
const restSubPath = normalized === 'rest' ? '' : normalized.slice('rest/'.length);
|
|
64
|
-
return { kind: 'rest', restSubPath };
|
|
65
|
-
}
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
function ensureStacks(ai) {
|
|
69
|
-
if (agentStacks.size > 0 || !ai?.isReady())
|
|
70
|
-
return;
|
|
71
|
-
rebuildAgentStacks(ai);
|
|
72
|
-
}
|
|
73
|
-
function sendJsonViaRawRes(ctx, status, body) {
|
|
74
|
-
ctx.respond = false;
|
|
75
|
-
ctx.res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
76
|
-
ctx.res.end(JSON.stringify(body));
|
|
77
|
-
}
|
|
78
|
-
useContext('router', (router) => {
|
|
79
|
-
const configService = root.inject('config');
|
|
80
|
-
const appConfig = configService?.get('zhin.config.yml') ?? {};
|
|
81
|
-
httpToken = appConfig.http?.token ?? '';
|
|
82
|
-
publicBaseUrl = resolvePublicBaseUrl(appConfig);
|
|
83
|
-
const ai = root.inject('ai');
|
|
84
|
-
if (ai?.isReady())
|
|
85
|
-
rebuildAgentStacks(ai);
|
|
86
|
-
const a2aHandler = async (ctx) => {
|
|
87
|
-
const agentName = decodeURIComponent(paramPath(ctx, 'agentName'));
|
|
88
|
-
const parsed = parseA2aTail(paramPath(ctx, 'tail'));
|
|
89
|
-
if (!parsed) {
|
|
90
|
-
sendJsonViaRawRes(ctx, 404, { error: 'Not found' });
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
const aiNow = root.inject('ai');
|
|
94
|
-
ensureStacks(aiNow);
|
|
95
|
-
if (httpToken && !verifyA2aBearer(ctx.req, httpToken)) {
|
|
96
|
-
sendJsonViaRawRes(ctx, 401, { error: 'Unauthorized — Bearer token required' });
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
const stack = agentStacks.get(agentName);
|
|
100
|
-
if (!stack) {
|
|
101
|
-
sendJsonViaRawRes(ctx, 404, { error: `A2A agent "${agentName}" not found` });
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
ctx.respond = false;
|
|
105
|
-
const preParsedBody = ctx.method === 'POST' ? ctx.request.body : undefined;
|
|
106
|
-
try {
|
|
107
|
-
if (parsed.kind === 'card') {
|
|
108
|
-
await handleAgentCard(ctx.req, ctx.res, stack.requestHandler);
|
|
109
|
-
}
|
|
110
|
-
else if (parsed.kind === 'jsonrpc') {
|
|
111
|
-
await handleJsonRpc(ctx.req, ctx.res, stack.requestHandler, preParsedBody);
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
await handleRest(ctx.req, ctx.res, stack.requestHandler, parsed.restSubPath ?? '', preParsedBody);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
catch (err) {
|
|
118
|
-
logger.error('A2A request error:', err);
|
|
119
|
-
if (!ctx.res.headersSent) {
|
|
120
|
-
sendJsonViaRawRes(ctx, 500, { error: 'Internal server error' });
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
router.all(A2A_ROUTE, a2aHandler);
|
|
125
|
-
logger.debug(formatCompact({ A2A: 'listening', base: `${publicBaseUrl}${a2aAgentBasePath('{agent}')}` }));
|
|
126
|
-
onDispose(() => {
|
|
127
|
-
agentStacks.clear();
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
// Rebuild when AI service becomes ready (late init)
|
|
131
|
-
useContext('ai', (ai) => {
|
|
132
|
-
if (ai.isReady())
|
|
133
|
-
rebuildAgentStacks(ai);
|
|
134
|
-
return () => {
|
|
135
|
-
agentStacks.clear();
|
|
136
|
-
};
|
|
137
|
-
});
|
|
7
|
+
export { installRuntimeA2a, } from './runtime.js';
|
|
8
|
+
export { verifyA2aBearer } from './auth.js';
|
|
138
9
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,iBAAiB,GAGlB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC"}
|
package/lib/runtime.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { HttpRouteHost } from '@zhin.js/host-http-contract';
|
|
2
2
|
import type { AgentHostPort } from '@zhin.js/agent/runtime';
|
|
3
3
|
export interface RuntimeA2aConfig {
|
|
4
4
|
readonly enabled?: boolean;
|
|
@@ -7,7 +7,7 @@ export interface RuntimeA2aConfig {
|
|
|
7
7
|
readonly publicUrl?: string;
|
|
8
8
|
}
|
|
9
9
|
export interface InstallRuntimeA2aOptions {
|
|
10
|
-
readonly http:
|
|
10
|
+
readonly http: HttpRouteHost;
|
|
11
11
|
readonly agentHost: AgentHostPort;
|
|
12
12
|
readonly config?: RuntimeA2aConfig;
|
|
13
13
|
readonly fallbackToken?: string;
|
package/lib/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAQ5D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;IAClC,QAAQ,CAAC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IACnC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;CAC/B;AAOD,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,MAAM,IAAI,CAyD/E"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/a2a",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "A2A Protocol server for Zhin.js — multi-agent Agent Cards and task delegation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -36,20 +36,21 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@a2a-js/sdk": "1.0.0-beta.0",
|
|
39
|
-
"@zhin.js/core": "1.
|
|
40
|
-
"@zhin.js/host-http": "1.0.
|
|
39
|
+
"@zhin.js/core": "1.5.1",
|
|
40
|
+
"@zhin.js/host-http-contract": "1.0.0",
|
|
41
41
|
"@zhin.js/logger": "1.0.75"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"@zhin.js/agent": "1.
|
|
45
|
-
"zhin.js": "
|
|
44
|
+
"@zhin.js/agent": "1.1.1",
|
|
45
|
+
"zhin.js": "6.0.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/node": "^26.1.2",
|
|
49
49
|
"rimraf": "^6.1.3",
|
|
50
50
|
"typescript": "^6.0.3",
|
|
51
|
-
"@zhin.js/agent": "1.
|
|
52
|
-
"zhin.js": "
|
|
51
|
+
"@zhin.js/agent": "1.1.1",
|
|
52
|
+
"@zhin.js/host-http": "1.0.5",
|
|
53
|
+
"zhin.js": "6.0.1"
|
|
53
54
|
},
|
|
54
55
|
"files": [
|
|
55
56
|
"src",
|
package/src/http-handlers.ts
CHANGED
|
@@ -11,11 +11,17 @@ import {
|
|
|
11
11
|
} from '@a2a-js/sdk';
|
|
12
12
|
import {
|
|
13
13
|
JsonRpcTransportHandler,
|
|
14
|
+
InvalidAgentResponseError,
|
|
15
|
+
RequestMalformedError,
|
|
14
16
|
ServerCallContext,
|
|
17
|
+
TaskNotFoundError,
|
|
15
18
|
UnauthenticatedUser,
|
|
19
|
+
UnsupportedOperationError,
|
|
16
20
|
validateVersion,
|
|
21
|
+
VersionNotSupportedError,
|
|
17
22
|
type A2ARequestHandler,
|
|
18
23
|
} from '@a2a-js/sdk/server';
|
|
24
|
+
import { HttpBodyError, readJsonBody } from '@zhin.js/host-http-contract';
|
|
19
25
|
import { RestTransportHandler } from './rest-transport-handler.js';
|
|
20
26
|
|
|
21
27
|
const SSE_HEADERS: Record<string, string> = {
|
|
@@ -32,14 +38,8 @@ function formatSSEErrorEvent(data: unknown): string {
|
|
|
32
38
|
return `event: error\ndata: ${JSON.stringify(data)}\n\n`;
|
|
33
39
|
}
|
|
34
40
|
|
|
35
|
-
async function
|
|
36
|
-
|
|
37
|
-
for await (const chunk of req) {
|
|
38
|
-
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
|
|
39
|
-
}
|
|
40
|
-
const raw = Buffer.concat(chunks).toString('utf8');
|
|
41
|
-
if (!raw.trim()) return {};
|
|
42
|
-
return JSON.parse(raw) as unknown;
|
|
41
|
+
async function readA2aJsonBody(req: IncomingMessage): Promise<unknown> {
|
|
42
|
+
return (await readJsonBody(req)) ?? {};
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
function buildServerContext(req: IncomingMessage): ServerCallContext {
|
|
@@ -101,7 +101,7 @@ export async function handleJsonRpc(
|
|
|
101
101
|
|
|
102
102
|
const transport = new JsonRpcTransportHandler(requestHandler);
|
|
103
103
|
try {
|
|
104
|
-
const body = preParsedBody !== undefined ? preParsedBody : await
|
|
104
|
+
const body = preParsedBody !== undefined ? preParsedBody : await readA2aJsonBody(req);
|
|
105
105
|
const context = buildServerContext(req);
|
|
106
106
|
const agentCard = await requestHandler.getAgentCard();
|
|
107
107
|
validateVersion(context.requestedVersion, agentCard, 'JSONRPC');
|
|
@@ -137,6 +137,14 @@ export async function handleJsonRpc(
|
|
|
137
137
|
|
|
138
138
|
sendJson(res, 200, rpcResponseOrStream);
|
|
139
139
|
} catch (err) {
|
|
140
|
+
if (err instanceof HttpBodyError) {
|
|
141
|
+
sendJson(res, err.statusCode, {
|
|
142
|
+
jsonrpc: '2.0',
|
|
143
|
+
id: null,
|
|
144
|
+
error: { code: -32700, message: 'Parse error' },
|
|
145
|
+
});
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
140
148
|
const errorResponse = {
|
|
141
149
|
jsonrpc: '2.0',
|
|
142
150
|
id: null,
|
|
@@ -168,14 +176,14 @@ export async function handleRest(
|
|
|
168
176
|
}
|
|
169
177
|
|
|
170
178
|
if (method === 'POST' && path === 'v1/message:send') {
|
|
171
|
-
const params = preParsedBody !== undefined ? preParsedBody : await
|
|
179
|
+
const params = preParsedBody !== undefined ? preParsedBody : await readA2aJsonBody(req);
|
|
172
180
|
const result = await transport.sendMessage(params as never, context);
|
|
173
181
|
sendJson(res, 200, result, { 'Content-Type': A2A_CONTENT_TYPE });
|
|
174
182
|
return;
|
|
175
183
|
}
|
|
176
184
|
|
|
177
185
|
if (method === 'POST' && path === 'v1/message:stream') {
|
|
178
|
-
const params = preParsedBody !== undefined ? preParsedBody : await
|
|
186
|
+
const params = preParsedBody !== undefined ? preParsedBody : await readA2aJsonBody(req);
|
|
179
187
|
const stream = await transport.sendMessageStream(params as never, context);
|
|
180
188
|
for (const [key, value] of Object.entries(SSE_HEADERS)) {
|
|
181
189
|
res.setHeader(key, value);
|
|
@@ -225,8 +233,26 @@ export async function handleRest(
|
|
|
225
233
|
|
|
226
234
|
sendJson(res, 404, { error: 'Not found' });
|
|
227
235
|
} catch (err) {
|
|
228
|
-
sendJson(res,
|
|
236
|
+
sendJson(res, restErrorStatus(err), { error: err instanceof Error ? err.message : String(err) });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* HTTP+JSON has HTTP status semantics. Keep its transport errors distinct from
|
|
242
|
+
* request validation, missing tasks, and failures inside an agent executor.
|
|
243
|
+
*/
|
|
244
|
+
function restErrorStatus(error: unknown): number {
|
|
245
|
+
if (error instanceof HttpBodyError) return error.statusCode;
|
|
246
|
+
if (error instanceof TaskNotFoundError) return 404;
|
|
247
|
+
if (
|
|
248
|
+
error instanceof RequestMalformedError
|
|
249
|
+
|| error instanceof UnsupportedOperationError
|
|
250
|
+
|| error instanceof VersionNotSupportedError
|
|
251
|
+
) {
|
|
252
|
+
return 400;
|
|
229
253
|
}
|
|
254
|
+
if (error instanceof InvalidAgentResponseError) return 500;
|
|
255
|
+
return 500;
|
|
230
256
|
}
|
|
231
257
|
|
|
232
258
|
export async function handleRestStreamError(
|
package/src/index.ts
CHANGED
|
@@ -1,196 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Runtime-first public API for the A2A Host.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* POST /a2a/{agentName}/jsonrpc
|
|
7
|
-
* * /a2a/{agentName}/rest/*
|
|
4
|
+
* A2A routes are installed by the CLI composition root. Importing this package
|
|
5
|
+
* is intentionally inert and cannot depend on the removed legacy router.
|
|
8
6
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} from '@a2a-js/sdk/server';
|
|
17
|
-
import { AGENT_CARD_PATH, type AgentCard } from '@a2a-js/sdk';
|
|
18
|
-
|
|
19
|
-
import { getAgentRuntimeRegistry, type AIService } from '@zhin.js/agent';
|
|
20
|
-
|
|
21
|
-
import { resolvePublicBaseUrl, a2aAgentBasePath } from './config.js';
|
|
22
|
-
import { verifyA2aBearer } from './auth.js';
|
|
23
|
-
import { buildAgentCardForBinding, listExposableAgentNames } from './card-builder.js';
|
|
24
|
-
import { ZhinA2AExecutor } from './agent-executor.js';
|
|
25
|
-
import { handleAgentCard, handleJsonRpc, handleRest } from './http-handlers.js';
|
|
26
|
-
|
|
27
|
-
// Legacy usePlugin 入口:`router` context 原由已删除的 @zhin.js/host-router(koa 风格)提供。
|
|
28
|
-
// 新 Plugin Runtime 走 ./runtime.ts(@zhin.js/host-http),本入口仅保留结构类型以便编译。
|
|
29
|
-
interface RouterContext {
|
|
30
|
-
req: IncomingMessage;
|
|
31
|
-
res: ServerResponse;
|
|
32
|
-
request: { body?: unknown };
|
|
33
|
-
method: string;
|
|
34
|
-
params: Record<string, string | string[] | undefined>;
|
|
35
|
-
respond: boolean;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface Router {
|
|
39
|
-
all(path: string, handler: (ctx: RouterContext) => void | Promise<void>): void;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
declare module '@zhin.js/core' {
|
|
43
|
-
namespace Plugin {
|
|
44
|
-
interface Contexts {
|
|
45
|
-
router: Router;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** 路径参数(含 `*name` 通配时可能返回 string[])。 */
|
|
51
|
-
function paramPath(ctx: RouterContext, key: string): string {
|
|
52
|
-
const v = ctx.params[key];
|
|
53
|
-
if (v === undefined || v === null) return '';
|
|
54
|
-
if (Array.isArray(v)) return v.map(String).join('/');
|
|
55
|
-
return String(v);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
interface AgentA2AStack {
|
|
59
|
-
agentName: string;
|
|
60
|
-
requestHandler: A2ARequestHandler;
|
|
61
|
-
agentCard: AgentCard;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const A2A_ROUTE = '/a2a/:agentName/:tail+';
|
|
65
|
-
|
|
66
|
-
const plugin = usePlugin();
|
|
67
|
-
const { root, useContext, logger, onDispose } = plugin;
|
|
68
|
-
|
|
69
|
-
let agentStacks = new Map<string, AgentA2AStack>();
|
|
70
|
-
let httpToken = '';
|
|
71
|
-
let publicBaseUrl = 'http://127.0.0.1:8086';
|
|
72
|
-
|
|
73
|
-
function rebuildAgentStacks(ai: AIService): void {
|
|
74
|
-
const registry = ai.getBindingRegistry();
|
|
75
|
-
publicBaseUrl = resolvePublicBaseUrl(
|
|
76
|
-
root.inject('config')?.get<{ http?: { host?: string; port?: number; publicUrl?: string } }>('zhin.config.yml') ?? {},
|
|
77
|
-
);
|
|
78
|
-
const runtime = getAgentRuntimeRegistry();
|
|
79
|
-
const primaryAgent = runtime.getDefault();
|
|
80
|
-
const next = new Map<string, AgentA2AStack>();
|
|
81
|
-
|
|
82
|
-
for (const agentName of listExposableAgentNames(registry)) {
|
|
83
|
-
const card = buildAgentCardForBinding(agentName, registry, publicBaseUrl);
|
|
84
|
-
if (!card || !primaryAgent) continue;
|
|
85
|
-
|
|
86
|
-
const executor = new ZhinA2AExecutor({
|
|
87
|
-
agentName,
|
|
88
|
-
getAgent: () => runtime.getDefault(),
|
|
89
|
-
resolveBinding: () => registry.getBinding(agentName),
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
const requestHandler = new DefaultRequestHandler(
|
|
93
|
-
card,
|
|
94
|
-
new InMemoryTaskStore(),
|
|
95
|
-
executor,
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
next.set(agentName, { agentName, requestHandler, agentCard: card });
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
agentStacks = next;
|
|
102
|
-
logger.debug(formatCompact({ A2A: 'ready', agents: [...next.keys()].join(',') }));
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function parseA2aTail(tail: string): { kind: 'card' | 'jsonrpc' | 'rest'; restSubPath?: string } | null {
|
|
106
|
-
const normalized = tail.replace(/^\/+/, '');
|
|
107
|
-
if (normalized === `.well-known/${AGENT_CARD_PATH}` || normalized === '.well-known/agent-card.json') {
|
|
108
|
-
return { kind: 'card' };
|
|
109
|
-
}
|
|
110
|
-
if (normalized === 'jsonrpc' || normalized.startsWith('jsonrpc/')) {
|
|
111
|
-
return { kind: 'jsonrpc' };
|
|
112
|
-
}
|
|
113
|
-
if (normalized === 'rest' || normalized.startsWith('rest/')) {
|
|
114
|
-
const restSubPath = normalized === 'rest' ? '' : normalized.slice('rest/'.length);
|
|
115
|
-
return { kind: 'rest', restSubPath };
|
|
116
|
-
}
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function ensureStacks(ai: AIService | undefined): void {
|
|
121
|
-
if (agentStacks.size > 0 || !ai?.isReady()) return;
|
|
122
|
-
rebuildAgentStacks(ai);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function sendJsonViaRawRes(ctx: RouterContext, status: number, body: unknown): void {
|
|
126
|
-
ctx.respond = false;
|
|
127
|
-
ctx.res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
128
|
-
ctx.res.end(JSON.stringify(body));
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
useContext('router', (router: Router) => {
|
|
132
|
-
const configService = root.inject('config');
|
|
133
|
-
const appConfig = configService?.get<{ http?: { token?: string; host?: string; port?: number; publicUrl?: string } }>('zhin.config.yml') ?? {};
|
|
134
|
-
httpToken = appConfig.http?.token ?? '';
|
|
135
|
-
publicBaseUrl = resolvePublicBaseUrl(appConfig);
|
|
136
|
-
|
|
137
|
-
const ai = root.inject('ai') as AIService | undefined;
|
|
138
|
-
if (ai?.isReady()) rebuildAgentStacks(ai);
|
|
139
|
-
|
|
140
|
-
const a2aHandler = async (ctx: RouterContext): Promise<void> => {
|
|
141
|
-
const agentName = decodeURIComponent(paramPath(ctx, 'agentName'));
|
|
142
|
-
const parsed = parseA2aTail(paramPath(ctx, 'tail'));
|
|
143
|
-
if (!parsed) {
|
|
144
|
-
sendJsonViaRawRes(ctx, 404, { error: 'Not found' });
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const aiNow = root.inject('ai') as AIService | undefined;
|
|
149
|
-
ensureStacks(aiNow);
|
|
150
|
-
|
|
151
|
-
if (httpToken && !verifyA2aBearer(ctx.req, httpToken)) {
|
|
152
|
-
sendJsonViaRawRes(ctx, 401, { error: 'Unauthorized — Bearer token required' });
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const stack = agentStacks.get(agentName);
|
|
157
|
-
if (!stack) {
|
|
158
|
-
sendJsonViaRawRes(ctx, 404, { error: `A2A agent "${agentName}" not found` });
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
ctx.respond = false;
|
|
163
|
-
const preParsedBody = ctx.method === 'POST' ? ctx.request.body : undefined;
|
|
164
|
-
|
|
165
|
-
try {
|
|
166
|
-
if (parsed.kind === 'card') {
|
|
167
|
-
await handleAgentCard(ctx.req, ctx.res, stack.requestHandler);
|
|
168
|
-
} else if (parsed.kind === 'jsonrpc') {
|
|
169
|
-
await handleJsonRpc(ctx.req, ctx.res, stack.requestHandler, preParsedBody);
|
|
170
|
-
} else {
|
|
171
|
-
await handleRest(ctx.req, ctx.res, stack.requestHandler, parsed.restSubPath ?? '', preParsedBody);
|
|
172
|
-
}
|
|
173
|
-
} catch (err) {
|
|
174
|
-
logger.error('A2A request error:', err);
|
|
175
|
-
if (!ctx.res.headersSent) {
|
|
176
|
-
sendJsonViaRawRes(ctx, 500, { error: 'Internal server error' });
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
router.all(A2A_ROUTE, a2aHandler);
|
|
182
|
-
|
|
183
|
-
logger.debug(formatCompact({ A2A: 'listening', base: `${publicBaseUrl}${a2aAgentBasePath('{agent}')}` }));
|
|
184
|
-
|
|
185
|
-
onDispose(() => {
|
|
186
|
-
agentStacks.clear();
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
// Rebuild when AI service becomes ready (late init)
|
|
191
|
-
useContext('ai', (ai: AIService) => {
|
|
192
|
-
if (ai.isReady()) rebuildAgentStacks(ai);
|
|
193
|
-
return () => {
|
|
194
|
-
agentStacks.clear();
|
|
195
|
-
};
|
|
196
|
-
});
|
|
7
|
+
export {
|
|
8
|
+
installRuntimeA2a,
|
|
9
|
+
type InstallRuntimeA2aOptions,
|
|
10
|
+
type RuntimeA2aConfig,
|
|
11
|
+
} from './runtime.js';
|
|
12
|
+
export type { RuntimeA2aConfig as A2aConfig } from './runtime.js';
|
|
13
|
+
export { verifyA2aBearer } from './auth.js';
|
package/src/runtime.ts
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
type A2ARequestHandler,
|
|
5
5
|
} from '@a2a-js/sdk/server';
|
|
6
6
|
import { AGENT_CARD_PATH, type AgentCard } from '@a2a-js/sdk';
|
|
7
|
-
import type {
|
|
7
|
+
import type { HttpRouteHost } from '@zhin.js/host-http-contract';
|
|
8
8
|
import type { AgentHostPort } from '@zhin.js/agent/runtime';
|
|
9
9
|
import type { ZhinAgent } from '@zhin.js/agent';
|
|
10
10
|
import type { AgentBindingRegistry } from '@zhin.js/agent/config';
|
|
@@ -21,7 +21,7 @@ export interface RuntimeA2aConfig {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export interface InstallRuntimeA2aOptions {
|
|
24
|
-
readonly http:
|
|
24
|
+
readonly http: HttpRouteHost;
|
|
25
25
|
readonly agentHost: AgentHostPort;
|
|
26
26
|
readonly config?: RuntimeA2aConfig;
|
|
27
27
|
readonly fallbackToken?: string;
|