@ryubyte/dsh-a2a 0.1.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/src/jsonrpc.ts ADDED
@@ -0,0 +1,185 @@
1
+ /**
2
+ * A2A (Agent2Agent) Protocol v1.0 — JSON-RPC 2.0 transport client.
3
+ *
4
+ * Implements the JSONRPC protocol binding of A2A v1.0: a thin JSON-RPC 2.0
5
+ * client for unary calls plus an SSE reader for `SendStreamingMessage`.
6
+ */
7
+
8
+ import type { JsonRpcRequest, JsonRpcResponse, JsonRpcSuccess, StreamResponse } from './protocol.js';
9
+ import { A2AError } from './errors.js';
10
+
11
+ export interface JsonRpcClientOptions {
12
+ /** Timeout (ms) for a single unary call. Default 60000. */
13
+ timeoutMs?: number;
14
+ /** Optional bearer token. */
15
+ bearerToken?: string;
16
+ /** Custom fetch (tests). */
17
+ fetchImpl?: typeof fetch;
18
+ }
19
+
20
+ export interface JsonRpcCallOptions {
21
+ signal?: AbortSignal;
22
+ timeoutMs?: number;
23
+ }
24
+
25
+ export class JsonRpcClient {
26
+ readonly url: string;
27
+ private readonly timeoutMs: number;
28
+ private readonly bearerToken?: string;
29
+ private readonly fetchImpl: typeof fetch;
30
+ private seq = 0;
31
+
32
+ constructor(url: string, options: JsonRpcClientOptions = {}) {
33
+ this.url = url;
34
+ this.timeoutMs = options.timeoutMs ?? 60000;
35
+ this.bearerToken = options.bearerToken;
36
+ this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
37
+ }
38
+
39
+ private headers(extra?: Record<string, string>): Record<string, string> {
40
+ const h: Record<string, string> = {
41
+ 'content-type': 'application/json',
42
+ accept: 'application/json',
43
+ ...extra,
44
+ };
45
+ if (this.bearerToken) h.authorization = `Bearer ${this.bearerToken}`;
46
+ return h;
47
+ }
48
+
49
+ /** Perform a unary JSON-RPC call. */
50
+ async call<T>(method: string, params: unknown, options: JsonRpcCallOptions = {}): Promise<T> {
51
+ const req: JsonRpcRequest = {
52
+ jsonrpc: '2.0',
53
+ id: ++this.seq,
54
+ method,
55
+ params,
56
+ };
57
+ const ctrl = options.signal ? new AbortController() : undefined;
58
+ const onAbort = () => ctrl?.abort();
59
+ options.signal?.addEventListener('abort', onAbort);
60
+ const timeout = setTimeout(
61
+ () => ctrl?.abort(),
62
+ options.timeoutMs ?? this.timeoutMs,
63
+ );
64
+ try {
65
+ const res = await this.fetchImpl(this.url, {
66
+ method: 'POST',
67
+ headers: this.headers(),
68
+ body: JSON.stringify(req),
69
+ signal: ctrl?.signal,
70
+ });
71
+ if (!res.ok) {
72
+ throw new A2AError(
73
+ res.status,
74
+ `A2A JSON-RPC request failed (HTTP ${res.status}) at ${this.url}`,
75
+ undefined,
76
+ true,
77
+ );
78
+ }
79
+ const json = (await res.json()) as JsonRpcResponse;
80
+ if ('error' in json && json.error) {
81
+ throw A2AError.fromJsonRpc(json.error);
82
+ }
83
+ return (json as JsonRpcSuccess).result as T;
84
+ } catch (err) {
85
+ if (err instanceof Error && err.name === 'AbortError') {
86
+ throw new A2AError(408, `A2A JSON-RPC call "${method}" timed out`, undefined, true);
87
+ }
88
+ throw err;
89
+ } finally {
90
+ clearTimeout(timeout);
91
+ options.signal?.removeEventListener('abort', onAbort);
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Open a streaming JSON-RPC call (`SendStreamingMessage`) over SSE and
97
+ * yield the `StreamResponse` events as they arrive. The connection is
98
+ * closed when the returned async iterator is broken or the request is
99
+ * cancelled. Events are decoded from SSE `data:` lines (JSON-RPC result
100
+ * frames); per A2A v1.0 the JSON structure uses member names to
101
+ * discriminate `task` / `message` / `statusUpdate` / `artifactUpdate`.
102
+ */
103
+ async *stream<T = StreamResponse>(
104
+ method: string,
105
+ params: unknown,
106
+ options: JsonRpcCallOptions = {},
107
+ ): AsyncGenerator<T> {
108
+ const req: JsonRpcRequest = {
109
+ jsonrpc: '2.0',
110
+ id: ++this.seq,
111
+ method,
112
+ params,
113
+ };
114
+ const ctrl = new AbortController();
115
+ const onAbort = () => ctrl.abort();
116
+ options.signal?.addEventListener('abort', onAbort);
117
+ const timeout = setTimeout(
118
+ () => ctrl.abort(),
119
+ options.timeoutMs ?? this.timeoutMs,
120
+ );
121
+ let res: Response;
122
+ try {
123
+ res = await this.fetchImpl(this.url, {
124
+ method: 'POST',
125
+ headers: this.headers({ accept: 'text/event-stream' }),
126
+ body: JSON.stringify(req),
127
+ signal: ctrl.signal,
128
+ });
129
+ } catch (err) {
130
+ clearTimeout(timeout);
131
+ options.signal?.removeEventListener('abort', onAbort);
132
+ if (err instanceof Error && err.name === 'AbortError') {
133
+ throw new A2AError(408, `A2A stream "${method}" timed out`, undefined, true);
134
+ }
135
+ throw err;
136
+ }
137
+ if (!res.ok || !res.body) {
138
+ clearTimeout(timeout);
139
+ options.signal?.removeEventListener('abort', onAbort);
140
+ throw new A2AError(
141
+ res.status,
142
+ `A2A stream request failed (HTTP ${res.status}) at ${this.url}`,
143
+ undefined,
144
+ true,
145
+ );
146
+ }
147
+
148
+ try {
149
+ const reader = res.body.getReader();
150
+ const decoder = new TextDecoder();
151
+ let buffer = '';
152
+ for (;;) {
153
+ const { done, value } = await reader.read();
154
+ if (done) break;
155
+ buffer += decoder.decode(value, { stream: true });
156
+ // SSE frames are separated by a blank line.
157
+ const frames = buffer.split(/\r?\n\r?\n/);
158
+ buffer = frames.pop() ?? '';
159
+ for (const frame of frames) {
160
+ const data = frame
161
+ .split(/\r?\n/)
162
+ .filter((l) => l.startsWith('data:'))
163
+ .map((l) => l.slice(5).trimStart())
164
+ .join('\n');
165
+ if (!data) continue;
166
+ let payload: unknown;
167
+ try {
168
+ payload = JSON.parse(data);
169
+ } catch {
170
+ continue; // ignore malformed frames
171
+ }
172
+ // A JSON-RPC error frame terminates the stream.
173
+ const envelope = payload as JsonRpcResponse;
174
+ if (envelope && 'error' in envelope && envelope.error) {
175
+ throw A2AError.fromJsonRpc(envelope.error);
176
+ }
177
+ yield payload as T;
178
+ }
179
+ }
180
+ } finally {
181
+ clearTimeout(timeout);
182
+ options.signal?.removeEventListener('abort', onAbort);
183
+ }
184
+ }
185
+ }
@@ -0,0 +1,186 @@
1
+ /**
2
+ * dsh-a2a — outbound half: bridge remote A2A agent skills into DSH's tool
3
+ * registry (`ctx.tools`).
4
+ *
5
+ * Each skill on a remote AgentCard becomes a model-facing tool named
6
+ * `a2a__<agentName>__<skillId>` (normalized to the DSH function-name
7
+ * contract: `[A-Za-z0-9_-]`, ≤64 chars; on collision a deterministic hash is
8
+ * appended, mirroring dsh-mcp-client's naming rule).
9
+ */
10
+
11
+ import type { AgentCard, AgentSkill, Message, Part } from './protocol.js';
12
+ import { Role } from './protocol.js';
13
+ import { A2AClient } from './client.js';
14
+ import { A2AError } from './errors.js';
15
+
16
+ export interface ToolLike {
17
+ register?(definition: unknown): (() => void) | void;
18
+ }
19
+
20
+ export interface OutboundOptions {
21
+ /** Namespace prefix for tool names (lowercase, unique across instances). */
22
+ name: string;
23
+ agentCardUrl: string;
24
+ bearerToken?: string;
25
+ /** Per-call timeout in ms. Default 60000. */
26
+ timeoutMs?: number;
27
+ /** Register one generic tool per skill (default true). */
28
+ mapSkills?: boolean;
29
+ /**
30
+ * Optional connection-lifecycle observation: called once the AgentCard is
31
+ * fetched and the connection is usable. `connectionId` is the stable id the
32
+ * dashboard tracks this connection under.
33
+ */
34
+ onReady?: (info: { connectionId: string; card: AgentCard }) => void;
35
+ /** Called when the connection (and its tools) are torn down. */
36
+ onDispose?: (connectionId: string) => void;
37
+ }
38
+
39
+ function normalizeToolName(raw: string): string {
40
+ const norm = raw.replace(/[^A-Za-z0-9_-]/g, '_').slice(0, 64);
41
+ if (norm.length > 0 && /^[A-Za-z0-9_-]+$/.test(norm)) return norm;
42
+ // fallback: deterministic hash
43
+ let h = 0;
44
+ for (let i = 0; i < raw.length; i++) h = (h * 31 + raw.charCodeAt(i)) >>> 0;
45
+ return `_${h.toString(16)}`;
46
+ }
47
+
48
+ function skillToToolName(agentName: string, skill: AgentSkill): string {
49
+ const raw = `a2a__${agentName}__${skill.id}`;
50
+ const base = raw.replace(/[^A-Za-z0-9_-]/g, '_').slice(0, 64);
51
+ if (base === raw || base.length < 64) return base;
52
+ let h = 0;
53
+ for (let i = 0; i < raw.length; i++) h = (h * 31 + raw.charCodeAt(i)) >>> 0;
54
+ return `${base.slice(0, 56)}_${h.toString(16)}`;
55
+ }
56
+
57
+ /** Extract the first meaningful text from message parts. */
58
+ function partsToText(parts: Part[] | undefined): string {
59
+ if (!parts) return '';
60
+ return parts
61
+ .map((p) => {
62
+ if ('text' in p && p.text) return p.text;
63
+ if ('url' in p && p.url) return p.url;
64
+ if ('data' in p && p.data !== undefined) return JSON.stringify(p.data);
65
+ if ('raw' in p && p.raw) return `[raw ${p.mediaType ?? 'application/octet-stream'}]`;
66
+ return '';
67
+ })
68
+ .filter(Boolean)
69
+ .join('\n');
70
+ }
71
+
72
+ export interface RegisteredTool {
73
+ name: string;
74
+ /** Stable id of the client connection this tool belongs to. */
75
+ connectionId?: string;
76
+ dispose: () => void;
77
+ }
78
+
79
+ /**
80
+ * Register all skills of a remote agent on `ctx.tools` and return disposers.
81
+ */
82
+ export async function registerAgentTools(
83
+ tools: ToolLike,
84
+ options: OutboundOptions,
85
+ ): Promise<RegisteredTool[]> {
86
+ const connectionId = crypto.randomUUID();
87
+ const client = await A2AClient.connect(options.agentCardUrl, {
88
+ bearerToken: options.bearerToken,
89
+ timeoutMs: options.timeoutMs,
90
+ });
91
+ const card: AgentCard = client.card;
92
+ const skills = card.skills ?? [];
93
+ options.onReady?.({ connectionId, card });
94
+
95
+ let disposers: RegisteredTool[] = [];
96
+ if (options.mapSkills === false || skills.length === 0) {
97
+ // Fall back to a single generic tool for the whole agent.
98
+ const name = normalizeToolName(`a2a__${options.name}__agent`);
99
+ const dispose = tools.register?.(makeTool(name, client, {
100
+ id: 'agent',
101
+ name: options.name,
102
+ description: `${card.name}: ${card.description} (no skills advertised)`,
103
+ }));
104
+ if (dispose) disposers = [{ name, dispose }];
105
+ } else {
106
+ const out: RegisteredTool[] = [];
107
+ for (const skill of skills) {
108
+ const name = skillToToolName(options.name, skill);
109
+ const dispose = tools.register?.(makeTool(name, client, skill));
110
+ if (dispose) out.push({ name, dispose });
111
+ }
112
+ disposers = out;
113
+ }
114
+
115
+ return disposers.map((r) => ({
116
+ ...r,
117
+ connectionId,
118
+ dispose: () => {
119
+ r.dispose();
120
+ options.onDispose?.(connectionId);
121
+ },
122
+ }));
123
+ }
124
+
125
+ function makeTool(name: string, client: A2AClient, skill: Pick<AgentSkill, 'id' | 'name' | 'description' | 'examples'>) {
126
+ const description = [
127
+ skill.description,
128
+ ...(skill.examples?.length ? [`Examples: ${skill.examples.join(' | ')}`] : []),
129
+ ].filter(Boolean).join('\n');
130
+
131
+ return {
132
+ name,
133
+ description,
134
+ parameters: {
135
+ type: 'object',
136
+ properties: {
137
+ prompt: {
138
+ type: 'string',
139
+ description: 'The task or instruction to send to the remote agent.',
140
+ },
141
+ },
142
+ required: ['prompt'],
143
+ },
144
+ output: {
145
+ schema: { type: 'string' },
146
+ render: (_args: unknown, value: unknown) => [
147
+ { type: 'text', text: String(value) },
148
+ ],
149
+ },
150
+ async execute(args: { prompt: string }, exec: { signal?: AbortSignal }): Promise<string> {
151
+ const message: Message = {
152
+ messageId: crypto.randomUUID(),
153
+ role: Role.USER,
154
+ parts: [{ text: args.prompt }],
155
+ };
156
+ try {
157
+ const task = await client.sendAndWait(message, {}, { signal: exec.signal });
158
+ const text = partsToText(task.artifacts?.flatMap((a) => a.parts));
159
+ const failureMsg =
160
+ task.status.message?.parts?.find((p) => 'text' in p && p.text);
161
+ if (task.status.state === 'TASK_STATE_FAILED') {
162
+ throw new A2AError(
163
+ -32000,
164
+ `Remote agent task failed: ${text || (failureMsg && 'text' in failureMsg ? failureMsg.text : 'no detail')}`,
165
+ );
166
+ }
167
+ // INPUT_REQUIRED / AUTH_REQUIRED: surface the remote's clarification
168
+ // request as a *usable* tool output, so the model can answer and call
169
+ // the tool again — not as a hard error that stalls the conversation.
170
+ if (task.status.state === 'TASK_STATE_INPUT_REQUIRED' || task.status.state === 'TASK_STATE_AUTH_REQUIRED') {
171
+ const ask =
172
+ (failureMsg && 'text' in failureMsg ? failureMsg.text : '') ||
173
+ text ||
174
+ 'agent awaits input';
175
+ return `[remote agent ${task.status.state}] ${ask}`;
176
+ }
177
+ return text || '(remote agent returned no output)';
178
+ } catch (err) {
179
+ if (err instanceof A2AError && err.code === -32001 && err.message.includes('interrupted')) {
180
+ throw new Error(`Remote agent requires input: ${err.message}`);
181
+ }
182
+ throw err;
183
+ }
184
+ },
185
+ };
186
+ }
@@ -0,0 +1,308 @@
1
+ /**
2
+ * A2A (Agent2Agent) Protocol v1.0 — TypeScript type definitions.
3
+ *
4
+ * These types mirror the normative source of truth `specification/a2a.proto`
5
+ * (package `lf.a2a.v1`) from https://github.com/a2aproject/A2A, v1.0.0.
6
+ * JSON wire names are camelCase per ProtoJSON serialization; enum values use
7
+ * SCREAMING_SNAKE_CASE as mandated by A2A v1.0.
8
+ */
9
+
10
+ /** The lifecycle states of a Task (a2a.proto `TaskState`). */
11
+ export enum TaskState {
12
+ UNSPECIFIED = 'TASK_STATE_UNSPECIFIED',
13
+ SUBMITTED = 'TASK_STATE_SUBMITTED',
14
+ WORKING = 'TASK_STATE_WORKING',
15
+ COMPLETED = 'TASK_STATE_COMPLETED',
16
+ FAILED = 'TASK_STATE_FAILED',
17
+ CANCELED = 'TASK_STATE_CANCELED',
18
+ INPUT_REQUIRED = 'TASK_STATE_INPUT_REQUIRED',
19
+ REJECTED = 'TASK_STATE_REJECTED',
20
+ AUTH_REQUIRED = 'TASK_STATE_AUTH_REQUIRED',
21
+ }
22
+
23
+ export type TerminalTaskState =
24
+ | TaskState.COMPLETED
25
+ | TaskState.FAILED
26
+ | TaskState.CANCELED
27
+ | TaskState.REJECTED;
28
+
29
+ export type InterruptedTaskState = TaskState.INPUT_REQUIRED | TaskState.AUTH_REQUIRED;
30
+
31
+ export function isTerminal(state: TaskState): state is TerminalTaskState {
32
+ return (
33
+ state === TaskState.COMPLETED ||
34
+ state === TaskState.FAILED ||
35
+ state === TaskState.CANCELED ||
36
+ state === TaskState.REJECTED
37
+ );
38
+ }
39
+
40
+ /** The sender of a Message (a2a.proto `Role`). */
41
+ export enum Role {
42
+ UNSPECIFIED = 'ROLE_UNSPECIFIED',
43
+ USER = 'ROLE_USER',
44
+ AGENT = 'ROLE_AGENT',
45
+ }
46
+
47
+ /**
48
+ * A section of communication content (a2a.proto `Part`): exactly one of
49
+ * text / raw / url / data.
50
+ */
51
+ export type Part =
52
+ | { text: string; raw?: never; url?: never; data?: never; mediaType?: string; filename?: string; metadata?: Record<string, unknown> }
53
+ | { raw: string; text?: never; url?: never; data?: never; mediaType?: string; filename?: string; metadata?: Record<string, unknown> }
54
+ | { url: string; text?: never; raw?: never; data?: never; mediaType?: string; filename?: string; metadata?: Record<string, unknown> }
55
+ | { data: unknown; text?: never; raw?: never; url?: never; mediaType?: string; filename?: string; metadata?: Record<string, unknown> };
56
+
57
+ /** One unit of communication between client and server (a2a.proto `Message`). */
58
+ export interface Message {
59
+ messageId: string;
60
+ /** Required for server (ROLE_AGENT) messages. */
61
+ contextId?: string;
62
+ taskId?: string;
63
+ role: Role | string;
64
+ parts: Part[];
65
+ metadata?: Record<string, unknown>;
66
+ extensions?: string[];
67
+ referenceTaskIds?: string[];
68
+ }
69
+
70
+ /** Task output (a2a.proto `Artifact`). */
71
+ export interface Artifact {
72
+ artifactId: string;
73
+ name?: string;
74
+ parts: Part[];
75
+ metadata?: Record<string, unknown>;
76
+ /** Only present in task-artifact-update events. */
77
+ append?: boolean;
78
+ lastChunk?: boolean;
79
+ }
80
+
81
+ /** Status of a task (a2a.proto `TaskStatus`). */
82
+ export interface TaskStatus {
83
+ state: TaskState | string;
84
+ message?: Message;
85
+ /** ISO 8601 timestamp. */
86
+ timestamp?: string;
87
+ }
88
+
89
+ /** The core unit of action for A2A (a2a.proto `Task`). */
90
+ export interface Task {
91
+ id: string;
92
+ contextId?: string;
93
+ status: TaskStatus;
94
+ artifacts?: Artifact[];
95
+ history?: Message[];
96
+ metadata?: Record<string, unknown>;
97
+ }
98
+
99
+ /** Configuration for a send request (a2a.proto `SendMessageConfiguration`). */
100
+ export interface SendMessageConfiguration {
101
+ acceptedOutputModes?: string[];
102
+ taskPushNotificationConfig?: TaskPushNotificationConfig;
103
+ historyLength?: number;
104
+ returnImmediately?: boolean;
105
+ }
106
+
107
+ /** Push notification registration (a2a.proto `TaskPushNotificationConfig`). */
108
+ export interface TaskPushNotificationConfig {
109
+ tenant?: string;
110
+ id?: string;
111
+ taskId?: string;
112
+ url: string;
113
+ token?: string;
114
+ authentication?: AuthenticationInfo;
115
+ }
116
+
117
+ /** Authentication details for push (a2a.proto `AuthenticationInfo`). */
118
+ export interface AuthenticationInfo {
119
+ scheme: string;
120
+ credentials?: string;
121
+ }
122
+
123
+ /** Discriminated stream payloads (a2a.proto `StreamResponse`). */
124
+ export type StreamResponse =
125
+ | { task: Task; message?: never; statusUpdate?: never; artifactUpdate?: never }
126
+ | { message: Message; task?: never; statusUpdate?: never; artifactUpdate?: never }
127
+ | { statusUpdate: TaskStatusUpdateEvent; task?: never; message?: never; artifactUpdate?: never }
128
+ | { artifactUpdate: TaskArtifactUpdateEvent; task?: never; message?: never; statusUpdate?: never };
129
+
130
+ export interface TaskStatusUpdateEvent {
131
+ taskId: string;
132
+ contextId: string;
133
+ status: TaskStatus;
134
+ metadata?: Record<string, unknown>;
135
+ }
136
+
137
+ export interface TaskArtifactUpdateEvent {
138
+ taskId: string;
139
+ contextId: string;
140
+ artifact: Artifact;
141
+ append?: boolean;
142
+ lastChunk?: boolean;
143
+ metadata?: Record<string, unknown>;
144
+ }
145
+
146
+ /** Response payload of SendMessage (a2a.proto `SendMessageResponse`). */
147
+ export type SendMessageResponse =
148
+ | { task: Task; message?: never }
149
+ | { message: Message; task?: never };
150
+
151
+ /** A declaration of a protocol extension (a2a.proto `AgentExtension`). */
152
+ export interface AgentExtension {
153
+ uri: string;
154
+ description?: string;
155
+ required?: boolean;
156
+ params?: Record<string, unknown>;
157
+ }
158
+
159
+ /** Optional capabilities (a2a.proto `AgentCapabilities`). */
160
+ export interface AgentCapabilities {
161
+ streaming?: boolean;
162
+ pushNotifications?: boolean;
163
+ extensions?: AgentExtension[];
164
+ extendedAgentCard?: boolean;
165
+ }
166
+
167
+ /** A distinct capability an agent can perform (a2a.proto `AgentSkill`). */
168
+ export interface AgentSkill {
169
+ id: string;
170
+ name: string;
171
+ description: string;
172
+ tags?: string[];
173
+ examples?: string[];
174
+ inputModes?: string[];
175
+ outputModes?: string[];
176
+ securityRequirements?: SecurityRequirement[];
177
+ }
178
+
179
+ /** OAuth flows supported by an agent (v1.0: code, client-credentials, device-code). */
180
+ export interface OAuthFlows {
181
+ authorizationCode?: AuthorizationCodeOAuthFlow;
182
+ clientCredentials?: ClientCredentialsOAuthFlow;
183
+ deviceCode?: DeviceCodeOAuthFlow;
184
+ }
185
+
186
+ export interface AuthorizationCodeOAuthFlow {
187
+ authorizationUrl: string;
188
+ tokenUrl: string;
189
+ scopes?: Record<string, string>;
190
+ refreshUrl?: string;
191
+ pkceRequired?: boolean;
192
+ }
193
+
194
+ export interface ClientCredentialsOAuthFlow {
195
+ tokenUrl: string;
196
+ scopes?: Record<string, string>;
197
+ }
198
+
199
+ export interface DeviceCodeOAuthFlow {
200
+ deviceAuthorizationUrl: string;
201
+ tokenUrl: string;
202
+ scopes?: Record<string, string>;
203
+ }
204
+
205
+ /** Security scheme (v1.0 supports apiKey, http, oauth2, openIdConnect, mutualTls). */
206
+ export type SecurityScheme =
207
+ | { type: 'apiKey'; name: string; in: 'header' | 'query' | 'cookie'; description?: string }
208
+ | { type: 'http'; scheme: 'basic' | 'bearer' | 'digest'; bearerFormat?: string; description?: string }
209
+ | { type: 'oauth2'; flows: OAuthFlows; description?: string }
210
+ | { type: 'openIdConnect'; openIdConnectUrl: string; description?: string }
211
+ | { type: 'mutualTls'; description?: string };
212
+
213
+ export interface SecurityRequirement {
214
+ /** Map of scheme name → required scopes. */
215
+ schemes: Record<string, string[]>;
216
+ }
217
+
218
+ /** The provider of an agent (a2a.proto `AgentProvider`). */
219
+ export interface AgentProvider {
220
+ url: string;
221
+ organization: string;
222
+ }
223
+
224
+ /** JWS signature of an AgentCard (RFC 7515). */
225
+ export interface AgentCardSignature {
226
+ protected: string;
227
+ signature: string;
228
+ header?: Record<string, unknown>;
229
+ }
230
+
231
+ /** A target URL + transport + protocol version (a2a.proto `AgentInterface`). */
232
+ export interface AgentInterface {
233
+ url: string;
234
+ protocolBinding: 'JSONRPC' | 'GRPC' | 'HTTP+JSON' | string;
235
+ /** Opaque routing value; clients MUST echo it in requests when set. */
236
+ tenant?: string;
237
+ protocolVersion: string;
238
+ }
239
+
240
+ /** Self-describing agent manifest (a2a.proto `AgentCard`). */
241
+ export interface AgentCard {
242
+ name: string;
243
+ description: string;
244
+ version: string;
245
+ supportedInterfaces: AgentInterface[];
246
+ provider?: AgentProvider;
247
+ documentationUrl?: string;
248
+ capabilities: AgentCapabilities;
249
+ securitySchemes?: Record<string, SecurityScheme>;
250
+ securityRequirements?: SecurityRequirement[];
251
+ defaultInputModes: string[];
252
+ defaultOutputModes: string[];
253
+ skills?: AgentSkill[];
254
+ signatures?: AgentCardSignature[];
255
+ iconUrl?: string;
256
+ }
257
+
258
+ /** JSON-RPC 2.0 request/response/error envelopes used by the JSONRPC binding. */
259
+ export interface JsonRpcRequest {
260
+ jsonrpc: '2.0';
261
+ id: string | number | null;
262
+ method: string;
263
+ params?: unknown;
264
+ }
265
+
266
+ export interface JsonRpcSuccess {
267
+ jsonrpc: '2.0';
268
+ id: string | number | null;
269
+ result: unknown;
270
+ }
271
+
272
+ export interface JsonRpcError {
273
+ jsonrpc: '2.0';
274
+ id: string | number | null;
275
+ error: { code: number; message: string; data?: unknown };
276
+ }
277
+
278
+ export type JsonRpcResponse = JsonRpcSuccess | JsonRpcError;
279
+
280
+ /** A2A JSON-RPC method names (v1.0 canonical PascalCase as in a2a.proto RPCs). */
281
+ export const A2A_METHODS = {
282
+ sendMessage: 'SendMessage',
283
+ sendStreamingMessage: 'SendStreamingMessage',
284
+ getTask: 'GetTask',
285
+ listTasks: 'ListTasks',
286
+ cancelTask: 'CancelTask',
287
+ subscribeToTask: 'SubscribeToTask',
288
+ getExtendedAgentCard: 'GetExtendedAgentCard',
289
+ } as const;
290
+
291
+ /** Standard error codes from the A2A JSON-RPC binding. */
292
+ export const A2A_ERROR_CODES = {
293
+ INVALID_REQUEST: -32600,
294
+ METHOD_NOT_FOUND: -32601,
295
+ INVALID_PARAMS: -32602,
296
+ INTERNAL_ERROR: -32603,
297
+ TASK_NOT_FOUND: -32001,
298
+ TASK_CANCEL_NOT_ALLOWED: -32002,
299
+ TASK_ALREADY_CANCELED: -32003,
300
+ AGENT_CARD_NOT_FOUND: -32004,
301
+ AGENT_CARD_SIGNATURE_INVALID: -32005,
302
+ CONTEXT_NOT_FOUND: -32006,
303
+ AUTHENTICATION_REQUIRED: -32007,
304
+ PUSH_NOTIFICATION_CONFIG_NOT_FOUND: -32008,
305
+ EXTENSION_NOT_SUPPORTED: -32009,
306
+ UNSUPPORTED_OPERATION: -32010,
307
+ VERSION_NOT_SUPPORTED: -32011,
308
+ } as const;