a2a-mesh 1.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 +171 -0
- package/README.md +22 -0
- package/dist/auth/index.cjs +8 -0
- package/dist/auth/index.d.cts +2 -0
- package/dist/auth/index.d.mts +2 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.mjs +2 -0
- package/dist/index.cjs +1428 -0
- package/dist/index.d.cts +1589 -0
- package/dist/index.d.mts +1589 -0
- package/dist/index.d.ts +1589 -0
- package/dist/index.mjs +1392 -0
- package/dist/middleware/index.cjs +9 -0
- package/dist/middleware/index.d.cts +46 -0
- package/dist/middleware/index.d.mts +46 -0
- package/dist/middleware/index.d.ts +46 -0
- package/dist/middleware/index.mjs +1 -0
- package/dist/shared/a2a-mesh.B8CFEj_l.cjs +101 -0
- package/dist/shared/a2a-mesh.CDuHtAfx.d.cts +70 -0
- package/dist/shared/a2a-mesh.CDuHtAfx.d.mts +70 -0
- package/dist/shared/a2a-mesh.CDuHtAfx.d.ts +70 -0
- package/dist/shared/a2a-mesh.CpP0sKxA.mjs +95 -0
- package/dist/shared/a2a-mesh.DMPwsFk8.cjs +121 -0
- package/dist/shared/a2a-mesh.DOHcVRN8.mjs +119 -0
- package/dist/telemetry/index.cjs +27 -0
- package/dist/telemetry/index.d.cts +7 -0
- package/dist/telemetry/index.d.mts +7 -0
- package/dist/telemetry/index.d.ts +7 -0
- package/dist/telemetry/index.mjs +24 -0
- package/package.json +90 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1392 @@
|
|
|
1
|
+
import { J as JsonRpcError, E as ErrorCodes, c as createRateLimiter } from './shared/a2a-mesh.CpP0sKxA.mjs';
|
|
2
|
+
export { I as InMemoryRateLimitStore, R as RedisRateLimitStore } from './shared/a2a-mesh.CpP0sKxA.mjs';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import express from 'express';
|
|
6
|
+
import { EventEmitter } from 'node:events';
|
|
7
|
+
import { a2aMeshTracer } from './telemetry/index.mjs';
|
|
8
|
+
export { withA2ABaggage } from './telemetry/index.mjs';
|
|
9
|
+
import { SpanStatusCode } from '@opentelemetry/api';
|
|
10
|
+
export { SpanStatusCode } from '@opentelemetry/api';
|
|
11
|
+
import { J as JwtAuthMiddleware } from './shared/a2a-mesh.DOHcVRN8.mjs';
|
|
12
|
+
import { createRequire } from 'node:module';
|
|
13
|
+
import EventSource from 'eventsource';
|
|
14
|
+
import 'jose';
|
|
15
|
+
|
|
16
|
+
function normalizeAgentCard(card) {
|
|
17
|
+
if (card.protocolVersion === "1.0") {
|
|
18
|
+
return card;
|
|
19
|
+
}
|
|
20
|
+
const capabilities = card.capabilities || card.supportsAuthenticatedExtendedCard ? {
|
|
21
|
+
...card.capabilities ?? {},
|
|
22
|
+
...card.supportsAuthenticatedExtendedCard || card.capabilities?.extendedAgentCard ? { extendedAgentCard: true } : {}
|
|
23
|
+
} : void 0;
|
|
24
|
+
return {
|
|
25
|
+
protocolVersion: "1.0",
|
|
26
|
+
name: card.name,
|
|
27
|
+
description: card.description,
|
|
28
|
+
url: card.url,
|
|
29
|
+
version: card.version,
|
|
30
|
+
...card.iconUrl ? { iconUrl: card.iconUrl } : {},
|
|
31
|
+
...card.provider ? { provider: card.provider } : {},
|
|
32
|
+
...capabilities ? { capabilities } : {},
|
|
33
|
+
...card.defaultInputMode ? { defaultInputModes: [card.defaultInputMode] } : {},
|
|
34
|
+
...card.defaultOutputMode ? { defaultOutputModes: [card.defaultOutputMode] } : {},
|
|
35
|
+
...card.skills ? { skills: card.skills } : {},
|
|
36
|
+
...card.authentication ? { securitySchemes: card.authentication } : {}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const LOG_LEVELS = {
|
|
41
|
+
debug: 10,
|
|
42
|
+
info: 20,
|
|
43
|
+
warn: 30,
|
|
44
|
+
error: 40
|
|
45
|
+
};
|
|
46
|
+
function resolveLogLevel() {
|
|
47
|
+
const level = process.env.LOG_LEVEL?.toLowerCase();
|
|
48
|
+
if (level === "debug" || level === "info" || level === "warn" || level === "error") {
|
|
49
|
+
return level;
|
|
50
|
+
}
|
|
51
|
+
return "info";
|
|
52
|
+
}
|
|
53
|
+
function isProductionMode() {
|
|
54
|
+
return process.env.NODE_ENV === "production";
|
|
55
|
+
}
|
|
56
|
+
function serializeError(error) {
|
|
57
|
+
if (error instanceof Error) {
|
|
58
|
+
return {
|
|
59
|
+
name: error.name,
|
|
60
|
+
message: error.message,
|
|
61
|
+
stack: error.stack
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return { error: String(error) };
|
|
65
|
+
}
|
|
66
|
+
function formatPretty(level, message, context) {
|
|
67
|
+
const contextEntries = Object.entries(context).filter(([, value]) => value !== void 0).map(([key, value]) => `${key}=${JSON.stringify(value)}`).join(" ");
|
|
68
|
+
return `[${(/* @__PURE__ */ new Date()).toISOString()}] ${level.toUpperCase()} ${message}${contextEntries ? ` ${contextEntries}` : ""}`;
|
|
69
|
+
}
|
|
70
|
+
function writeLog(level, message, context) {
|
|
71
|
+
if (LOG_LEVELS[level] < LOG_LEVELS[resolveLogLevel()]) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const payload = {
|
|
75
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
76
|
+
level,
|
|
77
|
+
message,
|
|
78
|
+
...context
|
|
79
|
+
};
|
|
80
|
+
const output = isProductionMode() ? JSON.stringify(payload) : formatPretty(level, message, context);
|
|
81
|
+
if (level === "error") {
|
|
82
|
+
process.stderr.write(`${output}
|
|
83
|
+
`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
process.stdout.write(`${output}
|
|
87
|
+
`);
|
|
88
|
+
}
|
|
89
|
+
const logger = {
|
|
90
|
+
debug(message, context = {}) {
|
|
91
|
+
writeLog("debug", message, context);
|
|
92
|
+
},
|
|
93
|
+
info(message, context = {}) {
|
|
94
|
+
writeLog("info", message, context);
|
|
95
|
+
},
|
|
96
|
+
warn(message, context = {}) {
|
|
97
|
+
writeLog("warn", message, context);
|
|
98
|
+
},
|
|
99
|
+
error(message, context = {}) {
|
|
100
|
+
const normalizedContext = { ...context };
|
|
101
|
+
if ("error" in normalizedContext) {
|
|
102
|
+
normalizedContext.error = serializeError(normalizedContext.error);
|
|
103
|
+
}
|
|
104
|
+
writeLog("error", message, normalizedContext);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const AuthSchemeSchema = z.union([
|
|
109
|
+
z.object({
|
|
110
|
+
type: z.literal("apiKey"),
|
|
111
|
+
id: z.string(),
|
|
112
|
+
in: z.enum(["header", "query"]),
|
|
113
|
+
name: z.string(),
|
|
114
|
+
description: z.string().optional()
|
|
115
|
+
}),
|
|
116
|
+
z.object({
|
|
117
|
+
type: z.literal("http"),
|
|
118
|
+
id: z.string(),
|
|
119
|
+
scheme: z.literal("bearer"),
|
|
120
|
+
bearerFormat: z.string().optional(),
|
|
121
|
+
description: z.string().optional()
|
|
122
|
+
}),
|
|
123
|
+
z.object({
|
|
124
|
+
type: z.literal("openIdConnect"),
|
|
125
|
+
id: z.string(),
|
|
126
|
+
openIdConnectUrl: z.string().url(),
|
|
127
|
+
audience: z.union([z.string(), z.array(z.string())]).optional(),
|
|
128
|
+
issuer: z.string().optional(),
|
|
129
|
+
jwksUri: z.string().url().optional(),
|
|
130
|
+
algorithms: z.array(z.string()).optional(),
|
|
131
|
+
description: z.string().optional()
|
|
132
|
+
})
|
|
133
|
+
]);
|
|
134
|
+
const A2AExtensionSchema = z.object({
|
|
135
|
+
uri: z.string().url(),
|
|
136
|
+
version: z.string().optional(),
|
|
137
|
+
required: z.boolean().optional()
|
|
138
|
+
});
|
|
139
|
+
const JsonRpcRequestSchema = z.object({
|
|
140
|
+
jsonrpc: z.literal("2.0"),
|
|
141
|
+
method: z.string(),
|
|
142
|
+
params: z.union([z.record(z.unknown()), z.array(z.unknown())]).optional(),
|
|
143
|
+
id: z.union([z.string(), z.number(), z.null()]).optional()
|
|
144
|
+
});
|
|
145
|
+
const PartSchema = z.union([
|
|
146
|
+
z.object({ type: z.literal("text"), text: z.string() }),
|
|
147
|
+
z.object({
|
|
148
|
+
type: z.literal("file"),
|
|
149
|
+
file: z.object({
|
|
150
|
+
name: z.string().optional(),
|
|
151
|
+
mimeType: z.string(),
|
|
152
|
+
bytes: z.string().optional(),
|
|
153
|
+
uri: z.string().optional()
|
|
154
|
+
})
|
|
155
|
+
}),
|
|
156
|
+
z.object({ type: z.literal("data"), data: z.record(z.unknown()) })
|
|
157
|
+
]);
|
|
158
|
+
const MessageSchema = z.object({
|
|
159
|
+
kind: z.literal("message").optional(),
|
|
160
|
+
role: z.enum(["user", "agent"]),
|
|
161
|
+
parts: z.array(PartSchema),
|
|
162
|
+
messageId: z.string(),
|
|
163
|
+
timestamp: z.string(),
|
|
164
|
+
// ISO8601
|
|
165
|
+
contextId: z.string().optional()
|
|
166
|
+
});
|
|
167
|
+
const PushNotificationConfigSchema = z.object({
|
|
168
|
+
id: z.string().optional(),
|
|
169
|
+
url: z.string().url(),
|
|
170
|
+
token: z.string().optional(),
|
|
171
|
+
authentication: AuthSchemeSchema.optional()
|
|
172
|
+
});
|
|
173
|
+
const MessageRequestConfigurationSchema = z.object({
|
|
174
|
+
blocking: z.boolean().optional(),
|
|
175
|
+
acceptedOutputModes: z.array(z.string()).optional(),
|
|
176
|
+
pushNotificationConfig: PushNotificationConfigSchema.optional(),
|
|
177
|
+
extensions: z.array(A2AExtensionSchema).optional()
|
|
178
|
+
});
|
|
179
|
+
const MessageSendParamsSchema = z.object({
|
|
180
|
+
message: MessageSchema,
|
|
181
|
+
taskId: z.string().optional(),
|
|
182
|
+
sessionId: z.string().optional(),
|
|
183
|
+
contextId: z.string().optional(),
|
|
184
|
+
configuration: MessageRequestConfigurationSchema.optional()
|
|
185
|
+
});
|
|
186
|
+
const TaskListParamsSchema = z.object({
|
|
187
|
+
contextId: z.string().optional(),
|
|
188
|
+
limit: z.number().int().positive().max(500).optional(),
|
|
189
|
+
offset: z.number().int().min(0).optional()
|
|
190
|
+
});
|
|
191
|
+
function validateRequest(schema, data) {
|
|
192
|
+
const result = schema.safeParse(data);
|
|
193
|
+
if (!result.success) {
|
|
194
|
+
throw new JsonRpcError(ErrorCodes.InvalidParams, "Invalid parameters", result.error.errors);
|
|
195
|
+
}
|
|
196
|
+
return result.data;
|
|
197
|
+
}
|
|
198
|
+
function validateMessageSendParams(data) {
|
|
199
|
+
return validateRequest(MessageSendParamsSchema, data);
|
|
200
|
+
}
|
|
201
|
+
function validateTaskListParams(data) {
|
|
202
|
+
return validateRequest(TaskListParamsSchema, data);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
class InMemoryTaskStorage {
|
|
206
|
+
tasks = /* @__PURE__ */ new Map();
|
|
207
|
+
contextIndex = /* @__PURE__ */ new Map();
|
|
208
|
+
pushNotifications = /* @__PURE__ */ new Map();
|
|
209
|
+
insertTask(task) {
|
|
210
|
+
const storedTask = structuredClone(task);
|
|
211
|
+
this.tasks.set(storedTask.id, storedTask);
|
|
212
|
+
this.syncContextIndex(void 0, storedTask.contextId, storedTask.id);
|
|
213
|
+
return structuredClone(storedTask);
|
|
214
|
+
}
|
|
215
|
+
getTask(taskId) {
|
|
216
|
+
const task = this.tasks.get(taskId);
|
|
217
|
+
return task ? structuredClone(task) : void 0;
|
|
218
|
+
}
|
|
219
|
+
saveTask(task) {
|
|
220
|
+
const previousTask = this.tasks.get(task.id);
|
|
221
|
+
const storedTask = structuredClone(task);
|
|
222
|
+
this.tasks.set(task.id, storedTask);
|
|
223
|
+
this.syncContextIndex(previousTask?.contextId, storedTask.contextId, storedTask.id);
|
|
224
|
+
}
|
|
225
|
+
getAllTasks() {
|
|
226
|
+
return Array.from(this.tasks.values(), (task) => structuredClone(task));
|
|
227
|
+
}
|
|
228
|
+
getTasksByContextId(contextId) {
|
|
229
|
+
const ids = this.contextIndex.get(contextId);
|
|
230
|
+
if (!ids) {
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
return Array.from(ids).map((taskId) => this.tasks.get(taskId)).filter((task) => task !== void 0).map((task) => structuredClone(task));
|
|
234
|
+
}
|
|
235
|
+
setPushNotification(taskId, config) {
|
|
236
|
+
if (!this.tasks.has(taskId)) {
|
|
237
|
+
return void 0;
|
|
238
|
+
}
|
|
239
|
+
const storedConfig = structuredClone(config);
|
|
240
|
+
this.pushNotifications.set(taskId, storedConfig);
|
|
241
|
+
return structuredClone(storedConfig);
|
|
242
|
+
}
|
|
243
|
+
getPushNotification(taskId) {
|
|
244
|
+
const config = this.pushNotifications.get(taskId);
|
|
245
|
+
return config ? structuredClone(config) : void 0;
|
|
246
|
+
}
|
|
247
|
+
syncContextIndex(previousContextId, nextContextId, taskId) {
|
|
248
|
+
if (previousContextId && previousContextId !== nextContextId) {
|
|
249
|
+
const previousIds = this.contextIndex.get(previousContextId);
|
|
250
|
+
previousIds?.delete(taskId);
|
|
251
|
+
if (previousIds?.size === 0) {
|
|
252
|
+
this.contextIndex.delete(previousContextId);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (!nextContextId) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const nextIds = this.contextIndex.get(nextContextId) ?? /* @__PURE__ */ new Set();
|
|
259
|
+
nextIds.add(taskId);
|
|
260
|
+
this.contextIndex.set(nextContextId, nextIds);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
class TaskManager extends EventEmitter {
|
|
265
|
+
constructor(storage = new InMemoryTaskStorage()) {
|
|
266
|
+
super();
|
|
267
|
+
this.storage = storage;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Creates a new task and stores it in memory.
|
|
271
|
+
*
|
|
272
|
+
* @param sessionId Optional session identifier.
|
|
273
|
+
* @param contextId Optional conversation context identifier.
|
|
274
|
+
* @returns Newly created task.
|
|
275
|
+
*/
|
|
276
|
+
createTask(sessionId, contextId) {
|
|
277
|
+
const task = {
|
|
278
|
+
kind: "task",
|
|
279
|
+
id: randomUUID(),
|
|
280
|
+
status: {
|
|
281
|
+
state: "submitted",
|
|
282
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
283
|
+
},
|
|
284
|
+
history: [],
|
|
285
|
+
artifacts: [],
|
|
286
|
+
extensions: [],
|
|
287
|
+
metadata: {},
|
|
288
|
+
...sessionId ? { sessionId } : {},
|
|
289
|
+
...contextId ? { contextId } : {}
|
|
290
|
+
};
|
|
291
|
+
const storedTask = this.storage.insertTask(task);
|
|
292
|
+
this.emitTaskUpdated(storedTask, "created");
|
|
293
|
+
return storedTask;
|
|
294
|
+
}
|
|
295
|
+
getTask(taskId) {
|
|
296
|
+
return this.storage.getTask(taskId);
|
|
297
|
+
}
|
|
298
|
+
getAllTasks() {
|
|
299
|
+
return this.storage.getAllTasks();
|
|
300
|
+
}
|
|
301
|
+
getTasksByContext(contextId) {
|
|
302
|
+
return this.storage.getTasksByContextId(contextId);
|
|
303
|
+
}
|
|
304
|
+
getTasksByContextId(contextId) {
|
|
305
|
+
return this.getTasksByContext(contextId);
|
|
306
|
+
}
|
|
307
|
+
addHistoryMessage(taskId, message) {
|
|
308
|
+
const task = this.storage.getTask(taskId);
|
|
309
|
+
if (!task) {
|
|
310
|
+
return void 0;
|
|
311
|
+
}
|
|
312
|
+
task.history.push({
|
|
313
|
+
...message,
|
|
314
|
+
...message.contextId ?? task.contextId ? { contextId: message.contextId ?? task.contextId } : {}
|
|
315
|
+
});
|
|
316
|
+
this.storage.saveTask(task);
|
|
317
|
+
this.emitTaskUpdated(task, "message");
|
|
318
|
+
return task;
|
|
319
|
+
}
|
|
320
|
+
addArtifact(taskId, artifact) {
|
|
321
|
+
const task = this.storage.getTask(taskId);
|
|
322
|
+
if (!task) {
|
|
323
|
+
return void 0;
|
|
324
|
+
}
|
|
325
|
+
const nextArtifact = {
|
|
326
|
+
...artifact,
|
|
327
|
+
...artifact.extensions ?? task.extensions ? { extensions: artifact.extensions ?? task.extensions } : {},
|
|
328
|
+
metadata: {
|
|
329
|
+
...artifact.metadata ?? {},
|
|
330
|
+
...task.contextId ? { contextId: task.contextId } : {}
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
task.artifacts = [...task.artifacts ?? [], nextArtifact];
|
|
334
|
+
this.storage.saveTask(task);
|
|
335
|
+
this.emitTaskUpdated(task, "artifact");
|
|
336
|
+
return task;
|
|
337
|
+
}
|
|
338
|
+
updateTaskState(taskId, state, historyMessage, metadata) {
|
|
339
|
+
const task = this.storage.getTask(taskId);
|
|
340
|
+
if (!task) {
|
|
341
|
+
return void 0;
|
|
342
|
+
}
|
|
343
|
+
task.status = {
|
|
344
|
+
state,
|
|
345
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
346
|
+
...typeof metadata?.message === "string" ? { message: metadata.message } : {}
|
|
347
|
+
};
|
|
348
|
+
if (historyMessage) {
|
|
349
|
+
task.history.push({
|
|
350
|
+
...historyMessage,
|
|
351
|
+
...historyMessage.contextId ?? task.contextId ? { contextId: historyMessage.contextId ?? task.contextId } : {}
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
if (metadata) {
|
|
355
|
+
task.metadata = { ...task.metadata ?? {}, ...metadata };
|
|
356
|
+
}
|
|
357
|
+
this.storage.saveTask(task);
|
|
358
|
+
this.emitTaskUpdated(task, "state");
|
|
359
|
+
return task;
|
|
360
|
+
}
|
|
361
|
+
cancelTask(taskId) {
|
|
362
|
+
return this.updateTaskState(taskId, "canceled");
|
|
363
|
+
}
|
|
364
|
+
setPushNotification(taskId, config) {
|
|
365
|
+
const task = this.storage.getTask(taskId);
|
|
366
|
+
if (!task) {
|
|
367
|
+
return void 0;
|
|
368
|
+
}
|
|
369
|
+
const storedConfig = this.storage.setPushNotification(taskId, config);
|
|
370
|
+
this.emitTaskUpdated(task, "push-config");
|
|
371
|
+
return storedConfig;
|
|
372
|
+
}
|
|
373
|
+
getPushNotification(taskId) {
|
|
374
|
+
return this.storage.getPushNotification(taskId);
|
|
375
|
+
}
|
|
376
|
+
setTaskExtensions(taskId, extensions) {
|
|
377
|
+
const task = this.storage.getTask(taskId);
|
|
378
|
+
if (!task) {
|
|
379
|
+
return void 0;
|
|
380
|
+
}
|
|
381
|
+
task.extensions = extensions;
|
|
382
|
+
this.storage.saveTask(task);
|
|
383
|
+
return task;
|
|
384
|
+
}
|
|
385
|
+
getTaskCounts() {
|
|
386
|
+
return this.storage.getAllTasks().reduce(
|
|
387
|
+
(counts, task) => {
|
|
388
|
+
counts.total += 1;
|
|
389
|
+
switch (task.status.state) {
|
|
390
|
+
case "submitted":
|
|
391
|
+
counts.submitted += 1;
|
|
392
|
+
counts.active += 1;
|
|
393
|
+
break;
|
|
394
|
+
case "working":
|
|
395
|
+
counts.working += 1;
|
|
396
|
+
counts.active += 1;
|
|
397
|
+
break;
|
|
398
|
+
case "input-required":
|
|
399
|
+
counts.inputRequired += 1;
|
|
400
|
+
counts.active += 1;
|
|
401
|
+
break;
|
|
402
|
+
case "completed":
|
|
403
|
+
counts.completed += 1;
|
|
404
|
+
break;
|
|
405
|
+
case "failed":
|
|
406
|
+
counts.failed += 1;
|
|
407
|
+
break;
|
|
408
|
+
case "canceled":
|
|
409
|
+
counts.canceled += 1;
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
return counts;
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
total: 0,
|
|
416
|
+
active: 0,
|
|
417
|
+
completed: 0,
|
|
418
|
+
failed: 0,
|
|
419
|
+
canceled: 0,
|
|
420
|
+
submitted: 0,
|
|
421
|
+
inputRequired: 0,
|
|
422
|
+
working: 0
|
|
423
|
+
}
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
emitTaskUpdated(task, reason) {
|
|
427
|
+
this.emit("taskUpdated", { task: structuredClone(task), reason });
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
class SSEStreamer {
|
|
432
|
+
clients = /* @__PURE__ */ new Map();
|
|
433
|
+
/**
|
|
434
|
+
* Add a client to the stream map (allows multiple subscribers per task).
|
|
435
|
+
* @param taskId The task ID associated with the stream.
|
|
436
|
+
* @param res The Express Response object.
|
|
437
|
+
*/
|
|
438
|
+
addClient(taskId, res) {
|
|
439
|
+
if (!this.clients.has(taskId)) {
|
|
440
|
+
this.clients.set(taskId, /* @__PURE__ */ new Set());
|
|
441
|
+
}
|
|
442
|
+
const taskClients = this.clients.get(taskId);
|
|
443
|
+
if (!taskClients) {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
taskClients.add(res);
|
|
447
|
+
res.writeHead(200, {
|
|
448
|
+
"Content-Type": "text/event-stream",
|
|
449
|
+
"Cache-Control": "no-cache",
|
|
450
|
+
Connection: "keep-alive",
|
|
451
|
+
"Access-Control-Allow-Origin": "*"
|
|
452
|
+
});
|
|
453
|
+
res.on("close", () => {
|
|
454
|
+
this.removeClient(taskId, res);
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Broadcast an event to all subscribers of a specific task.
|
|
459
|
+
* @param taskId The task ID.
|
|
460
|
+
* @param event The SSE event type (e.g. 'task_updated').
|
|
461
|
+
* @param data The JSON data payload.
|
|
462
|
+
*/
|
|
463
|
+
sendEvent(taskId, event, data) {
|
|
464
|
+
const taskClients = this.clients.get(taskId);
|
|
465
|
+
if (taskClients) {
|
|
466
|
+
const span = a2aMeshTracer.startSpan("sse.sendEvent", {
|
|
467
|
+
attributes: {
|
|
468
|
+
"a2a.task_id": taskId,
|
|
469
|
+
"a2a.event": event,
|
|
470
|
+
"a2a.client_count": taskClients.size
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
try {
|
|
474
|
+
const payload = `event: ${event}
|
|
475
|
+
data: ${JSON.stringify(data)}
|
|
476
|
+
|
|
477
|
+
`;
|
|
478
|
+
taskClients.forEach((res) => {
|
|
479
|
+
try {
|
|
480
|
+
res.write(payload);
|
|
481
|
+
} catch {
|
|
482
|
+
this.removeClient(taskId, res);
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
} finally {
|
|
486
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
487
|
+
span.end();
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Send a task update and complete the streams if terminal state reached.
|
|
493
|
+
* @param taskId The task ID.
|
|
494
|
+
* @param task The updated task object.
|
|
495
|
+
*/
|
|
496
|
+
sendTaskUpdate(taskId, task) {
|
|
497
|
+
this.sendEvent(taskId, "task_updated", task);
|
|
498
|
+
if (["completed", "failed", "canceled"].includes(task.status.state)) {
|
|
499
|
+
this.closeStream(taskId);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Close all streams for a specific task.
|
|
504
|
+
* @param taskId The task ID.
|
|
505
|
+
*/
|
|
506
|
+
closeStream(taskId) {
|
|
507
|
+
const taskClients = this.clients.get(taskId);
|
|
508
|
+
if (taskClients) {
|
|
509
|
+
taskClients.forEach((res) => {
|
|
510
|
+
try {
|
|
511
|
+
res.end();
|
|
512
|
+
} catch {
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
this.clients.delete(taskId);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Remove a specific client from the map.
|
|
520
|
+
* @param taskId The task ID.
|
|
521
|
+
* @param res The specific Response object.
|
|
522
|
+
*/
|
|
523
|
+
removeClient(taskId, res) {
|
|
524
|
+
const taskClients = this.clients.get(taskId);
|
|
525
|
+
if (taskClients) {
|
|
526
|
+
taskClients.delete(res);
|
|
527
|
+
if (taskClients.size === 0) {
|
|
528
|
+
this.clients.delete(taskId);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
class CircuitOpenError extends Error {
|
|
535
|
+
constructor(message) {
|
|
536
|
+
super(message);
|
|
537
|
+
this.name = "CircuitOpenError";
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
class CircuitBreaker {
|
|
541
|
+
constructor(name, options = {}) {
|
|
542
|
+
this.name = name;
|
|
543
|
+
this.failureThreshold = options.failureThreshold ?? 5;
|
|
544
|
+
this.recoveryTimeoutMs = options.recoveryTimeoutMs ?? 6e4;
|
|
545
|
+
this.successThreshold = options.successThreshold ?? 2;
|
|
546
|
+
}
|
|
547
|
+
state = "CLOSED";
|
|
548
|
+
failureCount = 0;
|
|
549
|
+
successCount = 0;
|
|
550
|
+
nextAttemptAt = 0;
|
|
551
|
+
failureThreshold;
|
|
552
|
+
recoveryTimeoutMs;
|
|
553
|
+
successThreshold;
|
|
554
|
+
getState() {
|
|
555
|
+
return this.state;
|
|
556
|
+
}
|
|
557
|
+
async execute(fn) {
|
|
558
|
+
if (this.state === "OPEN") {
|
|
559
|
+
if (Date.now() < this.nextAttemptAt) {
|
|
560
|
+
throw new CircuitOpenError(
|
|
561
|
+
`Circuit breaker [${this.name}] is OPEN. Next attempt at ${new Date(this.nextAttemptAt).toISOString()}`
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
this.state = "HALF_OPEN";
|
|
565
|
+
this.successCount = 0;
|
|
566
|
+
}
|
|
567
|
+
try {
|
|
568
|
+
const result = await fn();
|
|
569
|
+
this.onSuccess();
|
|
570
|
+
return result;
|
|
571
|
+
} catch (error) {
|
|
572
|
+
this.onFailure();
|
|
573
|
+
throw error;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
onSuccess() {
|
|
577
|
+
this.failureCount = 0;
|
|
578
|
+
if (this.state === "HALF_OPEN") {
|
|
579
|
+
this.successCount += 1;
|
|
580
|
+
if (this.successCount >= this.successThreshold) {
|
|
581
|
+
this.state = "CLOSED";
|
|
582
|
+
this.successCount = 0;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
onFailure() {
|
|
587
|
+
this.failureCount += 1;
|
|
588
|
+
if (this.state === "HALF_OPEN" || this.failureCount >= this.failureThreshold) {
|
|
589
|
+
this.state = "OPEN";
|
|
590
|
+
this.nextAttemptAt = Date.now() + this.recoveryTimeoutMs;
|
|
591
|
+
this.failureCount = 0;
|
|
592
|
+
this.successCount = 0;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
class PushNotificationService {
|
|
598
|
+
constructor(options = {}) {
|
|
599
|
+
this.options = options;
|
|
600
|
+
}
|
|
601
|
+
breakers = /* @__PURE__ */ new Map();
|
|
602
|
+
getBreakerFor(url) {
|
|
603
|
+
const existing = this.breakers.get(url);
|
|
604
|
+
if (existing) {
|
|
605
|
+
return existing;
|
|
606
|
+
}
|
|
607
|
+
const breaker = new CircuitBreaker(`push:${url}`, this.options.circuitBreaker);
|
|
608
|
+
this.breakers.set(url, breaker);
|
|
609
|
+
return breaker;
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Sends a task snapshot to the configured webhook endpoint.
|
|
613
|
+
*
|
|
614
|
+
* @param config Webhook delivery configuration.
|
|
615
|
+
* @param task Current task snapshot to deliver.
|
|
616
|
+
* @returns Resolves when delivery succeeds.
|
|
617
|
+
* @throws When the endpoint responds with a non-2xx status.
|
|
618
|
+
*/
|
|
619
|
+
async sendNotification(config, task) {
|
|
620
|
+
const breaker = this.getBreakerFor(config.url);
|
|
621
|
+
try {
|
|
622
|
+
await breaker.execute(async () => {
|
|
623
|
+
const headers = {
|
|
624
|
+
"Content-Type": "application/json"
|
|
625
|
+
};
|
|
626
|
+
let url = config.url;
|
|
627
|
+
if (config.token) {
|
|
628
|
+
headers["X-A2A-Notification-Token"] = config.token;
|
|
629
|
+
}
|
|
630
|
+
if (config.authentication && config.token) {
|
|
631
|
+
if (config.authentication.type === "apiKey") {
|
|
632
|
+
if (config.authentication.in === "header") {
|
|
633
|
+
headers[config.authentication.name] = config.token;
|
|
634
|
+
} else {
|
|
635
|
+
const nextUrl = new URL(url);
|
|
636
|
+
nextUrl.searchParams.set(config.authentication.name, config.token);
|
|
637
|
+
url = nextUrl.toString();
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
if (config.authentication.type === "http") {
|
|
641
|
+
headers.Authorization = `Bearer ${config.token}`;
|
|
642
|
+
}
|
|
643
|
+
if (config.authentication.type === "openIdConnect") {
|
|
644
|
+
headers.Authorization = `Bearer ${config.token}`;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
const response = await fetch(url, {
|
|
648
|
+
method: "POST",
|
|
649
|
+
headers,
|
|
650
|
+
body: JSON.stringify(task)
|
|
651
|
+
});
|
|
652
|
+
if (!response.ok) {
|
|
653
|
+
throw new Error(`Push notification failed: HTTP ${response.status}`);
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
} catch (error) {
|
|
657
|
+
if (error instanceof CircuitOpenError) {
|
|
658
|
+
logger.warn("Push notification skipped - circuit open", {
|
|
659
|
+
taskId: task.id,
|
|
660
|
+
url: config.url
|
|
661
|
+
});
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
throw error;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Executes an async action with exponential backoff.
|
|
669
|
+
*
|
|
670
|
+
* @param fn Async operation to retry.
|
|
671
|
+
* @param maxRetries Maximum number of retry attempts.
|
|
672
|
+
* @returns Resolves when the operation succeeds.
|
|
673
|
+
* @throws Re-throws the last failure when retries are exhausted.
|
|
674
|
+
*/
|
|
675
|
+
async retryWithBackoff(fn, maxRetries = 3) {
|
|
676
|
+
let attempt = 0;
|
|
677
|
+
let lastError;
|
|
678
|
+
while (attempt < maxRetries) {
|
|
679
|
+
try {
|
|
680
|
+
await fn();
|
|
681
|
+
return;
|
|
682
|
+
} catch (error) {
|
|
683
|
+
if (error instanceof CircuitOpenError) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
lastError = error;
|
|
687
|
+
attempt += 1;
|
|
688
|
+
if (attempt >= maxRetries) {
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
const delayMs = 250 * 2 ** (attempt - 1);
|
|
692
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
class A2AServer {
|
|
700
|
+
constructor(agentCard, options = {}) {
|
|
701
|
+
this.options = options;
|
|
702
|
+
this.app = express();
|
|
703
|
+
this.app.use(express.json());
|
|
704
|
+
this.agentCard = agentCard;
|
|
705
|
+
this.taskManager = new TaskManager(options.taskStorage ?? new InMemoryTaskStorage());
|
|
706
|
+
this.streamer = new SSEStreamer();
|
|
707
|
+
this.pushNotificationService = new PushNotificationService();
|
|
708
|
+
this.authMiddleware = options.auth ? new JwtAuthMiddleware(options.auth) : void 0;
|
|
709
|
+
this.setupMiddleware();
|
|
710
|
+
this.setupRoutes();
|
|
711
|
+
this.bindTaskObservers();
|
|
712
|
+
}
|
|
713
|
+
app;
|
|
714
|
+
agentCard;
|
|
715
|
+
taskManager;
|
|
716
|
+
streamer;
|
|
717
|
+
pushNotificationService;
|
|
718
|
+
authMiddleware;
|
|
719
|
+
startedAt = Date.now();
|
|
720
|
+
setupMiddleware() {
|
|
721
|
+
this.app.use((req, _res, next) => {
|
|
722
|
+
req.requestId = req.header("x-request-id") ?? randomUUID();
|
|
723
|
+
next();
|
|
724
|
+
});
|
|
725
|
+
if (this.options.rateLimit) {
|
|
726
|
+
this.app.use(createRateLimiter(this.options.rateLimit, this.options.rateLimitStore));
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
setupRoutes() {
|
|
730
|
+
const serveCard = (_req, res) => {
|
|
731
|
+
res.json(this.agentCard);
|
|
732
|
+
};
|
|
733
|
+
this.app.get("/.well-known/agent-card.json", serveCard);
|
|
734
|
+
this.app.get("/.well-known/agent.json", serveCard);
|
|
735
|
+
this.app.get("/health", (_req, res) => {
|
|
736
|
+
const taskCounts = this.taskManager.getTaskCounts();
|
|
737
|
+
const memoryUsage = process.memoryUsage();
|
|
738
|
+
const payload = {
|
|
739
|
+
status: "healthy",
|
|
740
|
+
version: this.agentCard.version,
|
|
741
|
+
protocol: "A2A/1.0",
|
|
742
|
+
uptime: Math.floor((Date.now() - this.startedAt) / 1e3),
|
|
743
|
+
tasks: {
|
|
744
|
+
active: taskCounts.active,
|
|
745
|
+
completed: taskCounts.completed,
|
|
746
|
+
failed: taskCounts.failed,
|
|
747
|
+
total: taskCounts.total
|
|
748
|
+
},
|
|
749
|
+
memory: {
|
|
750
|
+
heapUsedMb: Number((memoryUsage.heapUsed / 1024 / 1024).toFixed(1)),
|
|
751
|
+
heapTotalMb: Number((memoryUsage.heapTotal / 1024 / 1024).toFixed(1))
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
res.json(payload);
|
|
755
|
+
});
|
|
756
|
+
const handleJsonRpc = async (req, res) => {
|
|
757
|
+
try {
|
|
758
|
+
const rpcReq = validateRequest(JsonRpcRequestSchema, req.body);
|
|
759
|
+
const result = await this.handleRpc(rpcReq, { req });
|
|
760
|
+
const response = {
|
|
761
|
+
jsonrpc: "2.0",
|
|
762
|
+
result,
|
|
763
|
+
id: rpcReq.id || null
|
|
764
|
+
};
|
|
765
|
+
res.json(response);
|
|
766
|
+
} catch (err) {
|
|
767
|
+
if (err instanceof JsonRpcError) {
|
|
768
|
+
res.json({
|
|
769
|
+
jsonrpc: "2.0",
|
|
770
|
+
error: { code: err.code, message: err.message, data: err.data },
|
|
771
|
+
id: req.body.id || null
|
|
772
|
+
});
|
|
773
|
+
} else {
|
|
774
|
+
logger.error("Unhandled internal error", { error: String(err) });
|
|
775
|
+
res.json({
|
|
776
|
+
jsonrpc: "2.0",
|
|
777
|
+
error: { code: ErrorCodes.InternalError, message: "Internal Error" },
|
|
778
|
+
id: req.body.id || null
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
this.app.post("/", handleJsonRpc);
|
|
784
|
+
this.app.post("/a2a/jsonrpc", handleJsonRpc);
|
|
785
|
+
const handleStreamRequest = (req, res) => {
|
|
786
|
+
const taskId = req.query.taskId;
|
|
787
|
+
if (!taskId) {
|
|
788
|
+
res.status(400).send("Missing taskId query parameter");
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
this.streamer.addClient(taskId, res);
|
|
792
|
+
const task = this.taskManager.getTask(taskId);
|
|
793
|
+
if (task) {
|
|
794
|
+
this.streamer.sendTaskUpdate(taskId, task);
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
this.app.get("/stream", handleStreamRequest);
|
|
798
|
+
this.app.get("/a2a/stream", handleStreamRequest);
|
|
799
|
+
}
|
|
800
|
+
bindTaskObservers() {
|
|
801
|
+
this.taskManager.on("taskUpdated", async ({ task, reason }) => {
|
|
802
|
+
if (reason !== "push-config") {
|
|
803
|
+
this.streamer.sendTaskUpdate(task.id, task);
|
|
804
|
+
}
|
|
805
|
+
if (reason === "state") {
|
|
806
|
+
const pushConfig = this.taskManager.getPushNotification(task.id);
|
|
807
|
+
if (pushConfig) {
|
|
808
|
+
try {
|
|
809
|
+
await this.pushNotificationService.retryWithBackoff(
|
|
810
|
+
() => this.pushNotificationService.sendNotification(pushConfig, task)
|
|
811
|
+
);
|
|
812
|
+
} catch (error) {
|
|
813
|
+
logger.error("Push notification delivery failed", {
|
|
814
|
+
taskId: task.id,
|
|
815
|
+
contextId: task.contextId,
|
|
816
|
+
error
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
async handleRpc(req, context) {
|
|
824
|
+
const span = a2aMeshTracer.startSpan("a2a.handleRpc", {
|
|
825
|
+
attributes: {
|
|
826
|
+
"rpc.method": req.method,
|
|
827
|
+
"a2a.agent_name": this.agentCard.name
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
const requestId = context.req.requestId;
|
|
831
|
+
const startedAt = Date.now();
|
|
832
|
+
let failed = false;
|
|
833
|
+
try {
|
|
834
|
+
const params = req.params ?? {};
|
|
835
|
+
switch (req.method) {
|
|
836
|
+
case "message/send":
|
|
837
|
+
case "message/stream":
|
|
838
|
+
return await this.handleMessageRequest(validateMessageSendParams(params), req.method);
|
|
839
|
+
case "tasks/get": {
|
|
840
|
+
if (typeof params.taskId !== "string") {
|
|
841
|
+
throw new JsonRpcError(ErrorCodes.InvalidParams, "Missing taskId");
|
|
842
|
+
}
|
|
843
|
+
const task = this.taskManager.getTask(params.taskId);
|
|
844
|
+
if (!task) {
|
|
845
|
+
throw new JsonRpcError(ErrorCodes.TaskNotFound, "Task not found");
|
|
846
|
+
}
|
|
847
|
+
return task;
|
|
848
|
+
}
|
|
849
|
+
case "tasks/cancel": {
|
|
850
|
+
if (typeof params.taskId !== "string") {
|
|
851
|
+
throw new JsonRpcError(ErrorCodes.InvalidParams, "Missing taskId");
|
|
852
|
+
}
|
|
853
|
+
const task = this.taskManager.cancelTask(params.taskId);
|
|
854
|
+
if (!task) {
|
|
855
|
+
throw new JsonRpcError(ErrorCodes.TaskNotFound, "Task not found");
|
|
856
|
+
}
|
|
857
|
+
return task;
|
|
858
|
+
}
|
|
859
|
+
case "tasks/pushNotification/set": {
|
|
860
|
+
if (typeof params.taskId !== "string" || typeof params.pushNotificationConfig !== "object") {
|
|
861
|
+
throw new JsonRpcError(
|
|
862
|
+
ErrorCodes.InvalidParams,
|
|
863
|
+
"Missing taskId or pushNotificationConfig"
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
const config = this.taskManager.setPushNotification(
|
|
867
|
+
params.taskId,
|
|
868
|
+
params.pushNotificationConfig
|
|
869
|
+
);
|
|
870
|
+
if (!config) {
|
|
871
|
+
throw new JsonRpcError(ErrorCodes.TaskNotFound, "Task not found");
|
|
872
|
+
}
|
|
873
|
+
return config;
|
|
874
|
+
}
|
|
875
|
+
case "tasks/pushNotification/get": {
|
|
876
|
+
if (typeof params.taskId !== "string") {
|
|
877
|
+
throw new JsonRpcError(ErrorCodes.InvalidParams, "Missing taskId");
|
|
878
|
+
}
|
|
879
|
+
return this.taskManager.getPushNotification(params.taskId) ?? null;
|
|
880
|
+
}
|
|
881
|
+
case "tasks/list": {
|
|
882
|
+
const { contextId, limit = 50, offset = 0 } = validateTaskListParams(params);
|
|
883
|
+
const tasks = contextId ? this.taskManager.getTasksByContext(contextId) : this.taskManager.getAllTasks();
|
|
884
|
+
return {
|
|
885
|
+
tasks: tasks.slice(offset, offset + limit),
|
|
886
|
+
total: tasks.length
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
case "agent/authenticatedExtendedCard": {
|
|
890
|
+
if (!this.agentCard.capabilities?.extendedAgentCard) {
|
|
891
|
+
throw new JsonRpcError(ErrorCodes.UnsupportedOperation, "Extended card not supported");
|
|
892
|
+
}
|
|
893
|
+
if (this.authMiddleware) {
|
|
894
|
+
try {
|
|
895
|
+
await this.authMiddleware.authenticateRequest(context.req);
|
|
896
|
+
} catch (error) {
|
|
897
|
+
throw new JsonRpcError(ErrorCodes.Unauthorized, "Unauthorized", {
|
|
898
|
+
reason: String(error)
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
return this.agentCard;
|
|
903
|
+
}
|
|
904
|
+
default:
|
|
905
|
+
throw new JsonRpcError(ErrorCodes.MethodNotFound, `Method ${req.method} not found`);
|
|
906
|
+
}
|
|
907
|
+
} catch (error) {
|
|
908
|
+
failed = true;
|
|
909
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
|
|
910
|
+
throw error;
|
|
911
|
+
} finally {
|
|
912
|
+
if (!failed) {
|
|
913
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
914
|
+
}
|
|
915
|
+
span.end();
|
|
916
|
+
logger.info("Handled RPC request", {
|
|
917
|
+
...requestId ? { requestId } : {},
|
|
918
|
+
method: req.method,
|
|
919
|
+
agentName: this.agentCard.name,
|
|
920
|
+
durationMs: Date.now() - startedAt
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
async handleMessageRequest(params, method) {
|
|
925
|
+
const task = params.taskId ? this.taskManager.getTask(params.taskId) : this.taskManager.createTask(params.sessionId, params.contextId ?? params.message.contextId);
|
|
926
|
+
if (!task) {
|
|
927
|
+
throw new JsonRpcError(ErrorCodes.TaskNotFound, "Task not found");
|
|
928
|
+
}
|
|
929
|
+
const appliedExtensions = this.negotiateExtensions(params.configuration?.extensions ?? []);
|
|
930
|
+
this.taskManager.setTaskExtensions(task.id, appliedExtensions);
|
|
931
|
+
if (params.configuration?.pushNotificationConfig) {
|
|
932
|
+
this.taskManager.setPushNotification(task.id, params.configuration.pushNotificationConfig);
|
|
933
|
+
}
|
|
934
|
+
const message = {
|
|
935
|
+
...params.message,
|
|
936
|
+
kind: params.message.kind ?? "message",
|
|
937
|
+
...params.message.contextId ?? task.contextId ? { contextId: params.message.contextId ?? task.contextId } : {}
|
|
938
|
+
};
|
|
939
|
+
this.taskManager.addHistoryMessage(task.id, message);
|
|
940
|
+
this.taskManager.updateTaskState(task.id, "working");
|
|
941
|
+
this.processTaskInternal(task, message).catch((error) => {
|
|
942
|
+
logger.error("Task processing failed", {
|
|
943
|
+
taskId: task.id,
|
|
944
|
+
...task.contextId ? { contextId: task.contextId } : {},
|
|
945
|
+
error
|
|
946
|
+
});
|
|
947
|
+
});
|
|
948
|
+
if (method === "message/stream") {
|
|
949
|
+
return this.taskManager.getTask(task.id) ?? task;
|
|
950
|
+
}
|
|
951
|
+
return this.taskManager.getTask(task.id) ?? task;
|
|
952
|
+
}
|
|
953
|
+
negotiateExtensions(requestedExtensions) {
|
|
954
|
+
if (requestedExtensions.length === 0) {
|
|
955
|
+
return [];
|
|
956
|
+
}
|
|
957
|
+
const supported = new Set((this.agentCard.extensions ?? []).map((extension) => extension.uri));
|
|
958
|
+
const applied = [];
|
|
959
|
+
for (const extension of requestedExtensions) {
|
|
960
|
+
if (supported.has(extension.uri)) {
|
|
961
|
+
applied.push(extension.uri);
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
if (extension.required) {
|
|
965
|
+
throw new JsonRpcError(
|
|
966
|
+
ErrorCodes.ExtensionRequired,
|
|
967
|
+
`Required extension not supported: ${extension.uri}. See: https://oaslananka.github.io/a2a-mesh/protocol/extensions`
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
return applied;
|
|
972
|
+
}
|
|
973
|
+
normalizeArtifacts(task, artifacts) {
|
|
974
|
+
return artifacts.map((artifact) => ({
|
|
975
|
+
...artifact,
|
|
976
|
+
...artifact.extensions ?? task.extensions ? { extensions: artifact.extensions ?? task.extensions } : {},
|
|
977
|
+
metadata: {
|
|
978
|
+
...artifact.metadata ?? {},
|
|
979
|
+
taskId: task.id,
|
|
980
|
+
...task.contextId ? { contextId: task.contextId } : {},
|
|
981
|
+
appliedExtensions: task.extensions ?? []
|
|
982
|
+
}
|
|
983
|
+
}));
|
|
984
|
+
}
|
|
985
|
+
getExpressApp() {
|
|
986
|
+
return this.app;
|
|
987
|
+
}
|
|
988
|
+
getAgentCard() {
|
|
989
|
+
return this.agentCard;
|
|
990
|
+
}
|
|
991
|
+
getTaskManager() {
|
|
992
|
+
return this.taskManager;
|
|
993
|
+
}
|
|
994
|
+
static fromCard(card) {
|
|
995
|
+
return card.protocolVersion === "1.0" ? card : { ...card, protocolVersion: "1.0" };
|
|
996
|
+
}
|
|
997
|
+
async processTaskInternal(task, message) {
|
|
998
|
+
const span = a2aMeshTracer.startSpan("a2a.processTask", {
|
|
999
|
+
attributes: {
|
|
1000
|
+
"a2a.task_id": task.id,
|
|
1001
|
+
"a2a.context_id": task.contextId ?? ""
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
try {
|
|
1005
|
+
const artifacts = await this.handleTask(task, message);
|
|
1006
|
+
this.normalizeArtifacts(task, artifacts).forEach((artifact) => {
|
|
1007
|
+
this.taskManager.addArtifact(task.id, artifact);
|
|
1008
|
+
});
|
|
1009
|
+
this.taskManager.updateTaskState(task.id, "completed");
|
|
1010
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
1011
|
+
} catch (error) {
|
|
1012
|
+
this.taskManager.updateTaskState(task.id, "failed");
|
|
1013
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
|
|
1014
|
+
throw error;
|
|
1015
|
+
} finally {
|
|
1016
|
+
span.end();
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
start(port) {
|
|
1020
|
+
return this.app.listen(port, () => {
|
|
1021
|
+
logger.info(`A2A Server listening on port ${port}`);
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
function parseTask(row) {
|
|
1027
|
+
return row ? JSON.parse(row.task_json) : void 0;
|
|
1028
|
+
}
|
|
1029
|
+
function parsePushNotification(row) {
|
|
1030
|
+
return row ? JSON.parse(row.config_json) : void 0;
|
|
1031
|
+
}
|
|
1032
|
+
class SqliteTaskStorage {
|
|
1033
|
+
db;
|
|
1034
|
+
constructor(path) {
|
|
1035
|
+
const require = createRequire(import.meta.url);
|
|
1036
|
+
const imported = require("better-sqlite3");
|
|
1037
|
+
const Database = "default" in imported ? imported.default : imported;
|
|
1038
|
+
this.db = new Database(path);
|
|
1039
|
+
this.db.exec(`
|
|
1040
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
1041
|
+
id TEXT PRIMARY KEY,
|
|
1042
|
+
context_id TEXT,
|
|
1043
|
+
task_json TEXT NOT NULL
|
|
1044
|
+
);
|
|
1045
|
+
CREATE TABLE IF NOT EXISTS push_notifications (
|
|
1046
|
+
task_id TEXT PRIMARY KEY,
|
|
1047
|
+
config_json TEXT NOT NULL
|
|
1048
|
+
);
|
|
1049
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_context_id ON tasks(context_id);
|
|
1050
|
+
`);
|
|
1051
|
+
}
|
|
1052
|
+
insertTask(task) {
|
|
1053
|
+
this.db.prepare("INSERT INTO tasks (id, context_id, task_json) VALUES (?, ?, ?)").run(task.id, task.contextId ?? null, JSON.stringify(task));
|
|
1054
|
+
return structuredClone(task);
|
|
1055
|
+
}
|
|
1056
|
+
getTask(taskId) {
|
|
1057
|
+
return parseTask(
|
|
1058
|
+
this.db.prepare("SELECT task_json FROM tasks WHERE id = ?").get(taskId)
|
|
1059
|
+
);
|
|
1060
|
+
}
|
|
1061
|
+
saveTask(task) {
|
|
1062
|
+
this.db.prepare("UPDATE tasks SET context_id = ?, task_json = ? WHERE id = ?").run(task.contextId ?? null, JSON.stringify(task), task.id);
|
|
1063
|
+
}
|
|
1064
|
+
getAllTasks() {
|
|
1065
|
+
return this.db.prepare("SELECT task_json FROM tasks ORDER BY id").all().map((row) => JSON.parse(row.task_json));
|
|
1066
|
+
}
|
|
1067
|
+
getTasksByContextId(contextId) {
|
|
1068
|
+
return this.db.prepare("SELECT task_json FROM tasks WHERE context_id = ? ORDER BY id").all(contextId).map((row) => JSON.parse(row.task_json));
|
|
1069
|
+
}
|
|
1070
|
+
setPushNotification(taskId, config) {
|
|
1071
|
+
if (!this.getTask(taskId)) {
|
|
1072
|
+
return void 0;
|
|
1073
|
+
}
|
|
1074
|
+
this.db.prepare(
|
|
1075
|
+
"INSERT INTO push_notifications (task_id, config_json) VALUES (?, ?) ON CONFLICT(task_id) DO UPDATE SET config_json = excluded.config_json"
|
|
1076
|
+
).run(taskId, JSON.stringify(config));
|
|
1077
|
+
return structuredClone(config);
|
|
1078
|
+
}
|
|
1079
|
+
getPushNotification(taskId) {
|
|
1080
|
+
return parsePushNotification(
|
|
1081
|
+
this.db.prepare(
|
|
1082
|
+
"SELECT config_json FROM push_notifications WHERE task_id = ?"
|
|
1083
|
+
).get(taskId)
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
class A2AClient {
|
|
1089
|
+
constructor(baseUrl, options = {}) {
|
|
1090
|
+
this.baseUrl = baseUrl;
|
|
1091
|
+
this.fetchImplementation = options.fetchImplementation ?? fetch;
|
|
1092
|
+
this.cardPath = options.cardPath ?? "/.well-known/agent-card.json";
|
|
1093
|
+
this.rpcPath = options.rpcPath ?? "/a2a/jsonrpc";
|
|
1094
|
+
this.streamPath = options.streamPath ?? "/a2a/stream";
|
|
1095
|
+
this.eventSourceImplementation = options.eventSourceImplementation ?? EventSource;
|
|
1096
|
+
this.interceptors = options.interceptors ?? [];
|
|
1097
|
+
this.headers = options.headers ?? {};
|
|
1098
|
+
this.retry = {
|
|
1099
|
+
maxAttempts: options.retry?.maxAttempts ?? 3,
|
|
1100
|
+
backoffMs: options.retry?.backoffMs ?? 1e3,
|
|
1101
|
+
retryOn: options.retry?.retryOn ?? [502, 503, 504]
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
fetchImplementation;
|
|
1105
|
+
cardPath;
|
|
1106
|
+
rpcPath;
|
|
1107
|
+
streamPath;
|
|
1108
|
+
eventSourceImplementation;
|
|
1109
|
+
interceptors;
|
|
1110
|
+
headers;
|
|
1111
|
+
retry;
|
|
1112
|
+
async resolveCard() {
|
|
1113
|
+
const canonicalUrl = new URL(this.cardPath, this.baseUrl).toString();
|
|
1114
|
+
const legacyUrl = new URL("/.well-known/agent.json", this.baseUrl).toString();
|
|
1115
|
+
const response = await this.fetchWithRetry(canonicalUrl);
|
|
1116
|
+
if (response.ok) {
|
|
1117
|
+
return await response.json();
|
|
1118
|
+
}
|
|
1119
|
+
const legacyResponse = await this.fetchWithRetry(legacyUrl);
|
|
1120
|
+
if (!legacyResponse.ok) {
|
|
1121
|
+
throw new Error(`Failed to resolve agent card from ${canonicalUrl}`);
|
|
1122
|
+
}
|
|
1123
|
+
return await legacyResponse.json();
|
|
1124
|
+
}
|
|
1125
|
+
async sendMessage(params) {
|
|
1126
|
+
return this.rpc("message/send", this.normalizeParams(params));
|
|
1127
|
+
}
|
|
1128
|
+
async sendMessageStream(params) {
|
|
1129
|
+
const task = await this.rpc(
|
|
1130
|
+
"message/stream",
|
|
1131
|
+
this.normalizeParams(params)
|
|
1132
|
+
);
|
|
1133
|
+
return this.subscribeToTask(task.id);
|
|
1134
|
+
}
|
|
1135
|
+
async getTask(taskId) {
|
|
1136
|
+
return this.rpc("tasks/get", { taskId });
|
|
1137
|
+
}
|
|
1138
|
+
async listTasks(params = {}) {
|
|
1139
|
+
return this.rpc("tasks/list", params);
|
|
1140
|
+
}
|
|
1141
|
+
async cancelTask(taskId) {
|
|
1142
|
+
return this.rpc("tasks/cancel", { taskId });
|
|
1143
|
+
}
|
|
1144
|
+
async setPushNotification(taskId, pushNotificationConfig) {
|
|
1145
|
+
return this.rpc("tasks/pushNotification/set", {
|
|
1146
|
+
taskId,
|
|
1147
|
+
pushNotificationConfig
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
async getPushNotification(taskId) {
|
|
1151
|
+
return this.rpc(
|
|
1152
|
+
"tasks/pushNotification/get",
|
|
1153
|
+
{
|
|
1154
|
+
taskId
|
|
1155
|
+
}
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
async health() {
|
|
1159
|
+
const response = await this.fetchWithRetry(new URL("/health", this.baseUrl), {
|
|
1160
|
+
headers: this.headers
|
|
1161
|
+
});
|
|
1162
|
+
if (!response.ok) {
|
|
1163
|
+
throw new Error(`Health check failed with status ${response.status}`);
|
|
1164
|
+
}
|
|
1165
|
+
return await response.json();
|
|
1166
|
+
}
|
|
1167
|
+
async rpc(method, params) {
|
|
1168
|
+
const options = { headers: { ...this.headers } };
|
|
1169
|
+
const payload = {
|
|
1170
|
+
jsonrpc: "2.0",
|
|
1171
|
+
id: `${Date.now()}`,
|
|
1172
|
+
method,
|
|
1173
|
+
params
|
|
1174
|
+
};
|
|
1175
|
+
for (const interceptor of this.interceptors) {
|
|
1176
|
+
await interceptor.before({ method, body: payload, options });
|
|
1177
|
+
}
|
|
1178
|
+
const response = await this.fetchWithRetry(new URL(this.rpcPath, this.baseUrl), {
|
|
1179
|
+
method: "POST",
|
|
1180
|
+
headers: {
|
|
1181
|
+
"Content-Type": "application/json",
|
|
1182
|
+
...options.headers ?? {},
|
|
1183
|
+
...options.serviceParameters ?? {}
|
|
1184
|
+
},
|
|
1185
|
+
...options.signal ? { signal: options.signal } : {},
|
|
1186
|
+
body: JSON.stringify(payload)
|
|
1187
|
+
});
|
|
1188
|
+
if (!response.ok) {
|
|
1189
|
+
throw new Error(`RPC request failed with status ${response.status}`);
|
|
1190
|
+
}
|
|
1191
|
+
const json = await response.json();
|
|
1192
|
+
if ("error" in json) {
|
|
1193
|
+
const failure = json;
|
|
1194
|
+
throw new Error(`${failure.error.message} (${failure.error.code})`);
|
|
1195
|
+
}
|
|
1196
|
+
const success = json;
|
|
1197
|
+
for (const interceptor of this.interceptors) {
|
|
1198
|
+
await interceptor.after?.({ method, response: success.result });
|
|
1199
|
+
}
|
|
1200
|
+
return success.result;
|
|
1201
|
+
}
|
|
1202
|
+
normalizeParams(params) {
|
|
1203
|
+
if ("message" in params) {
|
|
1204
|
+
return params;
|
|
1205
|
+
}
|
|
1206
|
+
return { message: params };
|
|
1207
|
+
}
|
|
1208
|
+
async *subscribeToTask(taskId) {
|
|
1209
|
+
const streamUrl = new URL(this.streamPath, this.baseUrl);
|
|
1210
|
+
streamUrl.searchParams.set("taskId", taskId);
|
|
1211
|
+
const queue = [];
|
|
1212
|
+
let resolveNext;
|
|
1213
|
+
let closed = false;
|
|
1214
|
+
const source = new this.eventSourceImplementation(streamUrl.toString(), {
|
|
1215
|
+
headers: this.headers
|
|
1216
|
+
});
|
|
1217
|
+
const push = (data) => {
|
|
1218
|
+
queue.push(data);
|
|
1219
|
+
resolveNext?.();
|
|
1220
|
+
};
|
|
1221
|
+
source.addEventListener("task_updated", (event) => {
|
|
1222
|
+
const data = "data" in event ? JSON.parse(String(event.data)) : null;
|
|
1223
|
+
push(data);
|
|
1224
|
+
if (data && typeof data === "object" && "status" in data && typeof data.status === "object" && data.status !== null && "state" in data.status && ["completed", "failed", "canceled"].includes(String(data.status.state))) {
|
|
1225
|
+
closed = true;
|
|
1226
|
+
source.close();
|
|
1227
|
+
resolveNext?.();
|
|
1228
|
+
}
|
|
1229
|
+
});
|
|
1230
|
+
source.onerror = () => {
|
|
1231
|
+
closed = true;
|
|
1232
|
+
source.close();
|
|
1233
|
+
resolveNext?.();
|
|
1234
|
+
};
|
|
1235
|
+
try {
|
|
1236
|
+
while (!closed || queue.length > 0) {
|
|
1237
|
+
if (queue.length === 0) {
|
|
1238
|
+
await new Promise((resolve) => {
|
|
1239
|
+
resolveNext = resolve;
|
|
1240
|
+
});
|
|
1241
|
+
resolveNext = void 0;
|
|
1242
|
+
}
|
|
1243
|
+
const next = queue.shift();
|
|
1244
|
+
if (next !== void 0) {
|
|
1245
|
+
yield next;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
} finally {
|
|
1249
|
+
source.close();
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
async fetchWithRetry(input, init) {
|
|
1253
|
+
let lastError;
|
|
1254
|
+
for (let attempt = 1; attempt <= this.retry.maxAttempts; attempt += 1) {
|
|
1255
|
+
try {
|
|
1256
|
+
const response = await this.fetchImplementation(input, init);
|
|
1257
|
+
if (response.ok || attempt === this.retry.maxAttempts || !this.retry.retryOn.includes(response.status)) {
|
|
1258
|
+
return response;
|
|
1259
|
+
}
|
|
1260
|
+
} catch (error) {
|
|
1261
|
+
lastError = error;
|
|
1262
|
+
if (attempt === this.retry.maxAttempts) {
|
|
1263
|
+
throw error;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
await new Promise((resolve) => {
|
|
1267
|
+
setTimeout(resolve, this.retry.backoffMs * attempt);
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
throw new Error(
|
|
1271
|
+
`Request failed after ${this.retry.maxAttempts} attempts: ${String(lastError)}`
|
|
1272
|
+
);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
class AgentRegistryClient {
|
|
1277
|
+
constructor(baseUrl, fetchImplementation = fetch) {
|
|
1278
|
+
this.baseUrl = baseUrl;
|
|
1279
|
+
this.fetchImplementation = fetchImplementation;
|
|
1280
|
+
}
|
|
1281
|
+
async register(agentUrl, agentCard) {
|
|
1282
|
+
const response = await this.fetchImplementation(new URL("/agents/register", this.baseUrl), {
|
|
1283
|
+
method: "POST",
|
|
1284
|
+
headers: { "Content-Type": "application/json" },
|
|
1285
|
+
body: JSON.stringify({ agentUrl, agentCard })
|
|
1286
|
+
});
|
|
1287
|
+
if (!response.ok) {
|
|
1288
|
+
throw new Error(`Failed to register agent (${response.status})`);
|
|
1289
|
+
}
|
|
1290
|
+
return await response.json();
|
|
1291
|
+
}
|
|
1292
|
+
async listAgents() {
|
|
1293
|
+
const response = await this.fetchImplementation(new URL("/agents", this.baseUrl));
|
|
1294
|
+
if (!response.ok) {
|
|
1295
|
+
throw new Error(`Failed to list agents (${response.status})`);
|
|
1296
|
+
}
|
|
1297
|
+
return await response.json();
|
|
1298
|
+
}
|
|
1299
|
+
async getAgent(id) {
|
|
1300
|
+
const response = await this.fetchImplementation(new URL(`/agents/${id}`, this.baseUrl));
|
|
1301
|
+
if (!response.ok) {
|
|
1302
|
+
throw new Error(`Failed to fetch agent (${response.status})`);
|
|
1303
|
+
}
|
|
1304
|
+
return await response.json();
|
|
1305
|
+
}
|
|
1306
|
+
async searchAgents(query, filters = {}) {
|
|
1307
|
+
const url = new URL("/agents/search", this.baseUrl);
|
|
1308
|
+
url.searchParams.set("skill", query);
|
|
1309
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
1310
|
+
url.searchParams.set(key, value);
|
|
1311
|
+
}
|
|
1312
|
+
const response = await this.fetchImplementation(url);
|
|
1313
|
+
if (!response.ok) {
|
|
1314
|
+
throw new Error(`Failed to search agents (${response.status})`);
|
|
1315
|
+
}
|
|
1316
|
+
return await response.json();
|
|
1317
|
+
}
|
|
1318
|
+
async sendHeartbeat(id) {
|
|
1319
|
+
const response = await this.fetchImplementation(new URL(`/agents/${id}/heartbeat`, this.baseUrl), {
|
|
1320
|
+
method: "POST"
|
|
1321
|
+
});
|
|
1322
|
+
if (!response.ok) {
|
|
1323
|
+
throw new Error(`Failed to send heartbeat (${response.status})`);
|
|
1324
|
+
}
|
|
1325
|
+
return await response.json();
|
|
1326
|
+
}
|
|
1327
|
+
async health() {
|
|
1328
|
+
const response = await this.fetchImplementation(new URL("/health", this.baseUrl));
|
|
1329
|
+
if (!response.ok) {
|
|
1330
|
+
throw new Error(`Failed to fetch registry health (${response.status})`);
|
|
1331
|
+
}
|
|
1332
|
+
return await response.json();
|
|
1333
|
+
}
|
|
1334
|
+
async *events() {
|
|
1335
|
+
const source = new EventSource(new URL("/events", this.baseUrl).toString());
|
|
1336
|
+
const queue = [];
|
|
1337
|
+
let resolveNext;
|
|
1338
|
+
let closed = false;
|
|
1339
|
+
source.addEventListener("registry_update", (event) => {
|
|
1340
|
+
const data = "data" in event ? JSON.parse(String(event.data)) : null;
|
|
1341
|
+
queue.push(data);
|
|
1342
|
+
resolveNext?.();
|
|
1343
|
+
});
|
|
1344
|
+
source.onerror = () => {
|
|
1345
|
+
closed = true;
|
|
1346
|
+
source.close();
|
|
1347
|
+
resolveNext?.();
|
|
1348
|
+
};
|
|
1349
|
+
try {
|
|
1350
|
+
while (!closed || queue.length > 0) {
|
|
1351
|
+
if (queue.length === 0) {
|
|
1352
|
+
await new Promise((resolve) => {
|
|
1353
|
+
resolveNext = resolve;
|
|
1354
|
+
});
|
|
1355
|
+
resolveNext = void 0;
|
|
1356
|
+
}
|
|
1357
|
+
const next = queue.shift();
|
|
1358
|
+
if (next !== void 0) {
|
|
1359
|
+
yield next;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
} finally {
|
|
1363
|
+
source.close();
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
function createAuthenticatingFetchWithRetry(baseFetch, handler) {
|
|
1369
|
+
return async (input, init) => {
|
|
1370
|
+
const nextInit = {
|
|
1371
|
+
...init ?? {},
|
|
1372
|
+
headers: {
|
|
1373
|
+
...init?.headers ?? {},
|
|
1374
|
+
...await handler.headers()
|
|
1375
|
+
}
|
|
1376
|
+
};
|
|
1377
|
+
const firstResponse = await baseFetch(input, nextInit);
|
|
1378
|
+
const retryHeaders = handler.shouldRetryWithHeaders ? await handler.shouldRetryWithHeaders(nextInit, firstResponse) : void 0;
|
|
1379
|
+
if (!retryHeaders) {
|
|
1380
|
+
return firstResponse;
|
|
1381
|
+
}
|
|
1382
|
+
return baseFetch(input, {
|
|
1383
|
+
...nextInit,
|
|
1384
|
+
headers: {
|
|
1385
|
+
...nextInit.headers ?? {},
|
|
1386
|
+
...retryHeaders
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
export { A2AClient, A2AServer, AgentRegistryClient, CircuitBreaker, CircuitOpenError, ErrorCodes, InMemoryTaskStorage, JsonRpcError, JsonRpcRequestSchema, JwtAuthMiddleware, MessageRequestConfigurationSchema, MessageSchema, MessageSendParamsSchema, PartSchema, PushNotificationConfigSchema, PushNotificationService, SSEStreamer, SqliteTaskStorage, TaskListParamsSchema, TaskManager, a2aMeshTracer, createAuthenticatingFetchWithRetry, createRateLimiter, logger, normalizeAgentCard, validateMessageSendParams, validateRequest, validateTaskListParams };
|