@cogitator-ai/a2a 0.3.11 → 0.4.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.
Files changed (68) hide show
  1. package/README.md +64 -7
  2. package/dist/adapters/express.d.ts.map +1 -1
  3. package/dist/adapters/express.js +22 -22
  4. package/dist/adapters/express.js.map +1 -1
  5. package/dist/adapters/fastify.d.ts.map +1 -1
  6. package/dist/adapters/fastify.js +23 -24
  7. package/dist/adapters/fastify.js.map +1 -1
  8. package/dist/adapters/hono.d.ts.map +1 -1
  9. package/dist/adapters/hono.js +25 -8
  10. package/dist/adapters/hono.js.map +1 -1
  11. package/dist/adapters/koa.d.ts.map +1 -1
  12. package/dist/adapters/koa.js +16 -23
  13. package/dist/adapters/koa.js.map +1 -1
  14. package/dist/adapters/next.d.ts.map +1 -1
  15. package/dist/adapters/next.js +22 -19
  16. package/dist/adapters/next.js.map +1 -1
  17. package/dist/adapters/shared.d.ts +18 -0
  18. package/dist/adapters/shared.d.ts.map +1 -0
  19. package/dist/adapters/shared.js +52 -0
  20. package/dist/adapters/shared.js.map +1 -0
  21. package/dist/adapters/sse-error-event.d.ts +3 -0
  22. package/dist/adapters/sse-error-event.d.ts.map +1 -0
  23. package/dist/adapters/sse-error-event.js +11 -0
  24. package/dist/adapters/sse-error-event.js.map +1 -0
  25. package/dist/agent-card.d.ts.map +1 -1
  26. package/dist/agent-card.js +15 -2
  27. package/dist/agent-card.js.map +1 -1
  28. package/dist/client.d.ts +24 -4
  29. package/dist/client.d.ts.map +1 -1
  30. package/dist/client.js +163 -83
  31. package/dist/client.js.map +1 -1
  32. package/dist/errors.d.ts +1 -0
  33. package/dist/errors.d.ts.map +1 -1
  34. package/dist/errors.js +3 -0
  35. package/dist/errors.js.map +1 -1
  36. package/dist/index.d.ts +6 -5
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +3 -3
  39. package/dist/index.js.map +1 -1
  40. package/dist/json-rpc.d.ts +2 -2
  41. package/dist/json-rpc.d.ts.map +1 -1
  42. package/dist/json-rpc.js +11 -7
  43. package/dist/json-rpc.js.map +1 -1
  44. package/dist/push-notifications.d.ts +8 -1
  45. package/dist/push-notifications.d.ts.map +1 -1
  46. package/dist/push-notifications.js +178 -25
  47. package/dist/push-notifications.js.map +1 -1
  48. package/dist/redis-task-store.d.ts +1 -0
  49. package/dist/redis-task-store.d.ts.map +1 -1
  50. package/dist/redis-task-store.js +44 -4
  51. package/dist/redis-task-store.js.map +1 -1
  52. package/dist/server.d.ts +19 -2
  53. package/dist/server.d.ts.map +1 -1
  54. package/dist/server.js +287 -135
  55. package/dist/server.js.map +1 -1
  56. package/dist/task-manager.d.ts +27 -2
  57. package/dist/task-manager.d.ts.map +1 -1
  58. package/dist/task-manager.js +126 -36
  59. package/dist/task-manager.js.map +1 -1
  60. package/dist/task-store.d.ts +10 -0
  61. package/dist/task-store.d.ts.map +1 -1
  62. package/dist/task-store.js +28 -0
  63. package/dist/task-store.js.map +1 -1
  64. package/dist/types.d.ts +17 -0
  65. package/dist/types.d.ts.map +1 -1
  66. package/dist/types.js +7 -0
  67. package/dist/types.js.map +1 -1
  68. package/package.json +5 -5
package/dist/server.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { A2AServerConfig, AgentCard, A2AStreamEvent } from './types.js';
2
2
  import type { JsonRpcResponse } from './json-rpc.js';
