@nifrajs/a2a 3.2.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 +79 -0
- package/dist/index.d.ts +107 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +369 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -0
- package/src/index.ts +528 -0
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# @nifrajs/a2a
|
|
2
|
+
|
|
3
|
+
Mount a nifra agent as an [Agent2Agent (A2A)](https://a2a-protocol.org) protocol server. The agent card is served on GET, the A2A 1.0 JSON-RPC binding on POST: `SendMessage` runs one bounded agent turn, `SendStreamingMessage` streams step evidence over SSE, `GetTask` projects the stored turn state.
|
|
4
|
+
|
|
5
|
+
A protocol bridge over [`@nifrajs/agent`](../agent) - the request body reuses core's single bounded, prototype-guarded trust boundary, and the model, durable state store, and approval transport are injected per request through the same `ports` factory `mountAgent` uses. Dependency-free beyond the two nifra peers.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun add @nifrajs/a2a @nifrajs/agent
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Mount
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { server } from "@nifrajs/core"
|
|
17
|
+
import { mountA2A } from "@nifrajs/a2a"
|
|
18
|
+
import { agent } from "./agent" // an AgentDefinition
|
|
19
|
+
|
|
20
|
+
const app = server()
|
|
21
|
+
mountA2A(app, {
|
|
22
|
+
agent,
|
|
23
|
+
card: { url: "https://api.example.com/a2a", version: "1.0.0" },
|
|
24
|
+
ports: (c) => ({
|
|
25
|
+
model: myModelPort, // scope to the request subject
|
|
26
|
+
capabilities: ["search.read"],
|
|
27
|
+
state: myStateStore, // required for GetTask and resume
|
|
28
|
+
}),
|
|
29
|
+
})
|
|
30
|
+
// GET /.well-known/agent-card.json - the agent card
|
|
31
|
+
// POST /a2a - SendMessage | SendStreamingMessage | GetTask
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Task mapping
|
|
35
|
+
|
|
36
|
+
One A2A task is one agent turn: `taskId` is the runner's `turnId`.
|
|
37
|
+
|
|
38
|
+
| Run result | Task state |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| completed with output | `TASK_STATE_COMPLETED` + an `output` artifact |
|
|
41
|
+
| completed with a typed error | `TASK_STATE_FAILED` (error in `metadata`) |
|
|
42
|
+
| suspended (approval, budget, model, max turns) | `TASK_STATE_INPUT_REQUIRED` |
|
|
43
|
+
| suspended (cancelled) | `TASK_STATE_CANCELED` |
|
|
44
|
+
|
|
45
|
+
Agent input is `message.metadata.input` when present, otherwise the first `text` part. Structured output returns as a `text` part carrying JSON.
|
|
46
|
+
|
|
47
|
+
## Human-in-the-loop resume
|
|
48
|
+
|
|
49
|
+
A suspended task's status message carries the pending continuation in `metadata`. Send it back to continue the same task - the runtime keeps state token-only, so a suspended tool's input must be replayed in `continuation.input`:
|
|
50
|
+
|
|
51
|
+
```jsonc
|
|
52
|
+
{
|
|
53
|
+
"jsonrpc": "2.0", "id": 2, "method": "SendMessage",
|
|
54
|
+
"params": {
|
|
55
|
+
"message": {
|
|
56
|
+
"messageId": "m2", "role": "ROLE_USER", "parts": [],
|
|
57
|
+
"taskId": "<task id from the suspension>",
|
|
58
|
+
"metadata": {
|
|
59
|
+
"input": { "prompt": "go" },
|
|
60
|
+
"resume": {
|
|
61
|
+
"continuation": { "kind": "approval", "tool": "search.read", "effectId": "…", "input": { "q": "…" } },
|
|
62
|
+
"approval": { "granted": true }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Cross-request registries (cancellation, subscription fan-out, push notifications) are deliberately out of scope for the stateless seam; those spec methods answer `UnsupportedOperationError` (-32004).
|
|
71
|
+
|
|
72
|
+
The seam performs no authentication or authorization - wrap it with your app's route guards and scope every port in `ports(c)` to the caller.
|
|
73
|
+
|
|
74
|
+
## Docs
|
|
75
|
+
|
|
76
|
+
Part of the **[nifra](https://nifra.dev)** full-stack TypeScript framework - one core, five UI libraries, every runtime. Scaffold a new app with `bun create nifra`.
|
|
77
|
+
|
|
78
|
+
For AI agents, see [`LLM.md`](./LLM.md) and the full corpus
|
|
79
|
+
[`../../llms-full.txt`](../../llms-full.txt).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nifrajs/a2a` - mount a nifra agent as an Agent2Agent (A2A) protocol server.
|
|
3
|
+
*
|
|
4
|
+
* The package is a protocol bridge over the `@nifrajs/agent` runner, mirroring the `mountAgent`
|
|
5
|
+
* seam: HTTP in, the bounded runner in the middle, A2A JSON-RPC out. The request body is read
|
|
6
|
+
* through core's single bounded, proto-guarded framing lane (`@nifrajs/core/edge-kit`); the model,
|
|
7
|
+
* durable state store, and approval transport arrive through a per-request `ports` factory and never
|
|
8
|
+
* live in this package.
|
|
9
|
+
*
|
|
10
|
+
* Wire contract (A2A protocol version 1.0, JSON-RPC binding):
|
|
11
|
+
* - `GET {cardPath}` (default `/.well-known/agent-card.json`) -> the agent card.
|
|
12
|
+
* - `POST {path}` (default `/a2a`) -> a JSON-RPC 2.0 request:
|
|
13
|
+
* - `SendMessage` runs one turn and returns `{ task }` with a terminal or interrupted status.
|
|
14
|
+
* - `SendStreamingMessage` responds `text/event-stream`; each SSE `data:` field is a complete
|
|
15
|
+
* JSON-RPC response whose result is `{ statusUpdate }` per step-evidence item, then
|
|
16
|
+
* `{ artifactUpdate }` for a produced output, then a final `{ statusUpdate }` with
|
|
17
|
+
* `final: true`.
|
|
18
|
+
* - `GetTask` projects the stored turn state for a task id (requires `ports.state`).
|
|
19
|
+
* - Every other spec method returns `UnsupportedOperationError` (-32004): the seam is stateless
|
|
20
|
+
* per request, so cross-request cancellation and subscription registries belong to the caller.
|
|
21
|
+
* - A task is one agent turn: `taskId` == the runner's `turnId`. A suspended run surfaces as
|
|
22
|
+
* `TASK_STATE_INPUT_REQUIRED` whose status message carries the pending continuation in
|
|
23
|
+
* `metadata`. To resume, send the continuation back via `message.taskId` +
|
|
24
|
+
* `message.metadata.resume` (`{ continuation, approval? }`); the runtime keeps state token-only,
|
|
25
|
+
* so a suspended tool's input must be replayed in `continuation.input`.
|
|
26
|
+
* - Agent input is `message.metadata.input` when present, otherwise the first `text` part.
|
|
27
|
+
* Structured output is returned as a `text` part carrying JSON.
|
|
28
|
+
* - The seam performs no authentication or authorization. Wrap it with the app's own route guards,
|
|
29
|
+
* and scope the store/model returned by `ports` to the caller.
|
|
30
|
+
*/
|
|
31
|
+
import { type AgentDefinition, type AgentPorts } from "@nifrajs/agent";
|
|
32
|
+
import { type ProtoPoisoning } from "@nifrajs/core/edge-kit";
|
|
33
|
+
import type { StandardSchemaV1 } from "@nifrajs/core/schema";
|
|
34
|
+
export declare const A2A_PROTOCOL_VERSION = "1.0";
|
|
35
|
+
export declare const AGENT_CARD_WELL_KNOWN_PATH = "/.well-known/agent-card.json";
|
|
36
|
+
export declare const A2A_ERROR_CODES: {
|
|
37
|
+
readonly parseError: -32700;
|
|
38
|
+
readonly invalidRequest: -32600;
|
|
39
|
+
readonly methodNotFound: -32601;
|
|
40
|
+
readonly invalidParams: -32602;
|
|
41
|
+
readonly internalError: -32603;
|
|
42
|
+
readonly taskNotFound: -32001;
|
|
43
|
+
readonly unsupportedOperation: -32004;
|
|
44
|
+
};
|
|
45
|
+
export type A2ATaskState = "TASK_STATE_SUBMITTED" | "TASK_STATE_WORKING" | "TASK_STATE_INPUT_REQUIRED" | "TASK_STATE_COMPLETED" | "TASK_STATE_FAILED" | "TASK_STATE_CANCELED";
|
|
46
|
+
export interface A2AAgentSkill {
|
|
47
|
+
readonly id: string;
|
|
48
|
+
readonly name: string;
|
|
49
|
+
readonly description: string;
|
|
50
|
+
readonly tags: readonly string[];
|
|
51
|
+
}
|
|
52
|
+
export interface A2AAgentCard {
|
|
53
|
+
readonly protocolVersion: string;
|
|
54
|
+
readonly name: string;
|
|
55
|
+
readonly description: string;
|
|
56
|
+
readonly url: string;
|
|
57
|
+
readonly version: string;
|
|
58
|
+
readonly capabilities: {
|
|
59
|
+
readonly streaming: boolean;
|
|
60
|
+
};
|
|
61
|
+
readonly defaultInputModes: readonly string[];
|
|
62
|
+
readonly defaultOutputModes: readonly string[];
|
|
63
|
+
readonly skills: readonly A2AAgentSkill[];
|
|
64
|
+
}
|
|
65
|
+
/** Caller-supplied card identity; everything else derives from the agent definition. */
|
|
66
|
+
export interface A2ACardInfo {
|
|
67
|
+
/** Public URL of the JSON-RPC endpoint, as advertised to A2A clients. */
|
|
68
|
+
readonly url: string;
|
|
69
|
+
/** Version of the exposed agent (the deployment's, not the protocol's). */
|
|
70
|
+
readonly version: string;
|
|
71
|
+
readonly name?: string;
|
|
72
|
+
readonly description?: string;
|
|
73
|
+
}
|
|
74
|
+
/** The structural slice of a route context the seam needs. */
|
|
75
|
+
export interface A2ARouteContext {
|
|
76
|
+
readonly req: Request;
|
|
77
|
+
}
|
|
78
|
+
/** The structural slice of a nifra server `mountA2A` needs. */
|
|
79
|
+
export interface A2AMountableApp {
|
|
80
|
+
get(path: string, handler: (c: A2ARouteContext) => Response | Promise<Response>): unknown;
|
|
81
|
+
post(path: string, handler: (c: A2ARouteContext) => Response | Promise<Response>): unknown;
|
|
82
|
+
}
|
|
83
|
+
export interface MountA2AOptions<InputSchema extends StandardSchemaV1, OutputSchema extends StandardSchemaV1> {
|
|
84
|
+
readonly agent: AgentDefinition<InputSchema, OutputSchema>;
|
|
85
|
+
readonly card: A2ACardInfo;
|
|
86
|
+
/** JSON-RPC endpoint path. Default `/a2a`. */
|
|
87
|
+
readonly path?: string;
|
|
88
|
+
/** Agent card path. Default `/.well-known/agent-card.json`. */
|
|
89
|
+
readonly cardPath?: string;
|
|
90
|
+
/** Bounded run length passed to the runner. */
|
|
91
|
+
readonly maxTurns?: number;
|
|
92
|
+
/** Maximum request body size in bytes. Default 1_000_000. */
|
|
93
|
+
readonly maxBodyBytes?: number;
|
|
94
|
+
/** Prototype-poisoning policy for the framing lane. Default `"reject"`. */
|
|
95
|
+
readonly protoPoisoning?: ProtoPoisoning;
|
|
96
|
+
/**
|
|
97
|
+
* Build the ports for one request - the model, durable state store, approval transport,
|
|
98
|
+
* capabilities, and budgets. Receives the route context so the caller can scope every port to the
|
|
99
|
+
* request subject.
|
|
100
|
+
*/
|
|
101
|
+
readonly ports: (c: A2ARouteContext) => AgentPorts | Promise<AgentPorts>;
|
|
102
|
+
}
|
|
103
|
+
/** Derive a spec-shaped agent card from the agent definition plus the caller's identity fields. */
|
|
104
|
+
export declare function agentCard<InputSchema extends StandardSchemaV1, OutputSchema extends StandardSchemaV1>(agent: AgentDefinition<InputSchema, OutputSchema>, info: A2ACardInfo): A2AAgentCard;
|
|
105
|
+
/** Mount an agent as an A2A server: the agent card on GET, the JSON-RPC binding on POST. */
|
|
106
|
+
export declare function mountA2A<InputSchema extends StandardSchemaV1, OutputSchema extends StandardSchemaV1>(app: A2AMountableApp, options: MountA2AOptions<InputSchema, OutputSchema>): void;
|
|
107
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nifrajs/a2a` - mount a nifra agent as an Agent2Agent (A2A) protocol server.
|
|
3
|
+
*
|
|
4
|
+
* The package is a protocol bridge over the `@nifrajs/agent` runner, mirroring the `mountAgent`
|
|
5
|
+
* seam: HTTP in, the bounded runner in the middle, A2A JSON-RPC out. The request body is read
|
|
6
|
+
* through core's single bounded, proto-guarded framing lane (`@nifrajs/core/edge-kit`); the model,
|
|
7
|
+
* durable state store, and approval transport arrive through a per-request `ports` factory and never
|
|
8
|
+
* live in this package.
|
|
9
|
+
*
|
|
10
|
+
* Wire contract (A2A protocol version 1.0, JSON-RPC binding):
|
|
11
|
+
* - `GET {cardPath}` (default `/.well-known/agent-card.json`) -> the agent card.
|
|
12
|
+
* - `POST {path}` (default `/a2a`) -> a JSON-RPC 2.0 request:
|
|
13
|
+
* - `SendMessage` runs one turn and returns `{ task }` with a terminal or interrupted status.
|
|
14
|
+
* - `SendStreamingMessage` responds `text/event-stream`; each SSE `data:` field is a complete
|
|
15
|
+
* JSON-RPC response whose result is `{ statusUpdate }` per step-evidence item, then
|
|
16
|
+
* `{ artifactUpdate }` for a produced output, then a final `{ statusUpdate }` with
|
|
17
|
+
* `final: true`.
|
|
18
|
+
* - `GetTask` projects the stored turn state for a task id (requires `ports.state`).
|
|
19
|
+
* - Every other spec method returns `UnsupportedOperationError` (-32004): the seam is stateless
|
|
20
|
+
* per request, so cross-request cancellation and subscription registries belong to the caller.
|
|
21
|
+
* - A task is one agent turn: `taskId` == the runner's `turnId`. A suspended run surfaces as
|
|
22
|
+
* `TASK_STATE_INPUT_REQUIRED` whose status message carries the pending continuation in
|
|
23
|
+
* `metadata`. To resume, send the continuation back via `message.taskId` +
|
|
24
|
+
* `message.metadata.resume` (`{ continuation, approval? }`); the runtime keeps state token-only,
|
|
25
|
+
* so a suspended tool's input must be replayed in `continuation.input`.
|
|
26
|
+
* - Agent input is `message.metadata.input` when present, otherwise the first `text` part.
|
|
27
|
+
* Structured output is returned as a `text` part carrying JSON.
|
|
28
|
+
* - The seam performs no authentication or authorization. Wrap it with the app's own route guards,
|
|
29
|
+
* and scope the store/model returned by `ports` to the caller.
|
|
30
|
+
*/
|
|
31
|
+
import { combineAgentTelemetry, createAgentState, resumeAgent, runAgent, } from "@nifrajs/agent";
|
|
32
|
+
import { createAgentEvidenceStream } from "@nifrajs/agent/events";
|
|
33
|
+
import { EMPTY_RESPONSE_CONTROLS, readBodyFramed, toResponse, } from "@nifrajs/core/edge-kit";
|
|
34
|
+
export const A2A_PROTOCOL_VERSION = "1.0";
|
|
35
|
+
export const AGENT_CARD_WELL_KNOWN_PATH = "/.well-known/agent-card.json";
|
|
36
|
+
const DEFAULT_PATH = "/a2a";
|
|
37
|
+
const DEFAULT_MAX_BODY_BYTES = 1_000_000;
|
|
38
|
+
const PENDING_KINDS = ["approval", "budget", "model", "cancelled"];
|
|
39
|
+
const TURN_ID_PATTERN = /^[a-zA-Z0-9._:-]{1,128}$/;
|
|
40
|
+
// JSON-RPC 2.0 standard codes plus the A2A-specific range.
|
|
41
|
+
export const A2A_ERROR_CODES = {
|
|
42
|
+
parseError: -32700,
|
|
43
|
+
invalidRequest: -32600,
|
|
44
|
+
methodNotFound: -32601,
|
|
45
|
+
invalidParams: -32602,
|
|
46
|
+
internalError: -32603,
|
|
47
|
+
taskNotFound: -32001,
|
|
48
|
+
unsupportedOperation: -32004,
|
|
49
|
+
};
|
|
50
|
+
/** Derive a spec-shaped agent card from the agent definition plus the caller's identity fields. */
|
|
51
|
+
export function agentCard(agent, info) {
|
|
52
|
+
return {
|
|
53
|
+
protocolVersion: A2A_PROTOCOL_VERSION,
|
|
54
|
+
name: info.name ?? agent.name,
|
|
55
|
+
description: info.description ?? agent.instruction,
|
|
56
|
+
url: info.url,
|
|
57
|
+
version: info.version,
|
|
58
|
+
capabilities: { streaming: true },
|
|
59
|
+
defaultInputModes: ["application/json", "text/plain"],
|
|
60
|
+
defaultOutputModes: ["application/json", "text/plain"],
|
|
61
|
+
skills: agent.tools.map((tool) => ({
|
|
62
|
+
id: tool.name,
|
|
63
|
+
name: tool.name,
|
|
64
|
+
description: tool.description,
|
|
65
|
+
tags: [tool.capability],
|
|
66
|
+
})),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/** Mount an agent as an A2A server: the agent card on GET, the JSON-RPC binding on POST. */
|
|
70
|
+
export function mountA2A(app, options) {
|
|
71
|
+
const path = options.path ?? DEFAULT_PATH;
|
|
72
|
+
const cardPath = options.cardPath ?? AGENT_CARD_WELL_KNOWN_PATH;
|
|
73
|
+
const maxBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
|
|
74
|
+
const proto = options.protoPoisoning ?? "reject";
|
|
75
|
+
const runOptions = options.maxTurns === undefined ? {} : { maxTurns: options.maxTurns };
|
|
76
|
+
const card = agentCard(options.agent, options.card);
|
|
77
|
+
app.get(cardPath, () => jsonResponse(200, card));
|
|
78
|
+
app.post(path, (c) => readBodyFramed(c.req, maxBytes, proto, (parsed) => dispatch(c, parsed, options, runOptions), (rejection) => render(rejection), () => rpcErrorResponse(null, A2A_ERROR_CODES.parseError, "parse_error")).catch(() => rpcErrorResponse(null, A2A_ERROR_CODES.internalError, "internal_error")));
|
|
79
|
+
}
|
|
80
|
+
async function dispatch(c, parsed, options, runOptions) {
|
|
81
|
+
const request = asRecord(parsed);
|
|
82
|
+
const id = request === undefined ? undefined : asRpcId(request.id);
|
|
83
|
+
if (request === undefined || request.jsonrpc !== "2.0" || id === undefined)
|
|
84
|
+
return rpcErrorResponse(null, A2A_ERROR_CODES.invalidRequest, "invalid_request");
|
|
85
|
+
const method = request.method;
|
|
86
|
+
if (typeof method !== "string")
|
|
87
|
+
return rpcErrorResponse(id, A2A_ERROR_CODES.invalidRequest, "invalid_request");
|
|
88
|
+
const params = asRecord(request.params) ?? {};
|
|
89
|
+
switch (method) {
|
|
90
|
+
case "SendMessage":
|
|
91
|
+
case "SendStreamingMessage": {
|
|
92
|
+
const turn = prepareTurn(params);
|
|
93
|
+
if ("error" in turn)
|
|
94
|
+
return rpcErrorResponse(id, turn.error.code, turn.error.message);
|
|
95
|
+
const basePorts = await options.ports(c);
|
|
96
|
+
const start = (telemetry) => {
|
|
97
|
+
// Compose rather than replace: the SSE evidence stream must not displace a telemetry
|
|
98
|
+
// port the caller injected through `ports`.
|
|
99
|
+
const combined = combineAgentTelemetry(basePorts.telemetry, telemetry);
|
|
100
|
+
const ports = combined === undefined ? basePorts : { ...basePorts, telemetry: combined };
|
|
101
|
+
if (turn.resume !== undefined) {
|
|
102
|
+
return resumeAgent(options.agent, turn.turnId, { value: turn.input, resume: turn.resume }, ports, runOptions);
|
|
103
|
+
}
|
|
104
|
+
return runAgent(options.agent, { value: turn.input }, ports, {
|
|
105
|
+
...runOptions,
|
|
106
|
+
state: createAgentState(turn.turnId),
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
if (method === "SendMessage") {
|
|
110
|
+
try {
|
|
111
|
+
return rpcResultResponse(id, { task: projectTask(turn.turnId, await start()) });
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return rpcErrorResponse(id, A2A_ERROR_CODES.taskNotFound, "task_not_resumable");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return streamingResponse(id, turn.turnId, start);
|
|
118
|
+
}
|
|
119
|
+
case "GetTask": {
|
|
120
|
+
const taskId = typeof params.id === "string" ? params.id : undefined;
|
|
121
|
+
if (taskId === undefined)
|
|
122
|
+
return rpcErrorResponse(id, A2A_ERROR_CODES.invalidParams, "invalid_params");
|
|
123
|
+
const basePorts = await options.ports(c);
|
|
124
|
+
const state = await basePorts.state?.load(taskId);
|
|
125
|
+
if (state === undefined)
|
|
126
|
+
return rpcErrorResponse(id, A2A_ERROR_CODES.taskNotFound, "task_not_found");
|
|
127
|
+
return rpcResultResponse(id, { task: projectStoredTask(state) });
|
|
128
|
+
}
|
|
129
|
+
case "ListTasks":
|
|
130
|
+
case "CancelTask":
|
|
131
|
+
case "SubscribeToTask":
|
|
132
|
+
case "CreateTaskPushNotificationConfig":
|
|
133
|
+
case "GetTaskPushNotificationConfig":
|
|
134
|
+
case "ListTaskPushNotificationConfigs":
|
|
135
|
+
case "DeleteTaskPushNotificationConfig":
|
|
136
|
+
case "GetExtendedAgentCard":
|
|
137
|
+
return rpcErrorResponse(id, A2A_ERROR_CODES.unsupportedOperation, "unsupported_operation");
|
|
138
|
+
default:
|
|
139
|
+
return rpcErrorResponse(id, A2A_ERROR_CODES.methodNotFound, "method_not_found");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function prepareTurn(params) {
|
|
143
|
+
const message = asRecord(params.message);
|
|
144
|
+
if (message === undefined)
|
|
145
|
+
return { error: { code: A2A_ERROR_CODES.invalidParams, message: "invalid_params" } };
|
|
146
|
+
const metadata = asRecord(message.metadata) ?? {};
|
|
147
|
+
const taskId = typeof message.taskId === "string" ? message.taskId : undefined;
|
|
148
|
+
const resume = parseResume(metadata.resume);
|
|
149
|
+
if (taskId !== undefined && !TURN_ID_PATTERN.test(taskId))
|
|
150
|
+
return { error: { code: A2A_ERROR_CODES.invalidParams, message: "invalid_task_id" } };
|
|
151
|
+
// A message that names a task must carry the continuation: the runtime keeps state token-only,
|
|
152
|
+
// so there is nothing to continue from without it.
|
|
153
|
+
if (taskId !== undefined && resume === undefined)
|
|
154
|
+
return { error: { code: A2A_ERROR_CODES.invalidParams, message: "resume_metadata_required" } };
|
|
155
|
+
if (resume !== undefined && taskId === undefined)
|
|
156
|
+
return { error: { code: A2A_ERROR_CODES.invalidParams, message: "task_id_required" } };
|
|
157
|
+
return {
|
|
158
|
+
turnId: taskId ?? crypto.randomUUID(),
|
|
159
|
+
input: Object.hasOwn(metadata, "input") ? metadata.input : firstTextPart(message.parts),
|
|
160
|
+
resume,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function firstTextPart(parts) {
|
|
164
|
+
if (!Array.isArray(parts))
|
|
165
|
+
return undefined;
|
|
166
|
+
for (const part of parts) {
|
|
167
|
+
const record = asRecord(part);
|
|
168
|
+
if (record !== undefined && typeof record.text === "string")
|
|
169
|
+
return record.text;
|
|
170
|
+
}
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
function streamingResponse(id, turnId, start) {
|
|
174
|
+
const stream = createAgentEvidenceStream();
|
|
175
|
+
const body = new ReadableStream({
|
|
176
|
+
async start(controller) {
|
|
177
|
+
const encoder = new TextEncoder();
|
|
178
|
+
const send = (payload) => {
|
|
179
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(payload)}\n\n`));
|
|
180
|
+
};
|
|
181
|
+
const run = start(stream);
|
|
182
|
+
// The evidence stream must terminate whether the run resolves or throws, or the `for await`
|
|
183
|
+
// below would hang; the run result (or the error) is still reported in-band after it drains.
|
|
184
|
+
run
|
|
185
|
+
.then(() => { }, () => { })
|
|
186
|
+
.finally(() => stream.complete());
|
|
187
|
+
try {
|
|
188
|
+
for await (const evidence of stream) {
|
|
189
|
+
send(rpcResult(id, {
|
|
190
|
+
statusUpdate: {
|
|
191
|
+
taskId: turnId,
|
|
192
|
+
contextId: turnId,
|
|
193
|
+
status: { state: "TASK_STATE_WORKING" },
|
|
194
|
+
final: false,
|
|
195
|
+
metadata: { evidence },
|
|
196
|
+
},
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
199
|
+
const result = await run;
|
|
200
|
+
if (result.status === "completed" && result.error === undefined) {
|
|
201
|
+
send(rpcResult(id, {
|
|
202
|
+
artifactUpdate: {
|
|
203
|
+
taskId: turnId,
|
|
204
|
+
contextId: turnId,
|
|
205
|
+
artifact: outputArtifact(turnId, result.output),
|
|
206
|
+
lastChunk: true,
|
|
207
|
+
},
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
const task = projectTask(turnId, result);
|
|
211
|
+
send(rpcResult(id, {
|
|
212
|
+
statusUpdate: {
|
|
213
|
+
taskId: turnId,
|
|
214
|
+
contextId: turnId,
|
|
215
|
+
status: task.status,
|
|
216
|
+
final: true,
|
|
217
|
+
...(task.metadata === undefined ? {} : { metadata: task.metadata }),
|
|
218
|
+
},
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
send(rpcError(id, A2A_ERROR_CODES.internalError, "run_failed"));
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
controller.close();
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
return new Response(body, {
|
|
230
|
+
status: 200,
|
|
231
|
+
headers: {
|
|
232
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
233
|
+
"cache-control": "no-cache, no-transform",
|
|
234
|
+
connection: "keep-alive",
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
/** Project a run result onto the A2A task shape. Evidence is token-only by design, so it passes through. */
|
|
239
|
+
function projectTask(turnId, result) {
|
|
240
|
+
const base = { id: turnId, contextId: turnId };
|
|
241
|
+
if (result.status === "completed") {
|
|
242
|
+
if (result.error !== undefined) {
|
|
243
|
+
return {
|
|
244
|
+
...base,
|
|
245
|
+
status: { state: "TASK_STATE_FAILED" },
|
|
246
|
+
metadata: { evidence: result.evidence, error: result.error },
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
...base,
|
|
251
|
+
status: { state: "TASK_STATE_COMPLETED" },
|
|
252
|
+
artifacts: [outputArtifact(turnId, result.output)],
|
|
253
|
+
metadata: { evidence: result.evidence },
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
if (result.status === "suspended") {
|
|
257
|
+
const state = result.reason === "cancelled" ? "TASK_STATE_CANCELED" : "TASK_STATE_INPUT_REQUIRED";
|
|
258
|
+
return {
|
|
259
|
+
...base,
|
|
260
|
+
status: {
|
|
261
|
+
state,
|
|
262
|
+
message: {
|
|
263
|
+
messageId: `${turnId}:pending`,
|
|
264
|
+
taskId: turnId,
|
|
265
|
+
role: "ROLE_AGENT",
|
|
266
|
+
parts: [],
|
|
267
|
+
metadata: { reason: result.reason, continuation: result.pending },
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
metadata: { evidence: result.evidence },
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
// `continue` never escapes the run loop; project it defensively as still-working.
|
|
274
|
+
return {
|
|
275
|
+
...base,
|
|
276
|
+
status: { state: "TASK_STATE_WORKING" },
|
|
277
|
+
metadata: { evidence: result.evidence },
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
/** Project stored turn state (GetTask). Stored state cannot distinguish a failed completion. */
|
|
281
|
+
function projectStoredTask(state) {
|
|
282
|
+
const taskState = state.status === "completed"
|
|
283
|
+
? "TASK_STATE_COMPLETED"
|
|
284
|
+
: state.status === "suspended"
|
|
285
|
+
? "TASK_STATE_INPUT_REQUIRED"
|
|
286
|
+
: "TASK_STATE_WORKING";
|
|
287
|
+
return {
|
|
288
|
+
id: state.turnId,
|
|
289
|
+
contextId: state.turnId,
|
|
290
|
+
status: { state: taskState },
|
|
291
|
+
metadata: {
|
|
292
|
+
evidence: state.evidence,
|
|
293
|
+
...(state.pending === undefined ? {} : { continuation: state.pending }),
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function outputArtifact(turnId, output) {
|
|
298
|
+
return {
|
|
299
|
+
artifactId: `${turnId}:output`,
|
|
300
|
+
name: "output",
|
|
301
|
+
parts: [typeof output === "string" ? { text: output } : { text: JSON.stringify(output) }],
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function parseResume(raw) {
|
|
305
|
+
const record = asRecord(raw);
|
|
306
|
+
if (record === undefined)
|
|
307
|
+
return undefined;
|
|
308
|
+
const continuation = asRecord(record.continuation);
|
|
309
|
+
if (continuation === undefined)
|
|
310
|
+
return undefined;
|
|
311
|
+
const kind = continuation.kind;
|
|
312
|
+
if (typeof kind !== "string" || !PENDING_KINDS.includes(kind))
|
|
313
|
+
return undefined;
|
|
314
|
+
if (typeof continuation.effectId !== "string")
|
|
315
|
+
return undefined;
|
|
316
|
+
if (continuation.tool !== undefined && typeof continuation.tool !== "string")
|
|
317
|
+
return undefined;
|
|
318
|
+
const approval = asRecord(record.approval);
|
|
319
|
+
return {
|
|
320
|
+
continuation: {
|
|
321
|
+
kind: kind,
|
|
322
|
+
...(continuation.tool === undefined ? {} : { tool: continuation.tool }),
|
|
323
|
+
...(Object.hasOwn(continuation, "input") ? { input: continuation.input } : {}),
|
|
324
|
+
effectId: continuation.effectId,
|
|
325
|
+
},
|
|
326
|
+
...(approval !== undefined && typeof approval.granted === "boolean"
|
|
327
|
+
? {
|
|
328
|
+
approval: {
|
|
329
|
+
granted: approval.granted,
|
|
330
|
+
...(typeof approval.reason === "string" ? { reason: approval.reason } : {}),
|
|
331
|
+
},
|
|
332
|
+
}
|
|
333
|
+
: {}),
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
function rpcResult(id, result) {
|
|
337
|
+
return { jsonrpc: "2.0", id, result };
|
|
338
|
+
}
|
|
339
|
+
function rpcError(id, code, message) {
|
|
340
|
+
return { jsonrpc: "2.0", id, error: { code, message } };
|
|
341
|
+
}
|
|
342
|
+
function rpcResultResponse(id, result) {
|
|
343
|
+
return jsonResponse(200, rpcResult(id, result));
|
|
344
|
+
}
|
|
345
|
+
function rpcErrorResponse(id, code, message) {
|
|
346
|
+
return jsonResponse(200, rpcError(id, code, message));
|
|
347
|
+
}
|
|
348
|
+
function asRpcId(value) {
|
|
349
|
+
if (typeof value === "string")
|
|
350
|
+
return value;
|
|
351
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
352
|
+
return value;
|
|
353
|
+
return undefined;
|
|
354
|
+
}
|
|
355
|
+
function asRecord(value) {
|
|
356
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
357
|
+
return undefined;
|
|
358
|
+
return value;
|
|
359
|
+
}
|
|
360
|
+
function jsonResponse(status, body) {
|
|
361
|
+
return new Response(JSON.stringify(body), {
|
|
362
|
+
status,
|
|
363
|
+
headers: { "content-type": "application/json; charset=utf-8" },
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
function render(response) {
|
|
367
|
+
return response instanceof Response ? response : toResponse(response, EMPTY_RESPONSE_CONTROLS);
|
|
368
|
+
}
|
|
369
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nifrajs/a2a",
|
|
3
|
+
"version": "3.2.0",
|
|
4
|
+
"description": "Mount a nifra agent as an Agent2Agent (A2A) protocol server - the agent card on GET, the A2A 1.0 JSON-RPC binding on POST with SendMessage, streaming SendStreamingMessage over SSE, and GetTask projected from the runner's turn state. A protocol bridge over @nifrajs/agent; the model, state store, and approval transport stay injected per request.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"bun",
|
|
7
|
+
"typescript",
|
|
8
|
+
"nifra",
|
|
9
|
+
"a2a",
|
|
10
|
+
"agent2agent",
|
|
11
|
+
"agents",
|
|
12
|
+
"json-rpc",
|
|
13
|
+
"sse",
|
|
14
|
+
"interop"
|
|
15
|
+
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"module": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"bun": "./src/index.ts",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"src"
|
|
30
|
+
],
|
|
31
|
+
"sideEffects": false,
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc -p tsconfig.build.json && bun run ../../scripts/fix-dts.ts dist"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@nifrajs/agent": "^3.2.0",
|
|
37
|
+
"@nifrajs/core": "^3.2.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependenciesMeta": {
|
|
40
|
+
"@nifrajs/core": {
|
|
41
|
+
"optional": true
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@nifrajs/agent": "workspace:*",
|
|
46
|
+
"@nifrajs/core": "workspace:*",
|
|
47
|
+
"@nifrajs/schema": "workspace:*"
|
|
48
|
+
},
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"repository": {
|
|
54
|
+
"type": "git",
|
|
55
|
+
"url": "git+https://github.com/nifrajs/nifra.git",
|
|
56
|
+
"directory": "packages/a2a"
|
|
57
|
+
},
|
|
58
|
+
"bugs": {
|
|
59
|
+
"url": "https://github.com/nifrajs/nifra/issues"
|
|
60
|
+
},
|
|
61
|
+
"homepage": "https://github.com/nifrajs/nifra#readme"
|
|
62
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nifrajs/a2a` - mount a nifra agent as an Agent2Agent (A2A) protocol server.
|
|
3
|
+
*
|
|
4
|
+
* The package is a protocol bridge over the `@nifrajs/agent` runner, mirroring the `mountAgent`
|
|
5
|
+
* seam: HTTP in, the bounded runner in the middle, A2A JSON-RPC out. The request body is read
|
|
6
|
+
* through core's single bounded, proto-guarded framing lane (`@nifrajs/core/edge-kit`); the model,
|
|
7
|
+
* durable state store, and approval transport arrive through a per-request `ports` factory and never
|
|
8
|
+
* live in this package.
|
|
9
|
+
*
|
|
10
|
+
* Wire contract (A2A protocol version 1.0, JSON-RPC binding):
|
|
11
|
+
* - `GET {cardPath}` (default `/.well-known/agent-card.json`) -> the agent card.
|
|
12
|
+
* - `POST {path}` (default `/a2a`) -> a JSON-RPC 2.0 request:
|
|
13
|
+
* - `SendMessage` runs one turn and returns `{ task }` with a terminal or interrupted status.
|
|
14
|
+
* - `SendStreamingMessage` responds `text/event-stream`; each SSE `data:` field is a complete
|
|
15
|
+
* JSON-RPC response whose result is `{ statusUpdate }` per step-evidence item, then
|
|
16
|
+
* `{ artifactUpdate }` for a produced output, then a final `{ statusUpdate }` with
|
|
17
|
+
* `final: true`.
|
|
18
|
+
* - `GetTask` projects the stored turn state for a task id (requires `ports.state`).
|
|
19
|
+
* - Every other spec method returns `UnsupportedOperationError` (-32004): the seam is stateless
|
|
20
|
+
* per request, so cross-request cancellation and subscription registries belong to the caller.
|
|
21
|
+
* - A task is one agent turn: `taskId` == the runner's `turnId`. A suspended run surfaces as
|
|
22
|
+
* `TASK_STATE_INPUT_REQUIRED` whose status message carries the pending continuation in
|
|
23
|
+
* `metadata`. To resume, send the continuation back via `message.taskId` +
|
|
24
|
+
* `message.metadata.resume` (`{ continuation, approval? }`); the runtime keeps state token-only,
|
|
25
|
+
* so a suspended tool's input must be replayed in `continuation.input`.
|
|
26
|
+
* - Agent input is `message.metadata.input` when present, otherwise the first `text` part.
|
|
27
|
+
* Structured output is returned as a `text` part carrying JSON.
|
|
28
|
+
* - The seam performs no authentication or authorization. Wrap it with the app's own route guards,
|
|
29
|
+
* and scope the store/model returned by `ports` to the caller.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import {
|
|
33
|
+
type AgentDefinition,
|
|
34
|
+
type AgentPendingKind,
|
|
35
|
+
type AgentPorts,
|
|
36
|
+
type AgentRunResult,
|
|
37
|
+
type AgentTurnInput,
|
|
38
|
+
type AgentTurnState,
|
|
39
|
+
combineAgentTelemetry,
|
|
40
|
+
createAgentState,
|
|
41
|
+
resumeAgent,
|
|
42
|
+
runAgent,
|
|
43
|
+
} from "@nifrajs/agent"
|
|
44
|
+
import { createAgentEvidenceStream } from "@nifrajs/agent/events"
|
|
45
|
+
import {
|
|
46
|
+
EMPTY_RESPONSE_CONTROLS,
|
|
47
|
+
type ProtoPoisoning,
|
|
48
|
+
type ResponseResult,
|
|
49
|
+
readBodyFramed,
|
|
50
|
+
toResponse,
|
|
51
|
+
} from "@nifrajs/core/edge-kit"
|
|
52
|
+
import type { StandardSchemaV1 } from "@nifrajs/core/schema"
|
|
53
|
+
|
|
54
|
+
export const A2A_PROTOCOL_VERSION = "1.0"
|
|
55
|
+
export const AGENT_CARD_WELL_KNOWN_PATH = "/.well-known/agent-card.json"
|
|
56
|
+
|
|
57
|
+
const DEFAULT_PATH = "/a2a"
|
|
58
|
+
const DEFAULT_MAX_BODY_BYTES = 1_000_000
|
|
59
|
+
const PENDING_KINDS: readonly AgentPendingKind[] = ["approval", "budget", "model", "cancelled"]
|
|
60
|
+
const TURN_ID_PATTERN = /^[a-zA-Z0-9._:-]{1,128}$/
|
|
61
|
+
|
|
62
|
+
// JSON-RPC 2.0 standard codes plus the A2A-specific range.
|
|
63
|
+
export const A2A_ERROR_CODES = {
|
|
64
|
+
parseError: -32700,
|
|
65
|
+
invalidRequest: -32600,
|
|
66
|
+
methodNotFound: -32601,
|
|
67
|
+
invalidParams: -32602,
|
|
68
|
+
internalError: -32603,
|
|
69
|
+
taskNotFound: -32001,
|
|
70
|
+
unsupportedOperation: -32004,
|
|
71
|
+
} as const
|
|
72
|
+
|
|
73
|
+
export type A2ATaskState =
|
|
74
|
+
| "TASK_STATE_SUBMITTED"
|
|
75
|
+
| "TASK_STATE_WORKING"
|
|
76
|
+
| "TASK_STATE_INPUT_REQUIRED"
|
|
77
|
+
| "TASK_STATE_COMPLETED"
|
|
78
|
+
| "TASK_STATE_FAILED"
|
|
79
|
+
| "TASK_STATE_CANCELED"
|
|
80
|
+
|
|
81
|
+
export interface A2AAgentSkill {
|
|
82
|
+
readonly id: string
|
|
83
|
+
readonly name: string
|
|
84
|
+
readonly description: string
|
|
85
|
+
readonly tags: readonly string[]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface A2AAgentCard {
|
|
89
|
+
readonly protocolVersion: string
|
|
90
|
+
readonly name: string
|
|
91
|
+
readonly description: string
|
|
92
|
+
readonly url: string
|
|
93
|
+
readonly version: string
|
|
94
|
+
readonly capabilities: { readonly streaming: boolean }
|
|
95
|
+
readonly defaultInputModes: readonly string[]
|
|
96
|
+
readonly defaultOutputModes: readonly string[]
|
|
97
|
+
readonly skills: readonly A2AAgentSkill[]
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Caller-supplied card identity; everything else derives from the agent definition. */
|
|
101
|
+
export interface A2ACardInfo {
|
|
102
|
+
/** Public URL of the JSON-RPC endpoint, as advertised to A2A clients. */
|
|
103
|
+
readonly url: string
|
|
104
|
+
/** Version of the exposed agent (the deployment's, not the protocol's). */
|
|
105
|
+
readonly version: string
|
|
106
|
+
readonly name?: string
|
|
107
|
+
readonly description?: string
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The structural slice of a route context the seam needs. */
|
|
111
|
+
export interface A2ARouteContext {
|
|
112
|
+
readonly req: Request
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** The structural slice of a nifra server `mountA2A` needs. */
|
|
116
|
+
export interface A2AMountableApp {
|
|
117
|
+
get(path: string, handler: (c: A2ARouteContext) => Response | Promise<Response>): unknown
|
|
118
|
+
post(path: string, handler: (c: A2ARouteContext) => Response | Promise<Response>): unknown
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface MountA2AOptions<
|
|
122
|
+
InputSchema extends StandardSchemaV1,
|
|
123
|
+
OutputSchema extends StandardSchemaV1,
|
|
124
|
+
> {
|
|
125
|
+
readonly agent: AgentDefinition<InputSchema, OutputSchema>
|
|
126
|
+
readonly card: A2ACardInfo
|
|
127
|
+
/** JSON-RPC endpoint path. Default `/a2a`. */
|
|
128
|
+
readonly path?: string
|
|
129
|
+
/** Agent card path. Default `/.well-known/agent-card.json`. */
|
|
130
|
+
readonly cardPath?: string
|
|
131
|
+
/** Bounded run length passed to the runner. */
|
|
132
|
+
readonly maxTurns?: number
|
|
133
|
+
/** Maximum request body size in bytes. Default 1_000_000. */
|
|
134
|
+
readonly maxBodyBytes?: number
|
|
135
|
+
/** Prototype-poisoning policy for the framing lane. Default `"reject"`. */
|
|
136
|
+
readonly protoPoisoning?: ProtoPoisoning
|
|
137
|
+
/**
|
|
138
|
+
* Build the ports for one request - the model, durable state store, approval transport,
|
|
139
|
+
* capabilities, and budgets. Receives the route context so the caller can scope every port to the
|
|
140
|
+
* request subject.
|
|
141
|
+
*/
|
|
142
|
+
readonly ports: (c: A2ARouteContext) => AgentPorts | Promise<AgentPorts>
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Derive a spec-shaped agent card from the agent definition plus the caller's identity fields. */
|
|
146
|
+
export function agentCard<
|
|
147
|
+
InputSchema extends StandardSchemaV1,
|
|
148
|
+
OutputSchema extends StandardSchemaV1,
|
|
149
|
+
>(agent: AgentDefinition<InputSchema, OutputSchema>, info: A2ACardInfo): A2AAgentCard {
|
|
150
|
+
return {
|
|
151
|
+
protocolVersion: A2A_PROTOCOL_VERSION,
|
|
152
|
+
name: info.name ?? agent.name,
|
|
153
|
+
description: info.description ?? agent.instruction,
|
|
154
|
+
url: info.url,
|
|
155
|
+
version: info.version,
|
|
156
|
+
capabilities: { streaming: true },
|
|
157
|
+
defaultInputModes: ["application/json", "text/plain"],
|
|
158
|
+
defaultOutputModes: ["application/json", "text/plain"],
|
|
159
|
+
skills: agent.tools.map((tool) => ({
|
|
160
|
+
id: tool.name,
|
|
161
|
+
name: tool.name,
|
|
162
|
+
description: tool.description,
|
|
163
|
+
tags: [tool.capability],
|
|
164
|
+
})),
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Mount an agent as an A2A server: the agent card on GET, the JSON-RPC binding on POST. */
|
|
169
|
+
export function mountA2A<
|
|
170
|
+
InputSchema extends StandardSchemaV1,
|
|
171
|
+
OutputSchema extends StandardSchemaV1,
|
|
172
|
+
>(app: A2AMountableApp, options: MountA2AOptions<InputSchema, OutputSchema>): void {
|
|
173
|
+
const path = options.path ?? DEFAULT_PATH
|
|
174
|
+
const cardPath = options.cardPath ?? AGENT_CARD_WELL_KNOWN_PATH
|
|
175
|
+
const maxBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES
|
|
176
|
+
const proto = options.protoPoisoning ?? "reject"
|
|
177
|
+
const runOptions = options.maxTurns === undefined ? {} : { maxTurns: options.maxTurns }
|
|
178
|
+
const card = agentCard(options.agent, options.card)
|
|
179
|
+
|
|
180
|
+
app.get(cardPath, () => jsonResponse(200, card))
|
|
181
|
+
app.post(path, (c) =>
|
|
182
|
+
readBodyFramed<Response>(
|
|
183
|
+
c.req,
|
|
184
|
+
maxBytes,
|
|
185
|
+
proto,
|
|
186
|
+
(parsed) => dispatch(c, parsed, options, runOptions),
|
|
187
|
+
(rejection) => render(rejection),
|
|
188
|
+
() => rpcErrorResponse(null, A2A_ERROR_CODES.parseError, "parse_error"),
|
|
189
|
+
).catch(() => rpcErrorResponse(null, A2A_ERROR_CODES.internalError, "internal_error")),
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
type RpcId = string | number
|
|
194
|
+
type RunStart = (telemetry?: AgentPorts["telemetry"]) => Promise<AgentRunResult<unknown>>
|
|
195
|
+
|
|
196
|
+
async function dispatch<
|
|
197
|
+
InputSchema extends StandardSchemaV1,
|
|
198
|
+
OutputSchema extends StandardSchemaV1,
|
|
199
|
+
>(
|
|
200
|
+
c: A2ARouteContext,
|
|
201
|
+
parsed: unknown,
|
|
202
|
+
options: MountA2AOptions<InputSchema, OutputSchema>,
|
|
203
|
+
runOptions: { readonly maxTurns?: number },
|
|
204
|
+
): Promise<Response> {
|
|
205
|
+
const request = asRecord(parsed)
|
|
206
|
+
const id = request === undefined ? undefined : asRpcId(request.id)
|
|
207
|
+
if (request === undefined || request.jsonrpc !== "2.0" || id === undefined)
|
|
208
|
+
return rpcErrorResponse(null, A2A_ERROR_CODES.invalidRequest, "invalid_request")
|
|
209
|
+
const method = request.method
|
|
210
|
+
if (typeof method !== "string")
|
|
211
|
+
return rpcErrorResponse(id, A2A_ERROR_CODES.invalidRequest, "invalid_request")
|
|
212
|
+
const params = asRecord(request.params) ?? {}
|
|
213
|
+
|
|
214
|
+
switch (method) {
|
|
215
|
+
case "SendMessage":
|
|
216
|
+
case "SendStreamingMessage": {
|
|
217
|
+
const turn = prepareTurn(params)
|
|
218
|
+
if ("error" in turn) return rpcErrorResponse(id, turn.error.code, turn.error.message)
|
|
219
|
+
const basePorts = await options.ports(c)
|
|
220
|
+
const start: RunStart = (telemetry) => {
|
|
221
|
+
// Compose rather than replace: the SSE evidence stream must not displace a telemetry
|
|
222
|
+
// port the caller injected through `ports`.
|
|
223
|
+
const combined = combineAgentTelemetry(basePorts.telemetry, telemetry)
|
|
224
|
+
const ports = combined === undefined ? basePorts : { ...basePorts, telemetry: combined }
|
|
225
|
+
if (turn.resume !== undefined) {
|
|
226
|
+
return resumeAgent(
|
|
227
|
+
options.agent,
|
|
228
|
+
turn.turnId,
|
|
229
|
+
{ value: turn.input, resume: turn.resume },
|
|
230
|
+
ports,
|
|
231
|
+
runOptions,
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
return runAgent(options.agent, { value: turn.input }, ports, {
|
|
235
|
+
...runOptions,
|
|
236
|
+
state: createAgentState(turn.turnId),
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
if (method === "SendMessage") {
|
|
240
|
+
try {
|
|
241
|
+
return rpcResultResponse(id, { task: projectTask(turn.turnId, await start()) })
|
|
242
|
+
} catch {
|
|
243
|
+
return rpcErrorResponse(id, A2A_ERROR_CODES.taskNotFound, "task_not_resumable")
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return streamingResponse(id, turn.turnId, start)
|
|
247
|
+
}
|
|
248
|
+
case "GetTask": {
|
|
249
|
+
const taskId = typeof params.id === "string" ? params.id : undefined
|
|
250
|
+
if (taskId === undefined)
|
|
251
|
+
return rpcErrorResponse(id, A2A_ERROR_CODES.invalidParams, "invalid_params")
|
|
252
|
+
const basePorts = await options.ports(c)
|
|
253
|
+
const state = await basePorts.state?.load(taskId)
|
|
254
|
+
if (state === undefined)
|
|
255
|
+
return rpcErrorResponse(id, A2A_ERROR_CODES.taskNotFound, "task_not_found")
|
|
256
|
+
return rpcResultResponse(id, { task: projectStoredTask(state) })
|
|
257
|
+
}
|
|
258
|
+
case "ListTasks":
|
|
259
|
+
case "CancelTask":
|
|
260
|
+
case "SubscribeToTask":
|
|
261
|
+
case "CreateTaskPushNotificationConfig":
|
|
262
|
+
case "GetTaskPushNotificationConfig":
|
|
263
|
+
case "ListTaskPushNotificationConfigs":
|
|
264
|
+
case "DeleteTaskPushNotificationConfig":
|
|
265
|
+
case "GetExtendedAgentCard":
|
|
266
|
+
return rpcErrorResponse(id, A2A_ERROR_CODES.unsupportedOperation, "unsupported_operation")
|
|
267
|
+
default:
|
|
268
|
+
return rpcErrorResponse(id, A2A_ERROR_CODES.methodNotFound, "method_not_found")
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
interface PreparedTurn {
|
|
273
|
+
readonly turnId: string
|
|
274
|
+
readonly input: unknown
|
|
275
|
+
readonly resume: AgentTurnInput["resume"]
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function prepareTurn(
|
|
279
|
+
params: Record<string, unknown>,
|
|
280
|
+
): PreparedTurn | { readonly error: { readonly code: number; readonly message: string } } {
|
|
281
|
+
const message = asRecord(params.message)
|
|
282
|
+
if (message === undefined)
|
|
283
|
+
return { error: { code: A2A_ERROR_CODES.invalidParams, message: "invalid_params" } }
|
|
284
|
+
const metadata = asRecord(message.metadata) ?? {}
|
|
285
|
+
const taskId = typeof message.taskId === "string" ? message.taskId : undefined
|
|
286
|
+
const resume = parseResume(metadata.resume)
|
|
287
|
+
if (taskId !== undefined && !TURN_ID_PATTERN.test(taskId))
|
|
288
|
+
return { error: { code: A2A_ERROR_CODES.invalidParams, message: "invalid_task_id" } }
|
|
289
|
+
// A message that names a task must carry the continuation: the runtime keeps state token-only,
|
|
290
|
+
// so there is nothing to continue from without it.
|
|
291
|
+
if (taskId !== undefined && resume === undefined)
|
|
292
|
+
return { error: { code: A2A_ERROR_CODES.invalidParams, message: "resume_metadata_required" } }
|
|
293
|
+
if (resume !== undefined && taskId === undefined)
|
|
294
|
+
return { error: { code: A2A_ERROR_CODES.invalidParams, message: "task_id_required" } }
|
|
295
|
+
return {
|
|
296
|
+
turnId: taskId ?? crypto.randomUUID(),
|
|
297
|
+
input: Object.hasOwn(metadata, "input") ? metadata.input : firstTextPart(message.parts),
|
|
298
|
+
resume,
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function firstTextPart(parts: unknown): unknown {
|
|
303
|
+
if (!Array.isArray(parts)) return undefined
|
|
304
|
+
for (const part of parts) {
|
|
305
|
+
const record = asRecord(part)
|
|
306
|
+
if (record !== undefined && typeof record.text === "string") return record.text
|
|
307
|
+
}
|
|
308
|
+
return undefined
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function streamingResponse(id: RpcId, turnId: string, start: RunStart): Response {
|
|
312
|
+
const stream = createAgentEvidenceStream()
|
|
313
|
+
const body = new ReadableStream<Uint8Array>({
|
|
314
|
+
async start(controller) {
|
|
315
|
+
const encoder = new TextEncoder()
|
|
316
|
+
const send = (payload: unknown): void => {
|
|
317
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(payload)}\n\n`))
|
|
318
|
+
}
|
|
319
|
+
const run = start(stream)
|
|
320
|
+
// The evidence stream must terminate whether the run resolves or throws, or the `for await`
|
|
321
|
+
// below would hang; the run result (or the error) is still reported in-band after it drains.
|
|
322
|
+
run
|
|
323
|
+
.then(
|
|
324
|
+
() => {},
|
|
325
|
+
() => {},
|
|
326
|
+
)
|
|
327
|
+
.finally(() => stream.complete())
|
|
328
|
+
try {
|
|
329
|
+
for await (const evidence of stream) {
|
|
330
|
+
send(
|
|
331
|
+
rpcResult(id, {
|
|
332
|
+
statusUpdate: {
|
|
333
|
+
taskId: turnId,
|
|
334
|
+
contextId: turnId,
|
|
335
|
+
status: { state: "TASK_STATE_WORKING" satisfies A2ATaskState },
|
|
336
|
+
final: false,
|
|
337
|
+
metadata: { evidence },
|
|
338
|
+
},
|
|
339
|
+
}),
|
|
340
|
+
)
|
|
341
|
+
}
|
|
342
|
+
const result = await run
|
|
343
|
+
if (result.status === "completed" && result.error === undefined) {
|
|
344
|
+
send(
|
|
345
|
+
rpcResult(id, {
|
|
346
|
+
artifactUpdate: {
|
|
347
|
+
taskId: turnId,
|
|
348
|
+
contextId: turnId,
|
|
349
|
+
artifact: outputArtifact(turnId, result.output),
|
|
350
|
+
lastChunk: true,
|
|
351
|
+
},
|
|
352
|
+
}),
|
|
353
|
+
)
|
|
354
|
+
}
|
|
355
|
+
const task = projectTask(turnId, result)
|
|
356
|
+
send(
|
|
357
|
+
rpcResult(id, {
|
|
358
|
+
statusUpdate: {
|
|
359
|
+
taskId: turnId,
|
|
360
|
+
contextId: turnId,
|
|
361
|
+
status: task.status,
|
|
362
|
+
final: true,
|
|
363
|
+
...(task.metadata === undefined ? {} : { metadata: task.metadata }),
|
|
364
|
+
},
|
|
365
|
+
}),
|
|
366
|
+
)
|
|
367
|
+
} catch {
|
|
368
|
+
send(rpcError(id, A2A_ERROR_CODES.internalError, "run_failed"))
|
|
369
|
+
} finally {
|
|
370
|
+
controller.close()
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
})
|
|
374
|
+
return new Response(body, {
|
|
375
|
+
status: 200,
|
|
376
|
+
headers: {
|
|
377
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
378
|
+
"cache-control": "no-cache, no-transform",
|
|
379
|
+
connection: "keep-alive",
|
|
380
|
+
},
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
interface ProjectedTask {
|
|
385
|
+
readonly id: string
|
|
386
|
+
readonly contextId: string
|
|
387
|
+
readonly status: { readonly state: A2ATaskState; readonly message?: unknown }
|
|
388
|
+
readonly artifacts?: readonly unknown[]
|
|
389
|
+
readonly metadata?: Record<string, unknown>
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** Project a run result onto the A2A task shape. Evidence is token-only by design, so it passes through. */
|
|
393
|
+
function projectTask(turnId: string, result: AgentRunResult<unknown>): ProjectedTask {
|
|
394
|
+
const base = { id: turnId, contextId: turnId }
|
|
395
|
+
if (result.status === "completed") {
|
|
396
|
+
if (result.error !== undefined) {
|
|
397
|
+
return {
|
|
398
|
+
...base,
|
|
399
|
+
status: { state: "TASK_STATE_FAILED" },
|
|
400
|
+
metadata: { evidence: result.evidence, error: result.error },
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return {
|
|
404
|
+
...base,
|
|
405
|
+
status: { state: "TASK_STATE_COMPLETED" },
|
|
406
|
+
artifacts: [outputArtifact(turnId, result.output)],
|
|
407
|
+
metadata: { evidence: result.evidence },
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (result.status === "suspended") {
|
|
411
|
+
const state: A2ATaskState =
|
|
412
|
+
result.reason === "cancelled" ? "TASK_STATE_CANCELED" : "TASK_STATE_INPUT_REQUIRED"
|
|
413
|
+
return {
|
|
414
|
+
...base,
|
|
415
|
+
status: {
|
|
416
|
+
state,
|
|
417
|
+
message: {
|
|
418
|
+
messageId: `${turnId}:pending`,
|
|
419
|
+
taskId: turnId,
|
|
420
|
+
role: "ROLE_AGENT",
|
|
421
|
+
parts: [],
|
|
422
|
+
metadata: { reason: result.reason, continuation: result.pending },
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
metadata: { evidence: result.evidence },
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
// `continue` never escapes the run loop; project it defensively as still-working.
|
|
429
|
+
return {
|
|
430
|
+
...base,
|
|
431
|
+
status: { state: "TASK_STATE_WORKING" },
|
|
432
|
+
metadata: { evidence: result.evidence },
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** Project stored turn state (GetTask). Stored state cannot distinguish a failed completion. */
|
|
437
|
+
function projectStoredTask(state: AgentTurnState): ProjectedTask {
|
|
438
|
+
const taskState: A2ATaskState =
|
|
439
|
+
state.status === "completed"
|
|
440
|
+
? "TASK_STATE_COMPLETED"
|
|
441
|
+
: state.status === "suspended"
|
|
442
|
+
? "TASK_STATE_INPUT_REQUIRED"
|
|
443
|
+
: "TASK_STATE_WORKING"
|
|
444
|
+
return {
|
|
445
|
+
id: state.turnId,
|
|
446
|
+
contextId: state.turnId,
|
|
447
|
+
status: { state: taskState },
|
|
448
|
+
metadata: {
|
|
449
|
+
evidence: state.evidence,
|
|
450
|
+
...(state.pending === undefined ? {} : { continuation: state.pending }),
|
|
451
|
+
},
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function outputArtifact(turnId: string, output: unknown): Record<string, unknown> {
|
|
456
|
+
return {
|
|
457
|
+
artifactId: `${turnId}:output`,
|
|
458
|
+
name: "output",
|
|
459
|
+
parts: [typeof output === "string" ? { text: output } : { text: JSON.stringify(output) }],
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function parseResume(raw: unknown): AgentTurnInput["resume"] | undefined {
|
|
464
|
+
const record = asRecord(raw)
|
|
465
|
+
if (record === undefined) return undefined
|
|
466
|
+
const continuation = asRecord(record.continuation)
|
|
467
|
+
if (continuation === undefined) return undefined
|
|
468
|
+
const kind = continuation.kind
|
|
469
|
+
if (typeof kind !== "string" || !PENDING_KINDS.includes(kind as AgentPendingKind))
|
|
470
|
+
return undefined
|
|
471
|
+
if (typeof continuation.effectId !== "string") return undefined
|
|
472
|
+
if (continuation.tool !== undefined && typeof continuation.tool !== "string") return undefined
|
|
473
|
+
const approval = asRecord(record.approval)
|
|
474
|
+
return {
|
|
475
|
+
continuation: {
|
|
476
|
+
kind: kind as AgentPendingKind,
|
|
477
|
+
...(continuation.tool === undefined ? {} : { tool: continuation.tool as string }),
|
|
478
|
+
...(Object.hasOwn(continuation, "input") ? { input: continuation.input } : {}),
|
|
479
|
+
effectId: continuation.effectId,
|
|
480
|
+
},
|
|
481
|
+
...(approval !== undefined && typeof approval.granted === "boolean"
|
|
482
|
+
? {
|
|
483
|
+
approval: {
|
|
484
|
+
granted: approval.granted,
|
|
485
|
+
...(typeof approval.reason === "string" ? { reason: approval.reason } : {}),
|
|
486
|
+
},
|
|
487
|
+
}
|
|
488
|
+
: {}),
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function rpcResult(id: RpcId, result: unknown): Record<string, unknown> {
|
|
493
|
+
return { jsonrpc: "2.0", id, result }
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function rpcError(id: RpcId | null, code: number, message: string): Record<string, unknown> {
|
|
497
|
+
return { jsonrpc: "2.0", id, error: { code, message } }
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function rpcResultResponse(id: RpcId, result: unknown): Response {
|
|
501
|
+
return jsonResponse(200, rpcResult(id, result))
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function rpcErrorResponse(id: RpcId | null, code: number, message: string): Response {
|
|
505
|
+
return jsonResponse(200, rpcError(id, code, message))
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function asRpcId(value: unknown): RpcId | undefined {
|
|
509
|
+
if (typeof value === "string") return value
|
|
510
|
+
if (typeof value === "number" && Number.isFinite(value)) return value
|
|
511
|
+
return undefined
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
515
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return undefined
|
|
516
|
+
return value as Record<string, unknown>
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function jsonResponse(status: number, body: unknown): Response {
|
|
520
|
+
return new Response(JSON.stringify(body), {
|
|
521
|
+
status,
|
|
522
|
+
headers: { "content-type": "application/json; charset=utf-8" },
|
|
523
|
+
})
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function render(response: Response | ResponseResult): Response {
|
|
527
|
+
return response instanceof Response ? response : toResponse(response, EMPTY_RESPONSE_CONTROLS)
|
|
528
|
+
}
|