@a2a-js/sdk 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -163,19 +163,22 @@ class TaskExecutor implements AgentExecutor {
163
163
  requestContext: RequestContext,
164
164
  eventBus: ExecutionEventBus
165
165
  ): Promise<void> {
166
- const { taskId, contextId } = requestContext;
167
-
168
- // 1. Create and publish the initial task object.
169
- const initialTask: Task = {
170
- kind: "task",
171
- id: taskId,
172
- contextId: contextId,
173
- status: {
174
- state: "submitted",
175
- timestamp: new Date().toISOString(),
176
- },
177
- };
178
- eventBus.publish(initialTask);
166
+ const { taskId, contextId, userMessage, task } = requestContext;
167
+
168
+ // 1. Create and publish the initial task object if it doesn't exist.
169
+ if (!task) {
170
+ const initialTask: Task = {
171
+ kind: "task",
172
+ id: taskId,
173
+ contextId: contextId,
174
+ status: {
175
+ state: "submitted",
176
+ timestamp: new Date().toISOString(),
177
+ },
178
+ history: [userMessage]
179
+ };
180
+ eventBus.publish(initialTask);
181
+ }
179
182
 
180
183
  // 2. Create and publish an artifact.
181
184
  const artifactUpdate: TaskArtifactUpdateEvent = {
@@ -281,6 +284,28 @@ const client = await A2AClient.fromCardUrl(
281
284
  await client.sendMessage({ message: { messageId: uuidv4(), role: "user", parts: [{ kind: "text", text: "A message requiring custom headers." }], kind: "message" } });
282
285
  ```
283
286
 
287
+ ### Example: Specifying a Timeout
288
+
289
+ This example creates a `fetch` wrapper that sets a timeout for every outgoing request.
290
+
291
+ ```typescript
292
+ import { A2AClient } from "@a2a-js/sdk/client";
293
+
294
+ // 1. Create a wrapper around the global fetch function.
295
+ const fetchWithTimeout: typeof fetch = async (url, init) => {
296
+ return fetch(url, { ...init, signal: AbortSignal.timeout(5000)});
297
+ };
298
+
299
+ // 2. Provide the custom fetch implementation to the client.
300
+ const client = await A2AClient.fromCardUrl(
301
+ "http://localhost:4000/.well-known/agent-card.json",
302
+ { fetchImpl: fetchWithTimeout }
303
+ );
304
+
305
+ // Now, all requests made by this client instance will have a configured timeout.
306
+ await client.sendMessage({ message: { messageId: uuidv4(), role: "user", parts: [{ kind: "text", text: "A message requiring custom headers." }], kind: "message" } });
307
+ ```
308
+
284
309
  ### Using the Provided `AuthenticationHandler`
285
310
 
286
311
  For advanced authentication scenarios, the SDK includes a higher-order function `createAuthenticatingFetchWithRetry` and an `AuthenticationHandler` interface. This utility automatically adds authorization headers and can retry requests that fail with authentication errors (e.g., 401 Unauthorized).
@@ -354,15 +379,22 @@ class StreamingExecutor implements AgentExecutor {
354
379
  requestContext: RequestContext,
355
380
  eventBus: ExecutionEventBus
356
381
  ): Promise<void> {
357
- const { taskId, contextId } = requestContext;
358
-
359
- // 1. Publish initial 'submitted' state.
360
- eventBus.publish({
361
- kind: "task",
362
- id: taskId,
363
- contextId,
364
- status: { state: "submitted", timestamp: new Date().toISOString() },
365
- });
382
+ const { taskId, contextId, userMessage, task } = requestContext;
383
+
384
+ // 1. Create and publish the initial task object if it doesn't exist.
385
+ if (!task) {
386
+ const initialTask: Task = {
387
+ kind: "task",
388
+ id: taskId,
389
+ contextId: contextId,
390
+ status: {
391
+ state: "submitted",
392
+ timestamp: new Date().toISOString(),
393
+ },
394
+ history: [userMessage]
395
+ };
396
+ eventBus.publish(initialTask);
397
+ }
366
398
 
367
399
  // 2. Publish 'working' state.
368
400
  eventBus.publish({
@@ -535,6 +567,107 @@ class CancellableExecutor implements AgentExecutor {
535
567
  }
536
568
  ```
537
569
 
570
+ ## A2A Push Notifications
571
+
572
+ For very long-running tasks (e.g., lasting minutes, hours, or even days) or when clients cannot or prefer not to maintain persistent connections (like mobile clients or serverless functions), A2A supports asynchronous updates via push notifications. This mechanism allows the A2A Server to actively notify a client-provided webhook when a significant task update occurs.
573
+
574
+ ### Server-Side Configuration
575
+
576
+ To enable push notifications, your agent card must declare support:
577
+
578
+ ```typescript
579
+ const movieAgentCard: AgentCard = {
580
+ // ... other properties
581
+ capabilities: {
582
+ streaming: true,
583
+ pushNotifications: true, // Enable push notifications
584
+ stateTransitionHistory: true,
585
+ },
586
+ // ... rest of agent card
587
+ };
588
+ ```
589
+
590
+ When creating the `DefaultRequestHandler`, you can optionally provide custom push notification components:
591
+
592
+ ```typescript
593
+ import {
594
+ DefaultRequestHandler,
595
+ InMemoryPushNotificationStore,
596
+ DefaultPushNotificationSender
597
+ } from "@a2a-js/sdk/server";
598
+
599
+ // Optional: Custom push notification store and sender
600
+ const pushNotificationStore = new InMemoryPushNotificationStore();
601
+ const pushNotificationSender = new DefaultPushNotificationSender(
602
+ pushNotificationStore,
603
+ {
604
+ timeout: 5000, // 5 second timeout
605
+ tokenHeaderName: 'X-A2A-Notification-Token' // Custom header name
606
+ }
607
+ );
608
+
609
+ const requestHandler = new DefaultRequestHandler(
610
+ movieAgentCard,
611
+ taskStore,
612
+ agentExecutor,
613
+ undefined, // eventBusManager (optional)
614
+ pushNotificationStore, // custom store
615
+ pushNotificationSender, // custom sender
616
+ undefined // extendedAgentCard (optional)
617
+ );
618
+ ```
619
+
620
+ ### Client-Side Usage
621
+
622
+ Configure push notifications when sending messages:
623
+
624
+ ```typescript
625
+ // Configure push notification for a message
626
+ const pushConfig: PushNotificationConfig = {
627
+ id: "my-notification-config", // Optional, defaults to task ID
628
+ url: "https://my-app.com/webhook/task-updates",
629
+ token: "your-auth-token" // Optional authentication token
630
+ };
631
+
632
+ const sendParams: MessageSendParams = {
633
+ message: {
634
+ messageId: uuidv4(),
635
+ role: "user",
636
+ parts: [{ kind: "text", text: "Hello, agent!" }],
637
+ kind: "message",
638
+ },
639
+ configuration: {
640
+ blocking: true,
641
+ acceptedOutputModes: ["text/plain"],
642
+ pushNotificationConfig: pushConfig // Add push notification config
643
+ },
644
+ };
645
+ ```
646
+
647
+ ### Webhook Endpoint Implementation
648
+
649
+ Your webhook endpoint should expect POST requests with the task data:
650
+
651
+ ```typescript
652
+ // Example Express.js webhook endpoint
653
+ app.post('/webhook/task-updates', (req, res) => {
654
+ const task = req.body; // The complete task object
655
+
656
+ // Verify the token if provided
657
+ const token = req.headers['x-a2a-notification-token'];
658
+ if (token !== 'your-auth-token') {
659
+ return res.status(401).json({ error: 'Unauthorized' });
660
+ }
661
+
662
+ console.log(`Task ${task.id} status: ${task.status.state}`);
663
+
664
+ // Process the task update
665
+ // ...
666
+
667
+ res.status(200).json({ received: true });
668
+ });
669
+ ```
670
+
538
671
  ## License
539
672
 
540
673
  This project is licensed under the terms of the [Apache 2.0 License](https://raw.githubusercontent.com/google-a2a/a2a-python/refs/heads/main/LICENSE).
@@ -100,10 +100,8 @@ var JsonRpcTransportHandler = class {
100
100
  } else {
101
101
  throw A2AError.parseError("Invalid request body type.");
102
102
  }
103
- if (rpcRequest.jsonrpc !== "2.0" || !rpcRequest.method || typeof rpcRequest.method !== "string") {
104
- throw A2AError.invalidRequest(
105
- "Invalid JSON-RPC request structure."
106
- );
103
+ if (!this.isRequestValid(rpcRequest)) {
104
+ throw A2AError.invalidRequest("Invalid JSON-RPC Request.");
107
105
  }
108
106
  } catch (error) {
109
107
  const a2aError = error instanceof A2AError ? error : A2AError.parseError(error.message || "Failed to parse JSON request.");
@@ -123,8 +121,8 @@ var JsonRpcTransportHandler = class {
123
121
  result
124
122
  };
125
123
  }
126
- if (!rpcRequest.params) {
127
- throw A2AError.invalidParams(`'params' is required for '${method}'`);
124
+ if (!this.paramsAreValid(rpcRequest.params)) {
125
+ throw A2AError.invalidParams(`Invalid method parameters.`);
128
126
  }
129
127
  if (method === "message/stream" || method === "tasks/resubscribe") {
130
128
  const params = rpcRequest.params;
@@ -199,6 +197,37 @@ var JsonRpcTransportHandler = class {
199
197
  };
200
198
  }
201
199
  }
200
+ // Validates the basic structure of a JSON-RPC request
201
+ isRequestValid(rpcRequest) {
202
+ if (rpcRequest.jsonrpc !== "2.0") {
203
+ return false;
204
+ }
205
+ if ("id" in rpcRequest) {
206
+ const id = rpcRequest.id;
207
+ const isString = typeof id === "string";
208
+ const isInteger = typeof id === "number" && Number.isInteger(id);
209
+ const isNull = id === null;
210
+ if (!isString && !isInteger && !isNull) {
211
+ return false;
212
+ }
213
+ }
214
+ if (!rpcRequest.method || typeof rpcRequest.method !== "string") {
215
+ return false;
216
+ }
217
+ return true;
218
+ }
219
+ // Validates that params is an object with non-empty string keys
220
+ paramsAreValid(params) {
221
+ if (typeof params !== "object" || params === null || Array.isArray(params)) {
222
+ return false;
223
+ }
224
+ for (const key of Object.keys(params)) {
225
+ if (key === "") {
226
+ return false;
227
+ }
228
+ }
229
+ return true;
230
+ }
202
231
  };
203
232
 
204
233
  export {
@@ -243,6 +243,28 @@ var A2AClient = class _A2AClient {
243
243
  params
244
244
  );
245
245
  }
246
+ /**
247
+ * Lists the push notification configurations for a given task.
248
+ * @param params Parameters containing the taskId.
249
+ * @returns A Promise resolving to ListTaskPushNotificationConfigResponse.
250
+ */
251
+ async listTaskPushNotificationConfig(params) {
252
+ return this._postRpcRequest(
253
+ "tasks/pushNotificationConfig/list",
254
+ params
255
+ );
256
+ }
257
+ /**
258
+ * Deletes the push notification configuration for a given task.
259
+ * @param params Parameters containing the taskId and push notification configuration ID.
260
+ * @returns A Promise resolving to DeleteTaskPushNotificationConfigResponse.
261
+ */
262
+ async deleteTaskPushNotificationConfig(params) {
263
+ return this._postRpcRequest(
264
+ "tasks/pushNotificationConfig/delete",
265
+ params
266
+ );
267
+ }
246
268
  /**
247
269
  * Retrieves a task by its ID.
248
270
  * @param params Parameters containing the taskId and optional historyLength.
@@ -259,6 +281,17 @@ var A2AClient = class _A2AClient {
259
281
  async cancelTask(params) {
260
282
  return this._postRpcRequest("tasks/cancel", params);
261
283
  }
284
+ /**
285
+ * @template TExtensionParams The type of parameters for the custom extension method.
286
+ * @template TExtensionResponse The type of response expected from the custom extension method.
287
+ * This should extend JSONRPCResponse. This ensures the extension response is still a valid A2A response.
288
+ * @param method Custom JSON-RPC method defined in the AgentCard's extensions.
289
+ * @param params Extension paramters defined in the AgentCard's extensions.
290
+ * @returns A Promise that resolves to the RPC response.
291
+ */
292
+ async callExtensionMethod(method, params) {
293
+ return this._postRpcRequest(method, params);
294
+ }
262
295
  /**
263
296
  * Resubscribes to a task's event stream using Server-Sent Events (SSE).
264
297
  * This is used if a previous SSE connection for an active task was broken.
@@ -1,4 +1,4 @@
1
- import { ac as AgentCard, w as MessageSendParams, S as SendMessageResponse, B as Message, aw as Task, aO as TaskStatusUpdateEvent, aQ as TaskArtifactUpdateEvent, Z as TaskPushNotificationConfig, b as SetTaskPushNotificationConfigResponse, X as TaskIdParams, c as GetTaskPushNotificationConfigResponse, V as TaskQueryParams, G as GetTaskResponse, C as CancelTaskResponse, i as JSONRPCResponse, J as JSONRPCErrorResponse } from '../types-DNKcmF0f.cjs';
1
+ import { ac as AgentCard, w as MessageSendParams, S as SendMessageResponse, B as Message, aw as Task, aO as TaskStatusUpdateEvent, aQ as TaskArtifactUpdateEvent, Z as TaskPushNotificationConfig, b as SetTaskPushNotificationConfigResponse, X as TaskIdParams, c as GetTaskPushNotificationConfigResponse, a5 as ListTaskPushNotificationConfigParams, j as ListTaskPushNotificationConfigResponse, a7 as DeleteTaskPushNotificationConfigParams, g as DeleteTaskPushNotificationConfigResponse, V as TaskQueryParams, G as GetTaskResponse, C as CancelTaskResponse, i as JSONRPCResponse, J as JSONRPCErrorResponse } from '../types-DNKcmF0f.cjs';
2
2
 
3
3
  type A2AStreamEventData = Message | Task | TaskStatusUpdateEvent | TaskArtifactUpdateEvent;
4
4
  interface A2AClientOptions {
@@ -81,6 +81,18 @@ declare class A2AClient {
81
81
  * @returns A Promise resolving to GetTaskPushNotificationConfigResponse.
82
82
  */
83
83
  getTaskPushNotificationConfig(params: TaskIdParams): Promise<GetTaskPushNotificationConfigResponse>;
84
+ /**
85
+ * Lists the push notification configurations for a given task.
86
+ * @param params Parameters containing the taskId.
87
+ * @returns A Promise resolving to ListTaskPushNotificationConfigResponse.
88
+ */
89
+ listTaskPushNotificationConfig(params: ListTaskPushNotificationConfigParams): Promise<ListTaskPushNotificationConfigResponse>;
90
+ /**
91
+ * Deletes the push notification configuration for a given task.
92
+ * @param params Parameters containing the taskId and push notification configuration ID.
93
+ * @returns A Promise resolving to DeleteTaskPushNotificationConfigResponse.
94
+ */
95
+ deleteTaskPushNotificationConfig(params: DeleteTaskPushNotificationConfigParams): Promise<DeleteTaskPushNotificationConfigResponse>;
84
96
  /**
85
97
  * Retrieves a task by its ID.
86
98
  * @param params Parameters containing the taskId and optional historyLength.
@@ -93,6 +105,15 @@ declare class A2AClient {
93
105
  * @returns A Promise resolving to CancelTaskResponse, which contains the updated Task object or an error.
94
106
  */
95
107
  cancelTask(params: TaskIdParams): Promise<CancelTaskResponse>;
108
+ /**
109
+ * @template TExtensionParams The type of parameters for the custom extension method.
110
+ * @template TExtensionResponse The type of response expected from the custom extension method.
111
+ * This should extend JSONRPCResponse. This ensures the extension response is still a valid A2A response.
112
+ * @param method Custom JSON-RPC method defined in the AgentCard's extensions.
113
+ * @param params Extension paramters defined in the AgentCard's extensions.
114
+ * @returns A Promise that resolves to the RPC response.
115
+ */
116
+ callExtensionMethod<TExtensionParams, TExtensionResponse extends JSONRPCResponse>(method: string, params: TExtensionParams): Promise<TExtensionResponse>;
96
117
  /**
97
118
  * Resubscribes to a task's event stream using Server-Sent Events (SSE).
98
119
  * This is used if a previous SSE connection for an active task was broken.
@@ -1,4 +1,4 @@
1
- import { ac as AgentCard, w as MessageSendParams, S as SendMessageResponse, B as Message, aw as Task, aO as TaskStatusUpdateEvent, aQ as TaskArtifactUpdateEvent, Z as TaskPushNotificationConfig, b as SetTaskPushNotificationConfigResponse, X as TaskIdParams, c as GetTaskPushNotificationConfigResponse, V as TaskQueryParams, G as GetTaskResponse, C as CancelTaskResponse, i as JSONRPCResponse, J as JSONRPCErrorResponse } from '../types-DNKcmF0f.js';
1
+ import { ac as AgentCard, w as MessageSendParams, S as SendMessageResponse, B as Message, aw as Task, aO as TaskStatusUpdateEvent, aQ as TaskArtifactUpdateEvent, Z as TaskPushNotificationConfig, b as SetTaskPushNotificationConfigResponse, X as TaskIdParams, c as GetTaskPushNotificationConfigResponse, a5 as ListTaskPushNotificationConfigParams, j as ListTaskPushNotificationConfigResponse, a7 as DeleteTaskPushNotificationConfigParams, g as DeleteTaskPushNotificationConfigResponse, V as TaskQueryParams, G as GetTaskResponse, C as CancelTaskResponse, i as JSONRPCResponse, J as JSONRPCErrorResponse } from '../types-DNKcmF0f.js';
2
2
 
3
3
  type A2AStreamEventData = Message | Task | TaskStatusUpdateEvent | TaskArtifactUpdateEvent;
4
4
  interface A2AClientOptions {
@@ -81,6 +81,18 @@ declare class A2AClient {
81
81
  * @returns A Promise resolving to GetTaskPushNotificationConfigResponse.
82
82
  */
83
83
  getTaskPushNotificationConfig(params: TaskIdParams): Promise<GetTaskPushNotificationConfigResponse>;
84
+ /**
85
+ * Lists the push notification configurations for a given task.
86
+ * @param params Parameters containing the taskId.
87
+ * @returns A Promise resolving to ListTaskPushNotificationConfigResponse.
88
+ */
89
+ listTaskPushNotificationConfig(params: ListTaskPushNotificationConfigParams): Promise<ListTaskPushNotificationConfigResponse>;
90
+ /**
91
+ * Deletes the push notification configuration for a given task.
92
+ * @param params Parameters containing the taskId and push notification configuration ID.
93
+ * @returns A Promise resolving to DeleteTaskPushNotificationConfigResponse.
94
+ */
95
+ deleteTaskPushNotificationConfig(params: DeleteTaskPushNotificationConfigParams): Promise<DeleteTaskPushNotificationConfigResponse>;
84
96
  /**
85
97
  * Retrieves a task by its ID.
86
98
  * @param params Parameters containing the taskId and optional historyLength.
@@ -93,6 +105,15 @@ declare class A2AClient {
93
105
  * @returns A Promise resolving to CancelTaskResponse, which contains the updated Task object or an error.
94
106
  */
95
107
  cancelTask(params: TaskIdParams): Promise<CancelTaskResponse>;
108
+ /**
109
+ * @template TExtensionParams The type of parameters for the custom extension method.
110
+ * @template TExtensionResponse The type of response expected from the custom extension method.
111
+ * This should extend JSONRPCResponse. This ensures the extension response is still a valid A2A response.
112
+ * @param method Custom JSON-RPC method defined in the AgentCard's extensions.
113
+ * @param params Extension paramters defined in the AgentCard's extensions.
114
+ * @returns A Promise that resolves to the RPC response.
115
+ */
116
+ callExtensionMethod<TExtensionParams, TExtensionResponse extends JSONRPCResponse>(method: string, params: TExtensionParams): Promise<TExtensionResponse>;
96
117
  /**
97
118
  * Resubscribes to a task's event stream using Server-Sent Events (SSE).
98
119
  * This is used if a previous SSE connection for an active task was broken.
@@ -218,6 +218,28 @@ var A2AClient = class _A2AClient {
218
218
  params
219
219
  );
220
220
  }
221
+ /**
222
+ * Lists the push notification configurations for a given task.
223
+ * @param params Parameters containing the taskId.
224
+ * @returns A Promise resolving to ListTaskPushNotificationConfigResponse.
225
+ */
226
+ async listTaskPushNotificationConfig(params) {
227
+ return this._postRpcRequest(
228
+ "tasks/pushNotificationConfig/list",
229
+ params
230
+ );
231
+ }
232
+ /**
233
+ * Deletes the push notification configuration for a given task.
234
+ * @param params Parameters containing the taskId and push notification configuration ID.
235
+ * @returns A Promise resolving to DeleteTaskPushNotificationConfigResponse.
236
+ */
237
+ async deleteTaskPushNotificationConfig(params) {
238
+ return this._postRpcRequest(
239
+ "tasks/pushNotificationConfig/delete",
240
+ params
241
+ );
242
+ }
221
243
  /**
222
244
  * Retrieves a task by its ID.
223
245
  * @param params Parameters containing the taskId and optional historyLength.
@@ -234,6 +256,17 @@ var A2AClient = class _A2AClient {
234
256
  async cancelTask(params) {
235
257
  return this._postRpcRequest("tasks/cancel", params);
236
258
  }
259
+ /**
260
+ * @template TExtensionParams The type of parameters for the custom extension method.
261
+ * @template TExtensionResponse The type of response expected from the custom extension method.
262
+ * This should extend JSONRPCResponse. This ensures the extension response is still a valid A2A response.
263
+ * @param method Custom JSON-RPC method defined in the AgentCard's extensions.
264
+ * @param params Extension paramters defined in the AgentCard's extensions.
265
+ * @returns A Promise that resolves to the RPC response.
266
+ */
267
+ async callExtensionMethod(method, params) {
268
+ return this._postRpcRequest(method, params);
269
+ }
237
270
  /**
238
271
  * Resubscribes to a task's event stream using Server-Sent Events (SSE).
239
272
  * This is used if a previous SSE connection for an active task was broken.
@@ -138,10 +138,8 @@ var JsonRpcTransportHandler = class {
138
138
  } else {
139
139
  throw A2AError.parseError("Invalid request body type.");
140
140
  }
141
- if (rpcRequest.jsonrpc !== "2.0" || !rpcRequest.method || typeof rpcRequest.method !== "string") {
142
- throw A2AError.invalidRequest(
143
- "Invalid JSON-RPC request structure."
144
- );
141
+ if (!this.isRequestValid(rpcRequest)) {
142
+ throw A2AError.invalidRequest("Invalid JSON-RPC Request.");
145
143
  }
146
144
  } catch (error) {
147
145
  const a2aError = error instanceof A2AError ? error : A2AError.parseError(error.message || "Failed to parse JSON request.");
@@ -161,8 +159,8 @@ var JsonRpcTransportHandler = class {
161
159
  result
162
160
  };
163
161
  }
164
- if (!rpcRequest.params) {
165
- throw A2AError.invalidParams(`'params' is required for '${method}'`);
162
+ if (!this.paramsAreValid(rpcRequest.params)) {
163
+ throw A2AError.invalidParams(`Invalid method parameters.`);
166
164
  }
167
165
  if (method === "message/stream" || method === "tasks/resubscribe") {
168
166
  const params = rpcRequest.params;
@@ -237,6 +235,37 @@ var JsonRpcTransportHandler = class {
237
235
  };
238
236
  }
239
237
  }
238
+ // Validates the basic structure of a JSON-RPC request
239
+ isRequestValid(rpcRequest) {
240
+ if (rpcRequest.jsonrpc !== "2.0") {
241
+ return false;
242
+ }
243
+ if ("id" in rpcRequest) {
244
+ const id = rpcRequest.id;
245
+ const isString = typeof id === "string";
246
+ const isInteger = typeof id === "number" && Number.isInteger(id);
247
+ const isNull = id === null;
248
+ if (!isString && !isInteger && !isNull) {
249
+ return false;
250
+ }
251
+ }
252
+ if (!rpcRequest.method || typeof rpcRequest.method !== "string") {
253
+ return false;
254
+ }
255
+ return true;
256
+ }
257
+ // Validates that params is an object with non-empty string keys
258
+ paramsAreValid(params) {
259
+ if (typeof params !== "object" || params === null || Array.isArray(params)) {
260
+ return false;
261
+ }
262
+ for (const key of Object.keys(params)) {
263
+ if (key === "") {
264
+ return false;
265
+ }
266
+ }
267
+ return true;
268
+ }
240
269
  };
241
270
 
242
271
  // src/constants.ts
@@ -256,12 +285,24 @@ var A2AExpressApp = class {
256
285
  * @param app Optional existing Express app.
257
286
  * @param baseUrl The base URL for A2A endpoints (e.g., "/a2a/api").
258
287
  * @param middlewares Optional array of Express middlewares to apply to the A2A routes.
259
- * @param agentCardPath Optional custom path for the agent card endpoint (defaults to /.well-known/agent-card.json).
288
+ * @param agentCardPath Optional custom path for the agent card endpoint (defaults to .well-known/agent-card.json).
260
289
  * @returns The Express app with A2A routes.
261
290
  */
262
291
  setupRoutes(app, baseUrl = "", middlewares, agentCardPath = AGENT_CARD_PATH) {
263
292
  const router = import_express.default.Router();
264
293
  router.use(import_express.default.json(), ...middlewares ?? []);
294
+ router.use((err, req, res, next) => {
295
+ if (err instanceof SyntaxError && "body" in err) {
296
+ const a2aError = A2AError.parseError("Invalid JSON payload.");
297
+ const errorResponse = {
298
+ jsonrpc: "2.0",
299
+ id: null,
300
+ error: a2aError.toJSONRPCError()
301
+ };
302
+ return res.status(400).json(errorResponse);
303
+ }
304
+ next(err);
305
+ });
265
306
  router.get(`/${agentCardPath}`, async (req, res) => {
266
307
  try {
267
308
  const agentCard = await this.requestHandler.getAgentCard();
@@ -11,7 +11,7 @@ declare class A2AExpressApp {
11
11
  * @param app Optional existing Express app.
12
12
  * @param baseUrl The base URL for A2A endpoints (e.g., "/a2a/api").
13
13
  * @param middlewares Optional array of Express middlewares to apply to the A2A routes.
14
- * @param agentCardPath Optional custom path for the agent card endpoint (defaults to /.well-known/agent-card.json).
14
+ * @param agentCardPath Optional custom path for the agent card endpoint (defaults to .well-known/agent-card.json).
15
15
  * @returns The Express app with A2A routes.
16
16
  */
17
17
  setupRoutes(app: Express, baseUrl?: string, middlewares?: Array<RequestHandler | ErrorRequestHandler>, agentCardPath?: string): Express;
@@ -11,7 +11,7 @@ declare class A2AExpressApp {
11
11
  * @param app Optional existing Express app.
12
12
  * @param baseUrl The base URL for A2A endpoints (e.g., "/a2a/api").
13
13
  * @param middlewares Optional array of Express middlewares to apply to the A2A routes.
14
- * @param agentCardPath Optional custom path for the agent card endpoint (defaults to /.well-known/agent-card.json).
14
+ * @param agentCardPath Optional custom path for the agent card endpoint (defaults to .well-known/agent-card.json).
15
15
  * @returns The Express app with A2A routes.
16
16
  */
17
17
  setupRoutes(app: Express, baseUrl?: string, middlewares?: Array<RequestHandler | ErrorRequestHandler>, agentCardPath?: string): Express;
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  A2AError,
6
6
  JsonRpcTransportHandler
7
- } from "../../chunk-JA52GYRU.js";
7
+ } from "../../chunk-SY3G7ITG.js";
8
8
 
9
9
  // src/server/express/a2a_express_app.ts
10
10
  import express from "express";
@@ -21,12 +21,24 @@ var A2AExpressApp = class {
21
21
  * @param app Optional existing Express app.
22
22
  * @param baseUrl The base URL for A2A endpoints (e.g., "/a2a/api").
23
23
  * @param middlewares Optional array of Express middlewares to apply to the A2A routes.
24
- * @param agentCardPath Optional custom path for the agent card endpoint (defaults to /.well-known/agent-card.json).
24
+ * @param agentCardPath Optional custom path for the agent card endpoint (defaults to .well-known/agent-card.json).
25
25
  * @returns The Express app with A2A routes.
26
26
  */
27
27
  setupRoutes(app, baseUrl = "", middlewares, agentCardPath = AGENT_CARD_PATH) {
28
28
  const router = express.Router();
29
29
  router.use(express.json(), ...middlewares ?? []);
30
+ router.use((err, req, res, next) => {
31
+ if (err instanceof SyntaxError && "body" in err) {
32
+ const a2aError = A2AError.parseError("Invalid JSON payload.");
33
+ const errorResponse = {
34
+ jsonrpc: "2.0",
35
+ id: null,
36
+ error: a2aError.toJSONRPCError()
37
+ };
38
+ return res.status(400).json(errorResponse);
39
+ }
40
+ next(err);
41
+ });
30
42
  router.get(`/${agentCardPath}`, async (req, res) => {
31
43
  try {
32
44
  const agentCard = await this.requestHandler.getAgentCard();