@ap3x/a2a 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +27 -0
- package/dist/backend.d.ts +89 -0
- package/dist/backend.d.ts.map +1 -0
- package/dist/bindings/grpc.d.ts +121 -0
- package/dist/bindings/grpc.d.ts.map +1 -0
- package/dist/bindings/jsonrpc.d.ts +135 -0
- package/dist/bindings/jsonrpc.d.ts.map +1 -0
- package/dist/bindings/rest.d.ts +75 -0
- package/dist/bindings/rest.d.ts.map +1 -0
- package/dist/chunk-PYM7LV6R.js +1590 -0
- package/dist/client.d.ts +116 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/executor.d.ts +91 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/grpc-5DAU6EYQ.js +591 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +454 -0
- package/dist/model.d.ts +480 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/server.d.ts +60 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/task-store.d.ts +61 -0
- package/dist/task-store.d.ts.map +1 -0
- package/package.json +26 -15
- package/proto/a2a.proto +813 -0
- package/proto/google/api/annotations.proto +6 -0
- package/proto/google/api/client.proto +4 -0
- package/proto/google/api/field_behavior.proto +4 -0
- package/src/__tests__/a2a-auth-expired.test.ts +0 -84
- package/src/__tests__/a2a.test.ts +0 -87
- package/src/__tests__/handler-sendsubscribe-error.test.ts +0 -32
- package/src/__tests__/sendsubscribe-rejected-promise.test.ts +0 -89
- package/src/__tests__/server-apikey-warning.test.ts +0 -71
- package/src/__tests__/server-body-limit.test.ts +0 -54
- package/src/__tests__/server-security-headers.test.ts +0 -112
- package/src/__tests__/server-timing-safe-auth.test.ts +0 -45
- package/src/__tests__/sse-handler-error-propagation.test.ts +0 -69
- package/src/card/generator.ts +0 -18
- package/src/client/index.ts +0 -172
- package/src/errors.ts +0 -21
- package/src/index.ts +0 -15
- package/src/server/handler.ts +0 -90
- package/src/server/index.ts +0 -208
- package/src/server/sse.ts +0 -49
- package/src/swarm/a2a-agent.ts +0 -34
- package/src/types.ts +0 -50
- package/tsconfig.json +0 -20
- package/vitest.config.ts +0 -4
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
import {
|
|
2
|
+
A2A_ERROR_CODES,
|
|
3
|
+
A2aClientError,
|
|
4
|
+
IllegalTaskTransitionError,
|
|
5
|
+
MessageSchema,
|
|
6
|
+
TaskNotFoundError,
|
|
7
|
+
constantTimeEqual,
|
|
8
|
+
isTerminal
|
|
9
|
+
} from "./chunk-PYM7LV6R.js";
|
|
10
|
+
|
|
11
|
+
// src/bindings/grpc.ts
|
|
12
|
+
import { existsSync } from "fs";
|
|
13
|
+
import { join } from "path";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
|
+
import * as grpc from "@grpc/grpc-js";
|
|
16
|
+
import * as protoLoader from "@grpc/proto-loader";
|
|
17
|
+
import { Value } from "@sinclair/typebox/value";
|
|
18
|
+
var A2aGrpcMappingError = class extends Error {
|
|
19
|
+
constructor(message) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = "A2aGrpcMappingError";
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var TASK_STATE_TO_PROTO = {
|
|
25
|
+
submitted: "TASK_STATE_SUBMITTED",
|
|
26
|
+
working: "TASK_STATE_WORKING",
|
|
27
|
+
input_required: "TASK_STATE_INPUT_REQUIRED",
|
|
28
|
+
auth_required: "TASK_STATE_AUTH_REQUIRED",
|
|
29
|
+
completed: "TASK_STATE_COMPLETED",
|
|
30
|
+
failed: "TASK_STATE_FAILED",
|
|
31
|
+
canceled: "TASK_STATE_CANCELED",
|
|
32
|
+
rejected: "TASK_STATE_REJECTED"
|
|
33
|
+
};
|
|
34
|
+
var TASK_STATE_FROM_PROTO = new Map(
|
|
35
|
+
Object.entries(TASK_STATE_TO_PROTO).map(([state, proto]) => [
|
|
36
|
+
proto,
|
|
37
|
+
state
|
|
38
|
+
])
|
|
39
|
+
);
|
|
40
|
+
function taskStateToProto(state) {
|
|
41
|
+
const proto = TASK_STATE_TO_PROTO[state];
|
|
42
|
+
if (proto === void 0) throw new A2aGrpcMappingError(`unmappable A2A task state: ${state}`);
|
|
43
|
+
return proto;
|
|
44
|
+
}
|
|
45
|
+
function taskStateFromProto(value) {
|
|
46
|
+
const state = typeof value === "string" ? TASK_STATE_FROM_PROTO.get(value) : void 0;
|
|
47
|
+
if (state === void 0) {
|
|
48
|
+
throw new A2aGrpcMappingError(`unmappable proto TaskState: ${String(value)}`);
|
|
49
|
+
}
|
|
50
|
+
return state;
|
|
51
|
+
}
|
|
52
|
+
function toProtoValue(value) {
|
|
53
|
+
if (value === null || value === void 0) return { nullValue: "NULL_VALUE" };
|
|
54
|
+
if (typeof value === "number") return { numberValue: value };
|
|
55
|
+
if (typeof value === "string") return { stringValue: value };
|
|
56
|
+
if (typeof value === "boolean") return { boolValue: value };
|
|
57
|
+
if (Array.isArray(value)) return { listValue: { values: value.map(toProtoValue) } };
|
|
58
|
+
return { structValue: toProtoStruct(value) };
|
|
59
|
+
}
|
|
60
|
+
function toProtoStruct(record) {
|
|
61
|
+
const fields = {};
|
|
62
|
+
for (const [key, value] of Object.entries(record)) fields[key] = toProtoValue(value);
|
|
63
|
+
return { fields };
|
|
64
|
+
}
|
|
65
|
+
function fromProtoValue(value) {
|
|
66
|
+
switch (value.kind) {
|
|
67
|
+
case "numberValue":
|
|
68
|
+
return value.numberValue ?? 0;
|
|
69
|
+
case "stringValue":
|
|
70
|
+
return value.stringValue ?? "";
|
|
71
|
+
case "boolValue":
|
|
72
|
+
return value.boolValue ?? false;
|
|
73
|
+
case "structValue":
|
|
74
|
+
return fromProtoStruct(value.structValue ?? {});
|
|
75
|
+
case "listValue":
|
|
76
|
+
return (value.listValue?.values ?? []).map(fromProtoValue);
|
|
77
|
+
default:
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function fromProtoStruct(struct) {
|
|
82
|
+
const out = {};
|
|
83
|
+
for (const [key, value] of Object.entries(struct.fields ?? {})) out[key] = fromProtoValue(value);
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
function toProtoTimestamp(iso) {
|
|
87
|
+
const ms = Date.parse(iso);
|
|
88
|
+
if (Number.isNaN(ms)) throw new A2aGrpcMappingError(`unparsable A2A timestamp: ${iso}`);
|
|
89
|
+
return { seconds: Math.floor(ms / 1e3), nanos: ms % 1e3 * 1e6 };
|
|
90
|
+
}
|
|
91
|
+
function fromProtoTimestamp(ts) {
|
|
92
|
+
return new Date((ts.seconds ?? 0) * 1e3 + Math.round((ts.nanos ?? 0) / 1e6)).toISOString();
|
|
93
|
+
}
|
|
94
|
+
function partToProto(part) {
|
|
95
|
+
const extras = {
|
|
96
|
+
...part.mediaType !== void 0 ? { mediaType: part.mediaType } : {},
|
|
97
|
+
...part.metadata !== void 0 ? { metadata: toProtoStruct(part.metadata) } : {}
|
|
98
|
+
};
|
|
99
|
+
switch (part.kind) {
|
|
100
|
+
case "text":
|
|
101
|
+
return { text: part.text, ...extras };
|
|
102
|
+
case "url":
|
|
103
|
+
return { url: part.url, ...extras };
|
|
104
|
+
case "raw":
|
|
105
|
+
return { raw: part.data, ...extras };
|
|
106
|
+
case "data":
|
|
107
|
+
return { data: toProtoValue(part.data), ...extras };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function partFromProto(proto) {
|
|
111
|
+
const extras = {
|
|
112
|
+
...proto.mediaType !== void 0 && proto.mediaType !== "" ? { mediaType: proto.mediaType } : {},
|
|
113
|
+
...proto.metadata !== void 0 ? { metadata: fromProtoStruct(proto.metadata) } : {}
|
|
114
|
+
};
|
|
115
|
+
switch (proto.content) {
|
|
116
|
+
case "text":
|
|
117
|
+
return { kind: "text", text: proto.text ?? "", ...extras };
|
|
118
|
+
case "url":
|
|
119
|
+
return { kind: "url", url: proto.url ?? "", ...extras };
|
|
120
|
+
case "raw":
|
|
121
|
+
return { kind: "raw", data: proto.raw ?? "", ...extras };
|
|
122
|
+
case "data": {
|
|
123
|
+
const value = fromProtoValue(proto.data ?? {});
|
|
124
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
125
|
+
throw new A2aGrpcMappingError("proto data Part is not a JSON object");
|
|
126
|
+
}
|
|
127
|
+
return { kind: "data", data: value, ...extras };
|
|
128
|
+
}
|
|
129
|
+
default:
|
|
130
|
+
throw new A2aGrpcMappingError("proto Part carries no content");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function roleFromProto(value) {
|
|
134
|
+
if (value === "ROLE_USER") return "user";
|
|
135
|
+
if (value === "ROLE_AGENT") return "agent";
|
|
136
|
+
throw new A2aGrpcMappingError(`unmappable proto Role: ${String(value)}`);
|
|
137
|
+
}
|
|
138
|
+
function messageToProto(message) {
|
|
139
|
+
return {
|
|
140
|
+
messageId: message.messageId,
|
|
141
|
+
...message.contextId !== void 0 ? { contextId: message.contextId } : {},
|
|
142
|
+
...message.taskId !== void 0 ? { taskId: message.taskId } : {},
|
|
143
|
+
role: message.role === "user" ? "ROLE_USER" : "ROLE_AGENT",
|
|
144
|
+
parts: message.parts.map(partToProto),
|
|
145
|
+
...message.metadata !== void 0 ? { metadata: toProtoStruct(message.metadata) } : {}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function messageFromProto(proto) {
|
|
149
|
+
return {
|
|
150
|
+
messageId: proto.messageId ?? "",
|
|
151
|
+
// proto3 empty string means unset for the optional ids.
|
|
152
|
+
...proto.contextId ? { contextId: proto.contextId } : {},
|
|
153
|
+
...proto.taskId ? { taskId: proto.taskId } : {},
|
|
154
|
+
role: roleFromProto(proto.role),
|
|
155
|
+
parts: (proto.parts ?? []).map(partFromProto),
|
|
156
|
+
...proto.metadata !== void 0 ? { metadata: fromProtoStruct(proto.metadata) } : {}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function statusToProto(status2) {
|
|
160
|
+
return {
|
|
161
|
+
state: taskStateToProto(status2.state),
|
|
162
|
+
...status2.message !== void 0 ? { message: messageToProto(status2.message) } : {},
|
|
163
|
+
...status2.timestamp !== void 0 ? { timestamp: toProtoTimestamp(status2.timestamp) } : {}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function statusFromProto(proto) {
|
|
167
|
+
return {
|
|
168
|
+
state: taskStateFromProto(proto.state),
|
|
169
|
+
...proto.message !== void 0 ? { message: messageFromProto(proto.message) } : {},
|
|
170
|
+
...proto.timestamp !== void 0 ? { timestamp: fromProtoTimestamp(proto.timestamp) } : {}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function artifactToProto(artifact) {
|
|
174
|
+
return {
|
|
175
|
+
artifactId: artifact.artifactId,
|
|
176
|
+
...artifact.name !== void 0 ? { name: artifact.name } : {},
|
|
177
|
+
...artifact.description !== void 0 ? { description: artifact.description } : {},
|
|
178
|
+
parts: artifact.parts.map(partToProto),
|
|
179
|
+
...artifact.metadata !== void 0 ? { metadata: toProtoStruct(artifact.metadata) } : {}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function artifactFromProto(proto) {
|
|
183
|
+
return {
|
|
184
|
+
artifactId: proto.artifactId ?? "",
|
|
185
|
+
...proto.name ? { name: proto.name } : {},
|
|
186
|
+
...proto.description ? { description: proto.description } : {},
|
|
187
|
+
parts: (proto.parts ?? []).map(partFromProto),
|
|
188
|
+
...proto.metadata !== void 0 ? { metadata: fromProtoStruct(proto.metadata) } : {}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function taskToProto(task) {
|
|
192
|
+
return {
|
|
193
|
+
id: task.id,
|
|
194
|
+
contextId: task.contextId,
|
|
195
|
+
status: statusToProto(task.status),
|
|
196
|
+
...task.artifacts !== void 0 ? { artifacts: task.artifacts.map(artifactToProto) } : {},
|
|
197
|
+
...task.history !== void 0 ? { history: task.history.map(messageToProto) } : {}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function taskFromProto(proto) {
|
|
201
|
+
if (proto.status === void 0) throw new A2aGrpcMappingError("proto Task is missing status");
|
|
202
|
+
return {
|
|
203
|
+
id: proto.id ?? "",
|
|
204
|
+
contextId: proto.contextId ?? "",
|
|
205
|
+
status: statusFromProto(proto.status),
|
|
206
|
+
...proto.artifacts !== void 0 && proto.artifacts.length > 0 ? { artifacts: proto.artifacts.map(artifactFromProto) } : {},
|
|
207
|
+
...proto.history !== void 0 && proto.history.length > 0 ? { history: proto.history.map(messageFromProto) } : {}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function statusUpdateToProto(event) {
|
|
211
|
+
return {
|
|
212
|
+
taskId: event.taskId,
|
|
213
|
+
contextId: event.contextId,
|
|
214
|
+
status: statusToProto(event.status),
|
|
215
|
+
...event.metadata !== void 0 ? { metadata: toProtoStruct(event.metadata) } : {}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function statusUpdateFromProto(proto) {
|
|
219
|
+
const status2 = statusFromProto(proto.status ?? {});
|
|
220
|
+
return {
|
|
221
|
+
kind: "status-update",
|
|
222
|
+
taskId: proto.taskId ?? "",
|
|
223
|
+
contextId: proto.contextId ?? "",
|
|
224
|
+
status: status2,
|
|
225
|
+
final: isTerminal(status2.state),
|
|
226
|
+
...proto.metadata !== void 0 ? { metadata: fromProtoStruct(proto.metadata) } : {}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function artifactUpdateFromProto(proto) {
|
|
230
|
+
if (proto.artifact === void 0) {
|
|
231
|
+
throw new A2aGrpcMappingError("proto artifact update is missing artifact");
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
kind: "artifact-update",
|
|
235
|
+
taskId: proto.taskId ?? "",
|
|
236
|
+
contextId: proto.contextId ?? "",
|
|
237
|
+
artifact: artifactFromProto(proto.artifact),
|
|
238
|
+
...proto.append !== void 0 ? { append: proto.append } : {},
|
|
239
|
+
...proto.lastChunk !== void 0 ? { lastChunk: proto.lastChunk } : {},
|
|
240
|
+
...proto.metadata !== void 0 ? { metadata: fromProtoStruct(proto.metadata) } : {}
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function protoDir() {
|
|
244
|
+
for (const relative of ["../../proto/", "../proto/"]) {
|
|
245
|
+
const dir = fileURLToPath(new URL(relative, import.meta.url));
|
|
246
|
+
if (existsSync(join(dir, "a2a.proto"))) return dir;
|
|
247
|
+
}
|
|
248
|
+
throw new Error("a2a.proto not found relative to the @ap3x/a2a module");
|
|
249
|
+
}
|
|
250
|
+
var serviceCtor;
|
|
251
|
+
function loadServiceCtor() {
|
|
252
|
+
if (serviceCtor === void 0) {
|
|
253
|
+
const dir = protoDir();
|
|
254
|
+
const definition = protoLoader.loadSync(join(dir, "a2a.proto"), {
|
|
255
|
+
keepCase: false,
|
|
256
|
+
// snake_case wire fields -> camelCase JS, matching the model
|
|
257
|
+
longs: Number,
|
|
258
|
+
enums: String,
|
|
259
|
+
bytes: String,
|
|
260
|
+
// proto bytes <-> base64 strings, matching RawPart
|
|
261
|
+
defaults: false,
|
|
262
|
+
oneofs: true,
|
|
263
|
+
includeDirs: [dir]
|
|
264
|
+
});
|
|
265
|
+
const pkg = grpc.loadPackageDefinition(definition);
|
|
266
|
+
serviceCtor = pkg.lf.a2a.v1.A2AService;
|
|
267
|
+
}
|
|
268
|
+
return serviceCtor;
|
|
269
|
+
}
|
|
270
|
+
function serviceError(code, message) {
|
|
271
|
+
return Object.assign(new Error(message), {
|
|
272
|
+
code,
|
|
273
|
+
details: message,
|
|
274
|
+
metadata: new grpc.Metadata()
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
function mapThrown(error) {
|
|
278
|
+
if (error instanceof TaskNotFoundError) return serviceError(grpc.status.NOT_FOUND, error.message);
|
|
279
|
+
if (error instanceof IllegalTaskTransitionError) {
|
|
280
|
+
return serviceError(grpc.status.FAILED_PRECONDITION, error.message);
|
|
281
|
+
}
|
|
282
|
+
if (error instanceof A2aGrpcMappingError) {
|
|
283
|
+
return serviceError(grpc.status.INVALID_ARGUMENT, error.message);
|
|
284
|
+
}
|
|
285
|
+
return serviceError(grpc.status.INTERNAL, error instanceof Error ? error.message : String(error));
|
|
286
|
+
}
|
|
287
|
+
function inboundMessage(proto) {
|
|
288
|
+
if (proto === void 0) throw new A2aGrpcMappingError("request is missing message");
|
|
289
|
+
const message = messageFromProto(proto);
|
|
290
|
+
if (!Value.Check(MessageSchema, message)) {
|
|
291
|
+
throw new A2aGrpcMappingError("invalid A2A message");
|
|
292
|
+
}
|
|
293
|
+
return message;
|
|
294
|
+
}
|
|
295
|
+
function writeFrame(call, frame) {
|
|
296
|
+
if (!call.cancelled && call.writable) call.write(frame);
|
|
297
|
+
}
|
|
298
|
+
function endStream(call) {
|
|
299
|
+
if (!call.cancelled && call.writable) call.end();
|
|
300
|
+
}
|
|
301
|
+
async function startGrpcServer(executor, options) {
|
|
302
|
+
const ctor = loadServiceCtor();
|
|
303
|
+
const server = new grpc.Server();
|
|
304
|
+
const authError = (metadata) => {
|
|
305
|
+
if (options.bearerToken === void 0) return void 0;
|
|
306
|
+
const denied = serviceError(grpc.status.UNAUTHENTICATED, "missing or invalid bearer token");
|
|
307
|
+
const header = metadata.get("authorization")[0];
|
|
308
|
+
if (typeof header !== "string" || !header.startsWith("Bearer ")) return denied;
|
|
309
|
+
if (!constantTimeEqual(header.slice("Bearer ".length), options.bearerToken)) return denied;
|
|
310
|
+
return void 0;
|
|
311
|
+
};
|
|
312
|
+
const unimplemented = (_call, callback) => {
|
|
313
|
+
callback(serviceError(grpc.status.UNIMPLEMENTED, "not supported by this A2A v1 server"), null);
|
|
314
|
+
};
|
|
315
|
+
const handlers = {
|
|
316
|
+
SendMessage: ((call, callback) => {
|
|
317
|
+
const denied = authError(call.metadata);
|
|
318
|
+
if (denied) return callback(denied, null);
|
|
319
|
+
void (async () => {
|
|
320
|
+
try {
|
|
321
|
+
const message = inboundMessage(call.request?.message);
|
|
322
|
+
const task = await executor.sendMessage(message, {
|
|
323
|
+
blocking: !(call.request?.configuration?.returnImmediately ?? false)
|
|
324
|
+
});
|
|
325
|
+
callback(null, { task: taskToProto(task) });
|
|
326
|
+
} catch (error) {
|
|
327
|
+
callback(mapThrown(error), null);
|
|
328
|
+
}
|
|
329
|
+
})();
|
|
330
|
+
}),
|
|
331
|
+
SendStreamingMessage: ((call) => {
|
|
332
|
+
const denied = authError(call.metadata);
|
|
333
|
+
if (denied) {
|
|
334
|
+
call.emit("error", denied);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
let message;
|
|
338
|
+
try {
|
|
339
|
+
message = inboundMessage(call.request?.message);
|
|
340
|
+
} catch (error) {
|
|
341
|
+
call.emit("error", mapThrown(error));
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
executor.sendMessage(message, {
|
|
345
|
+
blocking: false,
|
|
346
|
+
// Spec: the stream MUST begin with the full Task object.
|
|
347
|
+
onTask: (task) => writeFrame(call, { task: taskToProto(task) }),
|
|
348
|
+
onEvent: (event) => {
|
|
349
|
+
writeFrame(call, { statusUpdate: statusUpdateToProto(event) });
|
|
350
|
+
if (event.final) endStream(call);
|
|
351
|
+
}
|
|
352
|
+
}).catch((error) => call.emit("error", mapThrown(error)));
|
|
353
|
+
}),
|
|
354
|
+
GetTask: ((call, callback) => {
|
|
355
|
+
const denied = authError(call.metadata);
|
|
356
|
+
if (denied) return callback(denied, null);
|
|
357
|
+
const id = call.request?.id ?? "";
|
|
358
|
+
const task = executor.getTask(id);
|
|
359
|
+
if (!task)
|
|
360
|
+
return callback(serviceError(grpc.status.NOT_FOUND, `A2A task not found: ${id}`), null);
|
|
361
|
+
callback(null, taskToProto(task));
|
|
362
|
+
}),
|
|
363
|
+
ListTasks: ((call, callback) => {
|
|
364
|
+
const denied = authError(call.metadata);
|
|
365
|
+
if (denied) return callback(denied, null);
|
|
366
|
+
try {
|
|
367
|
+
const filter = {
|
|
368
|
+
...call.request?.contextId ? { contextId: call.request.contextId } : {},
|
|
369
|
+
...call.request?.status !== void 0 ? { state: taskStateFromProto(call.request.status) } : {}
|
|
370
|
+
};
|
|
371
|
+
const tasks = executor.listTasks(filter).map(taskToProto);
|
|
372
|
+
callback(null, {
|
|
373
|
+
tasks,
|
|
374
|
+
nextPageToken: "",
|
|
375
|
+
pageSize: tasks.length,
|
|
376
|
+
totalSize: tasks.length
|
|
377
|
+
});
|
|
378
|
+
} catch (error) {
|
|
379
|
+
callback(mapThrown(error), null);
|
|
380
|
+
}
|
|
381
|
+
}),
|
|
382
|
+
CancelTask: ((call, callback) => {
|
|
383
|
+
const denied = authError(call.metadata);
|
|
384
|
+
if (denied) return callback(denied, null);
|
|
385
|
+
try {
|
|
386
|
+
callback(null, taskToProto(executor.cancel(call.request?.id ?? "")));
|
|
387
|
+
} catch (error) {
|
|
388
|
+
callback(mapThrown(error), null);
|
|
389
|
+
}
|
|
390
|
+
}),
|
|
391
|
+
SubscribeToTask: ((call) => {
|
|
392
|
+
const denied = authError(call.metadata);
|
|
393
|
+
if (denied) {
|
|
394
|
+
call.emit("error", denied);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const id = call.request?.id ?? "";
|
|
398
|
+
const task = executor.getTask(id);
|
|
399
|
+
if (!task) {
|
|
400
|
+
call.emit("error", serviceError(grpc.status.NOT_FOUND, `A2A task not found: ${id}`));
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
writeFrame(call, { task: taskToProto(task) });
|
|
404
|
+
if (isTerminal(task.status.state)) {
|
|
405
|
+
writeFrame(call, {
|
|
406
|
+
statusUpdate: statusUpdateToProto({
|
|
407
|
+
kind: "status-update",
|
|
408
|
+
taskId: task.id,
|
|
409
|
+
contextId: task.contextId,
|
|
410
|
+
status: task.status,
|
|
411
|
+
final: true
|
|
412
|
+
})
|
|
413
|
+
});
|
|
414
|
+
endStream(call);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
const unsubscribe = executor.subscribe(task.id, (event) => {
|
|
418
|
+
writeFrame(call, { statusUpdate: statusUpdateToProto(event) });
|
|
419
|
+
if (event.final) endStream(call);
|
|
420
|
+
});
|
|
421
|
+
call.on("cancelled", unsubscribe);
|
|
422
|
+
}),
|
|
423
|
+
// Out of v1 scope — spec-legal UNIMPLEMENTED.
|
|
424
|
+
CreateTaskPushNotificationConfig: unimplemented,
|
|
425
|
+
GetTaskPushNotificationConfig: unimplemented,
|
|
426
|
+
ListTaskPushNotificationConfigs: unimplemented,
|
|
427
|
+
DeleteTaskPushNotificationConfig: unimplemented,
|
|
428
|
+
GetExtendedAgentCard: unimplemented
|
|
429
|
+
};
|
|
430
|
+
server.addService(ctor.service, handlers);
|
|
431
|
+
const bindHost = options.host.includes(":") ? `[${options.host}]` : options.host;
|
|
432
|
+
const port = await new Promise((resolve, reject) => {
|
|
433
|
+
server.bindAsync(
|
|
434
|
+
`${bindHost}:${options.port}`,
|
|
435
|
+
grpc.ServerCredentials.createInsecure(),
|
|
436
|
+
(error, boundPort) => error ? reject(error) : resolve(boundPort)
|
|
437
|
+
);
|
|
438
|
+
});
|
|
439
|
+
return {
|
|
440
|
+
url: `${bindHost}:${port}`,
|
|
441
|
+
// forceShutdown: close() must never hang on open subscription streams.
|
|
442
|
+
close: async () => {
|
|
443
|
+
server.forceShutdown();
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
function a2aCodeFor(code) {
|
|
448
|
+
switch (code) {
|
|
449
|
+
case grpc.status.NOT_FOUND:
|
|
450
|
+
return A2A_ERROR_CODES.taskNotFound;
|
|
451
|
+
case grpc.status.FAILED_PRECONDITION:
|
|
452
|
+
return A2A_ERROR_CODES.taskNotCancelable;
|
|
453
|
+
case grpc.status.INVALID_ARGUMENT:
|
|
454
|
+
return A2A_ERROR_CODES.invalidParams;
|
|
455
|
+
case grpc.status.UNIMPLEMENTED:
|
|
456
|
+
return A2A_ERROR_CODES.unsupportedOperation;
|
|
457
|
+
default:
|
|
458
|
+
return void 0;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
function normalizeGrpcTarget(url) {
|
|
462
|
+
return url.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "").replace(/\/+$/, "");
|
|
463
|
+
}
|
|
464
|
+
var GrpcClientTransport = class {
|
|
465
|
+
client;
|
|
466
|
+
timeoutMs;
|
|
467
|
+
bearerToken;
|
|
468
|
+
constructor(target, options) {
|
|
469
|
+
const ctor = loadServiceCtor();
|
|
470
|
+
this.client = new ctor(normalizeGrpcTarget(target), grpc.credentials.createInsecure());
|
|
471
|
+
this.timeoutMs = options.timeoutMs;
|
|
472
|
+
if (options.bearerToken !== void 0) this.bearerToken = options.bearerToken;
|
|
473
|
+
}
|
|
474
|
+
/** Close the underlying channel (keeps the event loop free after tests). */
|
|
475
|
+
close() {
|
|
476
|
+
this.client.close();
|
|
477
|
+
}
|
|
478
|
+
async sendMessage(message, config) {
|
|
479
|
+
const request = {
|
|
480
|
+
message: messageToProto(message),
|
|
481
|
+
...config !== void 0 ? { configuration: { returnImmediately: config.returnImmediately ?? false } } : {}
|
|
482
|
+
};
|
|
483
|
+
const response = await this.unary("SendMessage", request);
|
|
484
|
+
if (response?.task !== void 0) {
|
|
485
|
+
return this.convert(() => taskFromProto(response.task), "SendMessage");
|
|
486
|
+
}
|
|
487
|
+
if (response?.message !== void 0) {
|
|
488
|
+
return this.convert(() => messageFromProto(response.message), "SendMessage");
|
|
489
|
+
}
|
|
490
|
+
throw new A2aClientError("A2A SendMessage returned no task or message payload");
|
|
491
|
+
}
|
|
492
|
+
sendStreamingMessage(message) {
|
|
493
|
+
return this.stream("SendStreamingMessage", { message: messageToProto(message) });
|
|
494
|
+
}
|
|
495
|
+
async getTask(id) {
|
|
496
|
+
const response = await this.unary("GetTask", { id });
|
|
497
|
+
return this.convert(() => taskFromProto(response), "GetTask");
|
|
498
|
+
}
|
|
499
|
+
async listTasks(filter = {}) {
|
|
500
|
+
const request = {
|
|
501
|
+
...filter.contextId !== void 0 ? { contextId: filter.contextId } : {},
|
|
502
|
+
...filter.state !== void 0 ? { status: taskStateToProto(filter.state) } : {}
|
|
503
|
+
};
|
|
504
|
+
const response = await this.unary("ListTasks", request);
|
|
505
|
+
return this.convert(() => (response.tasks ?? []).map(taskFromProto), "ListTasks");
|
|
506
|
+
}
|
|
507
|
+
async cancelTask(id) {
|
|
508
|
+
const response = await this.unary("CancelTask", { id });
|
|
509
|
+
return this.convert(() => taskFromProto(response), "CancelTask");
|
|
510
|
+
}
|
|
511
|
+
subscribeToTask(id) {
|
|
512
|
+
return this.stream("SubscribeToTask", { id });
|
|
513
|
+
}
|
|
514
|
+
metadata() {
|
|
515
|
+
const metadata = new grpc.Metadata();
|
|
516
|
+
if (this.bearerToken !== void 0) {
|
|
517
|
+
metadata.set("authorization", `Bearer ${this.bearerToken}`);
|
|
518
|
+
}
|
|
519
|
+
return metadata;
|
|
520
|
+
}
|
|
521
|
+
callOptions() {
|
|
522
|
+
return { deadline: new Date(Date.now() + this.timeoutMs) };
|
|
523
|
+
}
|
|
524
|
+
wrapError(error, op) {
|
|
525
|
+
if (error instanceof A2aClientError) return error;
|
|
526
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
527
|
+
const code = a2aCodeFor(error.code);
|
|
528
|
+
return new A2aClientError(`A2A ${op} failed: ${message}`, code);
|
|
529
|
+
}
|
|
530
|
+
convert(fn, op) {
|
|
531
|
+
try {
|
|
532
|
+
return fn();
|
|
533
|
+
} catch (error) {
|
|
534
|
+
throw new A2aClientError(
|
|
535
|
+
`A2A ${op} returned a malformed result: ${error instanceof Error ? error.message : String(error)}`
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
unary(method, request) {
|
|
540
|
+
return new Promise((resolve, reject) => {
|
|
541
|
+
const call = this.client[method];
|
|
542
|
+
if (call === void 0) {
|
|
543
|
+
reject(new A2aClientError(`A2A ${method} is not a known gRPC method`));
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
call.call(
|
|
547
|
+
this.client,
|
|
548
|
+
request,
|
|
549
|
+
this.metadata(),
|
|
550
|
+
this.callOptions(),
|
|
551
|
+
(error, response) => error ? reject(this.wrapError(error, method)) : resolve(response)
|
|
552
|
+
);
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
async *stream(method, request) {
|
|
556
|
+
const call = this.client[method];
|
|
557
|
+
if (call === void 0) throw new A2aClientError(`A2A ${method} is not a known gRPC method`);
|
|
558
|
+
const stream = call.call(this.client, request, this.metadata(), this.callOptions());
|
|
559
|
+
try {
|
|
560
|
+
for await (const chunk of stream) {
|
|
561
|
+
if (chunk.payload === "task" && chunk.task !== void 0) {
|
|
562
|
+
yield this.convert(() => taskFromProto(chunk.task), method);
|
|
563
|
+
} else if (chunk.payload === "statusUpdate" && chunk.statusUpdate !== void 0) {
|
|
564
|
+
yield this.convert(
|
|
565
|
+
() => statusUpdateFromProto(chunk.statusUpdate),
|
|
566
|
+
method
|
|
567
|
+
);
|
|
568
|
+
} else if (chunk.payload === "artifactUpdate" && chunk.artifactUpdate !== void 0) {
|
|
569
|
+
yield this.convert(
|
|
570
|
+
() => artifactUpdateFromProto(chunk.artifactUpdate),
|
|
571
|
+
method
|
|
572
|
+
);
|
|
573
|
+
} else if (chunk.payload === "message" && chunk.message !== void 0) {
|
|
574
|
+
yield this.convert(() => messageFromProto(chunk.message), method);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
} catch (error) {
|
|
579
|
+
throw this.wrapError(error, method);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
export {
|
|
584
|
+
A2aGrpcMappingError,
|
|
585
|
+
GrpcClientTransport,
|
|
586
|
+
startGrpcServer,
|
|
587
|
+
taskFromProto,
|
|
588
|
+
taskStateFromProto,
|
|
589
|
+
taskStateToProto,
|
|
590
|
+
taskToProto
|
|
591
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./model";
|
|
2
|
+
export * from "./task-store";
|
|
3
|
+
export * from "./executor";
|
|
4
|
+
export * from "./server";
|
|
5
|
+
export * from "./bindings/jsonrpc";
|
|
6
|
+
export * from "./bindings/rest";
|
|
7
|
+
export * from "./client";
|
|
8
|
+
export * from "./backend";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC"}
|