3
+ type HeaderGetter = (name: string) => string | null | undefined;
3
4
  export declare class A2AServer {
4
5
  private agents;
5
6
  private cogitator;
@@ -12,13 +13,28 @@ export declare class A2AServer {
12
13
  private cardSigning?;
13
14
  private extendedCardGenerator?;
14
15
  private allowPrivateUrls;
16
+ private auth?;
15
17
  constructor(config: A2AServerConfig);
18
+ /**
19
+ * Extract the credential configured by `auth` from request headers:
20
+ * the token of an `Authorization: Bearer` header, or the API key header.
21
+ * Framework adapters call this and pass the result to handleJsonRpc / handleJsonRpcStream.
22
+ */
23
+ getAuthToken(getHeader: HeaderGetter): string | undefined;
16
24
  getAgentCard(agentName?: string): AgentCard;
17
25
  getAgentCards(): AgentCard[];
18
- handleJsonRpc(body: unknown): Promise<JsonRpcResponse>;
19
- handleJsonRpcStream(body: unknown): AsyncGenerator<A2AStreamEvent>;
26
+ handleJsonRpc(body: unknown, authToken?: string): Promise<JsonRpcResponse | null>;
27
+ handleJsonRpcStream(body: unknown, authToken?: string, signal?: AbortSignal): AsyncGenerator<A2AStreamEvent>;
28
+ private validateAuth;
20
29
  private routeMethod;
21
30
  private handleSendMessage;
31
+ private validateInitialPushConfig;
32
+ private registerInitialPushConfig;
33
+ private assertWebhookAllowed;
34
+ /**
35
+ * Apply historyLength / acceptedOutputModes from the request configuration
36
+ */
37
+ private shapeTask;
22
38
  private handleGetTask;
23
39
  private handleCancelTask;
24
40
  private handleListTasks;
@@ -28,4 +44,5 @@ export declare class A2AServer {
28
44
  private handleDeletePushNotification;
29
45
  private handleExtendedCard;
30
46
  }
47
+ export {};
31
48
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EACf,SAAS,EAIT,cAAc,EAOf,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAkB,eAAe,EAAE,MAAM,eAAe,CAAC;AAoBrE,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,UAAU,CAAyB;IAC3C,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,qBAAqB,CAAwB;IACrD,OAAO,CAAC,UAAU,CAAyB;IAC3C,OAAO,CAAC,WAAW,CAAC,CAA0B;IAC9C,OAAO,CAAC,qBAAqB,CAAC,CAA2C;IACzE,OAAO,CAAC,gBAAgB,CAAU;gBAEtB,MAAM,EAAE,eAAe;IA6CnC,YAAY,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS;IAe3C,aAAa,IAAI,SAAS,EAAE;IAQtB,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC;IA6BrD,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,cAAc,CAAC,cAAc,CAAC;YAkK3D,WAAW;YA2BX,iBAAiB;YAwBjB,aAAa;YAMb,gBAAgB;YAMhB,eAAe;YAMf,4BAA4B;YAe5B,yBAAyB;YAQzB,2BAA2B;YAM3B,4BAA4B;YAS5B,kBAAkB;CASjC"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EACf,SAAS,EAIT,cAAc,EAQf,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAkB,eAAe,EAAE,MAAM,eAAe,CAAC;AAoBrE,KAAK,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;AAsChE,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,UAAU,CAAyB;IAC3C,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,qBAAqB,CAAwB;IACrD,OAAO,CAAC,UAAU,CAAyB;IAC3C,OAAO,CAAC,WAAW,CAAC,CAA0B;IAC9C,OAAO,CAAC,qBAAqB,CAAC,CAA2C;IACzE,OAAO,CAAC,gBAAgB,CAAU;IAClC,OAAO,CAAC,IAAI,CAAC,CAAgB;gBAEjB,MAAM,EAAE,eAAe;IA4DnC;;;;OAIG;IACH,YAAY,CAAC,SAAS,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS;IAWzD,YAAY,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS;IAe3C,aAAa,IAAI,SAAS,EAAE;IAQtB,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IA4ChF,mBAAmB,CACxB,IAAI,EAAE,OAAO,EACb,SAAS,CAAC,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,WAAW,GACnB,cAAc,CAAC,cAAc,CAAC;YAiKnB,YAAY;YAWZ,WAAW;YA2BX,iBAAiB;IAqC/B,OAAO,CAAC,yBAAyB;YASnB,yBAAyB;IAiBvC,OAAO,CAAC,oBAAoB;IAS5B;;OAEG;IACH,OAAO,CAAC,SAAS;YAsBH,aAAa;YAOb,gBAAgB;YAMhB,eAAe;YAcf,4BAA4B;YAa5B,yBAAyB;YAQzB,2BAA2B;YAM3B,4BAA4B;YAS5B,kBAAkB;CASjC"}
package/dist/server.js CHANGED
@@ -5,7 +5,35 @@ import { A2AError } from './errors.js';
5
5
  import * as errors from './errors.js';
6
6
  import { InMemoryTaskStore } from './task-store.js';
7
7
  import { InMemoryPushNotificationStore, PushNotificationSender, validateWebhookUrl, } from './push-notifications.js';
8
- import { isTerminalState } from './types.js';
8
+ import { isStreamFinalState } from './types.js';
9
+ const MAX_LIST_LIMIT = 1000;
10
+ function failedStatusEvent(message, taskId = '') {
11
+ const timestamp = new Date().toISOString();
12
+ return {
13
+ type: 'status-update',
14
+ taskId,
15
+ status: { state: 'failed', timestamp, message },
16
+ timestamp,
17
+ };
18
+ }
19
+ function errorMessageOf(error) {
20
+ if (error instanceof A2AError)
21
+ return error.message;
22
+ return error instanceof Error ? error.message : String(error);
23
+ }
24
+ function isValidMessage(message) {
25
+ if (!message || typeof message !== 'object')
26
+ return false;
27
+ const m = message;
28
+ return (m.role === 'user' || m.role === 'agent') && Array.isArray(m.parts);
29
+ }
30
+ function assertOptionalNonNegativeInt(value, name) {
31
+ if (value === undefined)
32
+ return;
33
+ if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {
34
+ throw new A2AError(errors.invalidParams(`${name} must be a non-negative integer`));
35
+ }
36
+ }
9
37
  export class A2AServer {
10
38
  agents;
11
39
  cogitator;
@@ -18,6 +46,7 @@ export class A2AServer {
18
46
  cardSigning;
19
47
  extendedCardGenerator;
20
48
  allowPrivateUrls;
49
+ auth;
21
50
  constructor(config) {
22
51
  const agentNames = Object.keys(config.agents);
23
52
  if (agentNames.length === 0) {
@@ -30,9 +59,10 @@ export class A2AServer {
30
59
  this.cardSigning = config.cardSigning;
31
60
  this.extendedCardGenerator = config.extendedCardGenerator;
32
61
  this.allowPrivateUrls = config.allowPrivateUrls ?? false;
62
+ this.auth = config.auth;
33
63
  this.pushNotificationStore =
34
64
  config.pushNotificationStore ?? new InMemoryPushNotificationStore();
35
- this.pushSender = new PushNotificationSender(this.pushNotificationStore);
65
+ this.pushSender = new PushNotificationSender(this.pushNotificationStore, this.allowPrivateUrls);
36
66
  this.taskManager = new TaskManager({
37
67
  taskStore: config.taskStore ?? new InMemoryTaskStore(),
38
68
  });
@@ -53,9 +83,38 @@ export class A2AServer {
53
83
  extendedAgentCard: hasExtendedCard,
54
84
  },
55
85
  });
86
+ if (this.auth) {
87
+ const schemeName = this.auth.type === 'bearer' ? 'bearer' : 'apiKey';
88
+ card.securitySchemes = {
89
+ [schemeName]: this.auth.type === 'bearer'
90
+ ? { type: 'http', scheme: 'bearer' }
91
+ : {
92
+ type: 'apiKey',
93
+ location: 'header',
94
+ parameterName: this.auth.headerName ?? 'x-api-key',
95
+ },
96
+ };
97
+ card.security = [{ [schemeName]: [] }];
98
+ }
56
99
  this.agentCards.set(name, card);
57
100
  }
58
101
  }
102
+ /**
103
+ * Extract the credential configured by `auth` from request headers:
104
+ * the token of an `Authorization: Bearer` header, or the API key header.
105
+ * Framework adapters call this and pass the result to handleJsonRpc / handleJsonRpcStream.
106
+ */
107
+ getAuthToken(getHeader) {
108
+ if (!this.auth)
109
+ return undefined;
110
+ if (this.auth.type === 'bearer') {
111
+ const header = getHeader('authorization');
112
+ const match = header ? /^Bearer\s+(.+)$/i.exec(header.trim()) : null;
113
+ return match ? match[1].trim() : undefined;
114
+ }
115
+ const key = getHeader(this.auth.headerName ?? 'x-api-key');
116
+ return key ? key.trim() || undefined : undefined;
117
+ }
59
118
  getAgentCard(agentName) {
60
119
  let card;
61
120
  if (agentName) {
@@ -79,7 +138,7 @@ export class A2AServer {
79
138
  }
80
139
  return cards;
81
140
  }
82
- async handleJsonRpc(body) {
141
+ async handleJsonRpc(body, authToken) {
83
142
  let request;
84
143
  try {
85
144
  const parsed = parseJsonRpcRequest(body);
@@ -90,172 +149,195 @@ export class A2AServer {
90
149
  }
91
150
  catch (e) {
92
151
  if (e instanceof JsonRpcParseError) {
93
- return createErrorResponse(null, errors.parseError(e.message));
152
+ return createErrorResponse(null, e.code === -32600 ? errors.invalidRequest(e.message) : errors.parseError(e.message));
94
153
  }
95
154
  return createErrorResponse(null, errors.internalError(String(e)));
96
155
  }
156
+ try {
157
+ await this.validateAuth(authToken);
158
+ }
159
+ catch (e) {
160
+ if (request.id === undefined)
161
+ return null;
162
+ if (e instanceof A2AError) {
163
+ return createErrorResponse(request.id, e.jsonRpcError);
164
+ }
165
+ return createErrorResponse(request.id, errors.internalError(String(e)));
166
+ }
97
167
  try {
98
168
  const result = await this.routeMethod(request.method, request.params);
169
+ if (request.id === undefined)
170
+ return null;
99
171
  return createSuccessResponse(request.id, result);
100
172
  }
101
173
  catch (e) {
174
+ if (request.id === undefined)
175
+ return null;
102
176
  if (e instanceof A2AError) {
103
177
  return createErrorResponse(request.id, e.jsonRpcError);
104
178
  }
105
179
  return createErrorResponse(request.id, errors.internalError(e instanceof Error ? e.message : String(e)));
106
180
  }
107
181
  }
108
- async *handleJsonRpcStream(body) {
182
+ async *handleJsonRpcStream(body, authToken, signal) {
109
183
  let request;
110
184
  try {
111
185
  const parsed = parseJsonRpcRequest(body);
112
186
  if (Array.isArray(parsed)) {
113
- yield {
114
- type: 'status-update',
115
- taskId: '',
116
- status: {
117
- state: 'failed',
118
- timestamp: new Date().toISOString(),
119
- message: 'Batch requests are not supported',
120
- },
121
- timestamp: new Date().toISOString(),
122
- };
187
+ yield failedStatusEvent('Batch requests are not supported');
123
188
  return;
124
189
  }
125
190
  request = parsed;
126
191
  }
127
192
  catch (e) {
128
- yield {
129
- type: 'status-update',
130
- taskId: '',
131
- status: {
132
- state: 'failed',
133
- timestamp: new Date().toISOString(),
134
- message: e instanceof Error ? e.message : 'Invalid JSON-RPC request',
135
- },
136
- timestamp: new Date().toISOString(),
137
- };
193
+ yield failedStatusEvent(e instanceof Error ? e.message : 'Invalid JSON-RPC request');
194
+ return;
195
+ }
196
+ try {
197
+ await this.validateAuth(authToken);
198
+ }
199
+ catch (e) {
200
+ yield failedStatusEvent(e instanceof Error ? e.message : 'Authentication failed');
138
201
  return;
139
202
  }
140
203
  if (request.method !== 'message/stream') {
141
- yield {
142
- type: 'status-update',
143
- taskId: '',
144
- status: {
145
- state: 'failed',
146
- timestamp: new Date().toISOString(),
147
- message: `Unsupported method for streaming: ${request.method}`,
148
- },
149
- timestamp: new Date().toISOString(),
150
- };
204
+ yield failedStatusEvent(`Unsupported method for streaming: ${request.method}`);
151
205
  return;
152
206
  }
153
207
  const params = request.params;
154
- if (!params?.message?.parts || !params.message.role) {
155
- yield {
156
- type: 'status-update',
157
- taskId: '',
158
- status: {
159
- state: 'failed',
160
- timestamp: new Date().toISOString(),
161
- message: 'Missing required parameter: message with role and parts',
162
- },
163
- timestamp: new Date().toISOString(),
164
- };
208
+ if (!params || !isValidMessage(params.message)) {
209
+ yield failedStatusEvent('Missing required parameter: message with role and parts');
165
210
  return;
166
211
  }
212
+ const message = params.message;
167
213
  const agentName = params.agentName ?? Object.keys(this.agents)[0];
168
214
  const agent = this.agents[agentName];
169
215
  if (!agent) {
170
- yield {
171
- type: 'status-update',
172
- taskId: '',
173
- status: {
174
- state: 'failed',
175
- timestamp: new Date().toISOString(),
176
- message: `Agent not found: ${agentName}`,
177
- },
178
- timestamp: new Date().toISOString(),
179
- };
216
+ yield failedStatusEvent(`Agent not found: ${agentName}`);
180
217
  return;
181
218
  }
219
+ if (signal?.aborted)
220
+ return;
182
221
  const eventQueue = [];
183
- let resolve = null;
184
- let taskId = null;
222
+ let wake = null;
223
+ const notify = () => {
224
+ if (wake) {
225
+ wake();
226
+ wake = null;
227
+ }
228
+ };
229
+ let taskId = message.taskId ?? null;
185
230
  const onEvent = (event) => {
186
231
  if (taskId && event.taskId === taskId) {
187
232
  eventQueue.push(event);
188
- if (resolve) {
189
- resolve();
190
- resolve = null;
191
- }
233
+ notify();
192
234
  }
193
235
  };
236
+ let executingTaskId = null;
237
+ const onAbort = () => {
238
+ if (executingTaskId)
239
+ this.taskManager.abortExecution(executingTaskId);
240
+ notify();
241
+ };
194
242
  this.taskManager.on('event', onEvent);
195
- let task;
196
- if (params.message.taskId) {
197
- taskId = params.message.taskId;
198
- task = await this.taskManager.continueTask(params.message.taskId, params.message);
199
- }
200
- else {
201
- task = await this.taskManager.createTask(params.message, params.message.contextId);
202
- taskId = task.id;
203
- }
204
- const onToken = (token) => {
205
- const event = {
206
- type: 'token',
207
- taskId: task.id,
208
- token,
209
- timestamp: new Date().toISOString(),
210
- };
211
- eventQueue.push(event);
212
- if (resolve) {
213
- resolve();
214
- resolve = null;
243
+ signal?.addEventListener('abort', onAbort, { once: true });
244
+ let executionPromise = null;
245
+ try {
246
+ let task;
247
+ const isContinued = !!message.taskId;
248
+ try {
249
+ this.validateInitialPushConfig(params.configuration);
250
+ if (isContinued) {
251
+ task = await this.taskManager.continueTask(message.taskId, message);
252
+ }
253
+ else {
254
+ task = await this.taskManager.createTask(message, message.contextId);
255
+ taskId = task.id;
256
+ await this.registerInitialPushConfig(task.id, params.configuration);
257
+ }
215
258
  }
216
- };
217
- let executionDone = false;
218
- const executionPromise = this.taskManager
219
- .executeTask(task, this.cogitator, agent, params.message, onToken)
220
- .then(() => {
221
- executionDone = true;
222
- if (resolve) {
223
- resolve();
224
- resolve = null;
259
+ catch (error) {
260
+ yield failedStatusEvent(errorMessageOf(error), taskId ?? '');
261
+ return;
225
262
  }
226
- });
227
- try {
228
- yield {
229
- type: 'status-update',
230
- taskId: task.id,
231
- status: task.status,
232
- timestamp: new Date().toISOString(),
263
+ const currentTaskId = task.id;
264
+ const onToken = (token) => {
265
+ const event = {
266
+ type: 'token',
267
+ taskId: currentTaskId,
268
+ token,
269
+ timestamp: new Date().toISOString(),
270
+ };
271
+ eventQueue.push(event);
272
+ notify();
233
273
  };
234
- let done = false;
235
- while (!done) {
236
- if (eventQueue.length > 0) {
237
- const event = eventQueue.shift();
274
+ let executionDone = false;
275
+ let executionError;
276
+ executingTaskId = task.id;
277
+ executionPromise = this.taskManager
278
+ .executeTask(task, this.cogitator, agent, message, {
279
+ onToken,
280
+ timeout: params.configuration?.timeout,
281
+ })
282
+ .then(() => {
283
+ executionDone = true;
284
+ notify();
285
+ }, (error) => {
286
+ executionError = error;
287
+ executionDone = true;
288
+ notify();
289
+ });
290
+ if (!isContinued) {
291
+ yield {
292
+ type: 'status-update',
293
+ taskId: task.id,
294
+ status: task.status,
295
+ timestamp: new Date().toISOString(),
296
+ };
297
+ }
298
+ while (!signal?.aborted) {
299
+ const event = eventQueue.shift();
300
+ if (event) {
238
301
  yield event;
239
- if (event.type === 'status-update' && isTerminalState(event.status.state)) {
240
- done = true;
302
+ if (event.type === 'status-update' && isStreamFinalState(event.status.state)) {
303
+ return;
241
304
  }
305
+ continue;
242
306
  }
243
- else if (executionDone) {
307
+ if (executionDone) {
308
+ if (executionError !== undefined) {
309
+ yield failedStatusEvent(errorMessageOf(executionError), task.id);
310
+ return;
311
+ }
244
312
  const finalTask = await this.taskManager.getTask(task.id);
245
- if (isTerminalState(finalTask.status.state)) {
246
- done = true;
313
+ if (!isStreamFinalState(finalTask.status.state)) {
314
+ yield failedStatusEvent('Execution ended without reaching a terminal state', task.id);
247
315
  }
316
+ return;
248
317
  }
249
- else {
250
- await new Promise((r) => {
251
- resolve = r;
252
- });
253
- }
318
+ await new Promise((r) => {
319
+ wake = r;
320
+ });
254
321
  }
255
322
  }
256
323
  finally {
257
324
  this.taskManager.removeListener('event', onEvent);
258
- await executionPromise.catch(() => { });
325
+ signal?.removeEventListener('abort', onAbort);
326
+ if (executingTaskId)
327
+ this.taskManager.abortExecution(executingTaskId);
328
+ if (executionPromise)
329
+ await executionPromise;
330
+ }
331
+ }
332
+ async validateAuth(authToken) {
333
+ if (!this.auth)
334
+ return;
335
+ if (!authToken) {
336
+ throw new A2AError(errors.unauthorized('Authentication required'));
337
+ }
338
+ const valid = await this.auth.validate(authToken);
339
+ if (!valid) {
340
+ throw new A2AError(errors.unauthorized('Invalid credentials'));
259
341
  }
260
342
  }
261
343
  async routeMethod(method, params) {
@@ -285,69 +367,139 @@ export class A2AServer {
285
367
  }
286
368
  }
287
369
  async handleSendMessage(params) {
288
- const { message, agentName } = params;
289
- if (!message?.parts || !message.role) {
370
+ const { message, agentName, configuration } = (params ?? {});
371
+ if (!isValidMessage(message)) {
290
372
  throw new A2AError(errors.invalidParams('message is required with role and parts'));
291
373
  }
374
+ assertOptionalNonNegativeInt(configuration?.historyLength, 'configuration.historyLength');
375
+ assertOptionalNonNegativeInt(configuration?.timeout, 'configuration.timeout');
292
376
  const resolvedAgentName = agentName ?? Object.keys(this.agents)[0];
293
377
  const agent = this.agents[resolvedAgentName];
294
378
  if (!agent)
295
379
  throw new A2AError(errors.agentNotFound(resolvedAgentName));
380
+ this.validateInitialPushConfig(configuration);
381
+ let task;
296
382
  if (message.taskId) {
297
- const task = await this.taskManager.continueTask(message.taskId, message);
298
- return await this.taskManager.executeTask(task, this.cogitator, agent, message);
383
+ task = await this.taskManager.continueTask(message.taskId, message);
384
+ }
385
+ else {
386
+ task = await this.taskManager.createTask(message, message.contextId);
387
+ await this.registerInitialPushConfig(task.id, configuration);
388
+ }
389
+ const execution = this.taskManager.executeTask(task, this.cogitator, agent, message, {
390
+ timeout: configuration?.timeout,
391
+ });
392
+ if (configuration?.blocking === false) {
393
+ execution.catch((error) => {
394
+ process.stderr.write(`[a2a] Background task ${task.id} failed: ${errorMessageOf(error)}\n`);
395
+ });
396
+ return this.shapeTask(task, configuration);
397
+ }
398
+ return this.shapeTask(await execution, configuration);
399
+ }
400
+ validateInitialPushConfig(configuration) {
401
+ const pushConfig = configuration?.pushNotificationConfig;
402
+ if (!pushConfig)
403
+ return;
404
+ if (!pushConfig.webhookUrl) {
405
+ throw new A2AError(errors.invalidParams('pushNotificationConfig.webhookUrl is required'));
299
406
  }
300
- const task = await this.taskManager.createTask(message, message.contextId);
301
- return await this.taskManager.executeTask(task, this.cogitator, agent, message);
407
+ this.assertWebhookAllowed(pushConfig.webhookUrl);
408
+ }
409
+ async registerInitialPushConfig(taskId, configuration) {
410
+ const pushConfig = configuration?.pushNotificationConfig;
411
+ if (!pushConfig)
412
+ return;
413
+ try {
414
+ await this.pushNotificationStore.create(taskId, pushConfig);
415
+ }
416
+ catch (error) {
417
+ await this.taskManager.failTask(taskId, `Failed to register push notification: ${errorMessageOf(error)}`);
418
+ throw error;
419
+ }
420
+ }
421
+ assertWebhookAllowed(webhookUrl) {
422
+ if (this.allowPrivateUrls)
423
+ return;
424
+ try {
425
+ validateWebhookUrl(webhookUrl);
426
+ }
427
+ catch (e) {
428
+ throw new A2AError(errors.invalidParams(e instanceof Error ? e.message : String(e)));
429
+ }
430
+ }
431
+ /**
432
+ * Apply historyLength / acceptedOutputModes from the request configuration
433
+ */
434
+ shapeTask(task, configuration) {
435
+ let shaped = task;
436
+ const historyLength = configuration?.historyLength;
437
+ if (historyLength !== undefined) {
438
+ shaped = {
439
+ ...shaped,
440
+ history: historyLength === 0 ? [] : shaped.history.slice(-historyLength),
441
+ };
442
+ }
443
+ const accepted = configuration?.acceptedOutputModes;
444
+ if (accepted && accepted.length > 0) {
445
+ shaped = {
446
+ ...shaped,
447
+ artifacts: shaped.artifacts.filter((a) => !a.mimeType || accepted.includes(a.mimeType)),
448
+ };
449
+ }
450
+ return shaped;
302
451
  }
303
452
  async handleGetTask(params) {
304
- const { id } = params;
453
+ const { id, historyLength } = (params ?? {});
305
454
  if (!id)
306
455
  throw new A2AError(errors.invalidParams('id is required'));
307
- return this.taskManager.getTask(id);
456
+ assertOptionalNonNegativeInt(historyLength, 'historyLength');
457
+ return this.shapeTask(await this.taskManager.getTask(id), { historyLength });
308
458
  }
309
459
  async handleCancelTask(params) {
310
- const { id } = params;
460
+ const { id } = (params ?? {});
311
461
  if (!id)
312
462
  throw new A2AError(errors.invalidParams('id is required'));
313
463
  return this.taskManager.cancelTask(id);
314
464
  }
315
465
  async handleListTasks(params) {
316
- const filter = (params ?? {});
466
+ const raw = (params ?? {});
467
+ assertOptionalNonNegativeInt(raw.limit, 'limit');
468
+ assertOptionalNonNegativeInt(raw.offset, 'offset');
469
+ const filter = {
470
+ contextId: raw.contextId,
471
+ state: raw.state,
472
+ offset: raw.offset,
473
+ limit: Math.min(raw.limit ?? MAX_LIST_LIMIT, MAX_LIST_LIMIT),
474
+ };
317
475
  const tasks = await this.taskManager.listTasks(filter);
318
476
  return { tasks };
319
477
  }
320
478
  async handleCreatePushNotification(params) {
321
- const { taskId, config } = params;
479
+ const { taskId, config } = (params ?? {});
322
480
  if (!taskId)
323
481
  throw new A2AError(errors.invalidParams('taskId is required'));
324
482
  if (!config?.webhookUrl)
325
483
  throw new A2AError(errors.invalidParams('config.webhookUrl is required'));
326
- if (!this.allowPrivateUrls) {
327
- try {
328
- validateWebhookUrl(config.webhookUrl);
329
- }
330
- catch (e) {
331
- throw new A2AError(errors.invalidParams(e instanceof Error ? e.message : String(e)));
332
- }
333
- }
484
+ this.assertWebhookAllowed(config.webhookUrl);
485
+ await this.taskManager.getTask(taskId);
334
486
  return this.pushNotificationStore.create(taskId, config);
335
487
  }
336
488
  async handleGetPushNotification(params) {
337
- const { taskId, configId } = params;
489
+ const { taskId, configId } = (params ?? {});
338
490
  if (!taskId || !configId) {
339
491
  throw new A2AError(errors.invalidParams('taskId and configId are required'));
340
492
  }
341
493
  return this.pushNotificationStore.get(taskId, configId);
342
494
  }
343
495
  async handleListPushNotifications(params) {
344
- const { taskId } = params;
496
+ const { taskId } = (params ?? {});
345
497
  if (!taskId)
346
498
  throw new A2AError(errors.invalidParams('taskId is required'));
347
499
  return this.pushNotificationStore.list(taskId);
348
500
  }
349
501
  async handleDeletePushNotification(params) {
350
- const { taskId, configId } = params;
502
+ const { taskId, configId } = (params ?? {});
351
503
  if (!taskId || !configId) {
352
504
  throw new A2AError(errors.invalidParams('taskId and configId are required'));
353
505
  }