salmon-loop 0.3.0 → 0.3.2

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 (93) hide show
  1. package/dist/cli/authorization/non-interactive.js +7 -21
  2. package/dist/cli/commands/chat.js +1 -1
  3. package/dist/cli/commands/parallel.js +46 -41
  4. package/dist/cli/commands/run/assistant-message.js +3 -0
  5. package/dist/cli/commands/run/handler.js +2 -1
  6. package/dist/cli/commands/serve.js +123 -154
  7. package/dist/cli/headless/json-protocol.js +1 -1
  8. package/dist/cli/headless/stream-json-protocol.js +3 -2
  9. package/dist/cli/slash/runtime.js +5 -1
  10. package/dist/cli/ui/components/CommandSuggestionList.js +1 -1
  11. package/dist/core/adapters/fs/node-fs.js +1 -0
  12. package/dist/core/benchmark/patch-artifact.js +1 -1
  13. package/dist/core/context/service.js +36 -10
  14. package/dist/core/extensions/index.js +2 -35
  15. package/dist/core/extensions/redact.js +9 -3
  16. package/dist/core/extensions/schemas.js +2 -51
  17. package/dist/core/facades/cli-authorization-non-interactive.js +1 -1
  18. package/dist/core/facades/cli-serve.js +0 -1
  19. package/dist/core/grizzco/dsl/strategies.js +1 -3
  20. package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +12 -7
  21. package/dist/core/grizzco/engine/transaction/attempt-failure.js +23 -23
  22. package/dist/core/grizzco/engine/transaction/report-mapper.js +3 -0
  23. package/dist/core/grizzco/engine/transaction/transaction-runner.js +14 -0
  24. package/dist/core/grizzco/flows/AutopilotFlow.js +1 -0
  25. package/dist/core/grizzco/flows/SalmonLoopFlow.js +1 -0
  26. package/dist/core/grizzco/steps/apply.js +0 -7
  27. package/dist/core/grizzco/steps/autopilot.js +108 -6
  28. package/dist/core/grizzco/steps/preflight.js +10 -0
  29. package/dist/core/grizzco/steps/tool-runtime.js +1 -0
  30. package/dist/core/interaction/events/bus.js +14 -0
  31. package/dist/core/interaction/orchestration/facade.js +10 -0
  32. package/dist/core/mcp/bridge/index.js +4 -0
  33. package/dist/core/mcp/bridge/prompt-command-provider.js +261 -0
  34. package/dist/core/mcp/bridge/resource-context-provider.js +259 -0
  35. package/dist/core/mcp/bridge/tool-bridge.js +303 -0
  36. package/dist/core/mcp/cache/resource-cache.js +41 -0
  37. package/dist/core/mcp/catalog/discovery.js +51 -0
  38. package/dist/core/mcp/catalog/notification-router.js +28 -0
  39. package/dist/core/mcp/catalog/prompt-catalog.js +4 -0
  40. package/dist/core/mcp/catalog/resource-catalog.js +7 -0
  41. package/dist/core/mcp/catalog/tool-catalog.js +4 -0
  42. package/dist/core/mcp/client/connection-manager.js +239 -0
  43. package/dist/core/mcp/client/lifecycle.js +13 -0
  44. package/dist/core/mcp/client/transport-factory.js +168 -0
  45. package/dist/core/mcp/config/index.js +32 -0
  46. package/dist/core/mcp/config/schema-v2.js +129 -0
  47. package/dist/core/mcp/host/elicitation-provider.js +209 -0
  48. package/dist/core/mcp/host/roots-provider.js +70 -0
  49. package/dist/core/mcp/host/sampling-provider.js +170 -0
  50. package/dist/core/mcp/index.js +4 -0
  51. package/dist/core/mcp/observability/events.js +19 -0
  52. package/dist/core/mcp/policy/approval-policy.js +2 -0
  53. package/dist/core/mcp/policy/classifier.js +172 -0
  54. package/dist/core/mcp/policy/grants.js +356 -0
  55. package/dist/core/mcp/policy/uri-policy.js +60 -0
  56. package/dist/core/mcp/schema/json-schema-to-zod.js +511 -0
  57. package/dist/core/mcp/types.js +2 -0
  58. package/dist/core/protocols/a2a/agent-card.js +36 -11
  59. package/dist/core/protocols/a2a/sdk/executor.js +105 -36
  60. package/dist/core/protocols/a2a/sdk/server.js +1311 -3
  61. package/dist/core/protocols/acp/acp-checkpoint-probe.js +113 -0
  62. package/dist/core/protocols/acp/acp-session-persistence.js +336 -0
  63. package/dist/core/protocols/acp/acp-types.js +17 -0
  64. package/dist/core/protocols/acp/formal-agent.js +271 -603
  65. package/dist/core/protocols/acp/handlers.js +3 -0
  66. package/dist/core/protocols/acp/permission-provider.js +11 -39
  67. package/dist/core/protocols/acp/stdio-server.js +20 -1
  68. package/dist/core/protocols/acp/tool-kind-mapping.js +62 -0
  69. package/dist/core/protocols/shared/flow-mode-mapping.js +0 -8
  70. package/dist/core/public-capabilities/flow-mode-metadata.js +0 -6
  71. package/dist/core/public-capabilities/projections.js +1 -0
  72. package/dist/core/runtime/agent-server-runtime.js +2 -3
  73. package/dist/core/runtime/spawn-command.js +8 -2
  74. package/dist/core/runtime/spawn-interactive.js +26 -0
  75. package/dist/core/session/manager.js +65 -35
  76. package/dist/core/tools/builtin/index.js +6 -1
  77. package/dist/core/tools/builtin/proposal.js +0 -7
  78. package/dist/core/tools/builtin/workspace.js +76 -0
  79. package/dist/core/tools/dispatcher.js +1 -0
  80. package/dist/core/tools/loader.js +92 -46
  81. package/dist/core/verification/runner.js +60 -31
  82. package/dist/core/workspace/capabilities.js +80 -0
  83. package/dist/locales/en.js +17 -3
  84. package/package.json +4 -2
  85. package/dist/core/protocols/a2a/mapper.js +0 -14
  86. package/dist/core/protocols/a2a/sdk/auth-middleware.js +0 -31
  87. package/dist/core/protocols/a2a/task-projection.js +0 -45
  88. package/dist/core/protocols/acp/checkpoint-meta.js +0 -2
  89. package/dist/core/tools/mcp/client.js +0 -309
  90. package/dist/core/tools/mcp/loader.js +0 -110
  91. package/dist/core/tools/mcp/schema.js +0 -54
  92. package/dist/core/tools/mcp/streamable-http.js +0 -101
  93. package/dist/core/tools/mcp/types.js +0 -26
@@ -1,9 +1,1307 @@
1
- import { DefaultRequestHandler, InMemoryTaskStore, } from '@a2a-js/sdk/server';
1
+ import { A2AError, InMemoryPushNotificationStore, DefaultRequestHandler, } from '@a2a-js/sdk/server';
2
2
  import { agentCardHandler, jsonRpcHandler, UserBuilder, } from '@a2a-js/sdk/server/express';
3
3
  import express from 'express';
4
+ export class ProtocolAlignedInMemoryTaskStore {
5
+ tasks = new Map();
6
+ async save(task) {
7
+ this.tasks.set(task.id, task);
8
+ }
9
+ async load(taskId) {
10
+ return this.tasks.get(taskId);
11
+ }
12
+ async listTasks(query) {
13
+ let tasks = [...this.tasks.values()];
14
+ if (query.contextId) {
15
+ tasks = tasks.filter((task) => task.contextId === query.contextId);
16
+ }
17
+ if (query.status) {
18
+ tasks = tasks.filter((task) => task.status.state === query.status);
19
+ }
20
+ if (query.statusTimestampAfter) {
21
+ const threshold = Date.parse(query.statusTimestampAfter);
22
+ tasks = tasks.filter((task) => {
23
+ if (!task.status.timestamp) {
24
+ return false;
25
+ }
26
+ const timestamp = Date.parse(task.status.timestamp);
27
+ return Number.isFinite(timestamp) && timestamp >= threshold;
28
+ });
29
+ }
30
+ tasks = tasks.sort(compareTasksByLastUpdateDesc);
31
+ const totalSize = tasks.length;
32
+ if (query.pageToken) {
33
+ const cursorIndex = tasks.findIndex((task) => task.id === query.pageToken);
34
+ if (cursorIndex < 0) {
35
+ throw A2AError.invalidParams(`Unknown pageToken: ${query.pageToken}`);
36
+ }
37
+ tasks = tasks.slice(cursorIndex + 1);
38
+ }
39
+ const pageTasks = tasks.slice(0, query.pageSize);
40
+ const nextPageToken = tasks.length > pageTasks.length && pageTasks.length > 0
41
+ ? (pageTasks[pageTasks.length - 1]?.id ?? '')
42
+ : '';
43
+ return {
44
+ tasks: pageTasks,
45
+ nextPageToken,
46
+ pageSize: query.pageSize,
47
+ totalSize,
48
+ };
49
+ }
50
+ }
51
+ function compareTasksByLastUpdateDesc(left, right) {
52
+ const leftTimestamp = parseTaskStatusTimestamp(left);
53
+ const rightTimestamp = parseTaskStatusTimestamp(right);
54
+ if (leftTimestamp !== rightTimestamp) {
55
+ return rightTimestamp - leftTimestamp;
56
+ }
57
+ return left.id.localeCompare(right.id);
58
+ }
59
+ function parseTaskStatusTimestamp(task) {
60
+ if (!task.status.timestamp) {
61
+ return Number.NEGATIVE_INFINITY;
62
+ }
63
+ const parsed = Date.parse(task.status.timestamp);
64
+ return Number.isFinite(parsed) ? parsed : Number.NEGATIVE_INFINITY;
65
+ }
66
+ class ProtocolAlignedRequestHandler extends DefaultRequestHandler {
67
+ agentCardRef;
68
+ taskStoreRef;
69
+ pushNotificationStoreRef;
70
+ pushNotificationSenderRef;
71
+ extendedAgentCardProviderRef;
72
+ constructor(agentCardRef, taskStoreRef, agentExecutor, pushNotificationStoreRef, pushNotificationSenderRef, extendedAgentCardProviderRef) {
73
+ super(agentCardRef, taskStoreRef, agentExecutor, undefined, pushNotificationStoreRef, pushNotificationSenderRef, extendedAgentCardProviderRef);
74
+ this.agentCardRef = agentCardRef;
75
+ this.taskStoreRef = taskStoreRef;
76
+ this.pushNotificationStoreRef = pushNotificationStoreRef;
77
+ this.pushNotificationSenderRef = pushNotificationSenderRef;
78
+ this.extendedAgentCardProviderRef = extendedAgentCardProviderRef;
79
+ }
80
+ async sendMessage(params, context) {
81
+ const result = await super.sendMessage(params, context);
82
+ if (result.kind !== 'task') {
83
+ return result;
84
+ }
85
+ return applyHistoryLength(result, params.configuration?.historyLength);
86
+ }
87
+ async *sendMessageStream(params, context) {
88
+ for await (const event of super.sendMessageStream(params, context)) {
89
+ if (event.kind !== 'task') {
90
+ yield event;
91
+ continue;
92
+ }
93
+ yield applyHistoryLength(event, params.configuration?.historyLength);
94
+ }
95
+ }
96
+ async getTask(params, context) {
97
+ const task = await this.taskStoreRef.load(params.id, context);
98
+ if (!task) {
99
+ throw A2AError.taskNotFound(params.id);
100
+ }
101
+ return applyHistoryLength(task, params.historyLength);
102
+ }
103
+ async getAuthenticatedExtendedAgentCard(context) {
104
+ if (!supportsExtendedAgentCard(this.agentCardRef)) {
105
+ throw A2AError.unsupportedOperation('Agent does not support authenticated extended card.');
106
+ }
107
+ if (!context?.user?.isAuthenticated) {
108
+ throw A2AError.invalidRequest('Authentication required for authenticated extended card.');
109
+ }
110
+ if (!this.extendedAgentCardProviderRef) {
111
+ throw A2AError.authenticatedExtendedCardNotConfigured();
112
+ }
113
+ if (typeof this.extendedAgentCardProviderRef === 'function') {
114
+ return this.extendedAgentCardProviderRef(context);
115
+ }
116
+ return this.extendedAgentCardProviderRef;
117
+ }
118
+ }
119
+ const JSON_RPC_METHOD_ALIASES = {
120
+ SendMessage: 'message/send',
121
+ SendStreamingMessage: 'message/stream',
122
+ GetTask: 'tasks/get',
123
+ CancelTask: 'tasks/cancel',
124
+ SubscribeToTask: 'tasks/resubscribe',
125
+ CreateTaskPushNotificationConfig: 'tasks/pushNotificationConfig/set',
126
+ GetTaskPushNotificationConfig: 'tasks/pushNotificationConfig/get',
127
+ ListTaskPushNotificationConfigs: 'tasks/pushNotificationConfig/list',
128
+ DeleteTaskPushNotificationConfig: 'tasks/pushNotificationConfig/delete',
129
+ GetExtendedAgentCard: 'agent/getAuthenticatedExtendedCard',
130
+ };
131
+ const OFFICIAL_A2A_EXTENSIONS_HEADER = 'A2A-Extensions';
132
+ const LEGACY_A2A_EXTENSIONS_HEADER = 'X-A2A-Extensions';
133
+ const LEGACY_JSON_RPC_METHODS = new Set(Object.values(JSON_RPC_METHOD_ALIASES));
134
+ const SUPPORTED_A2A_VERSIONS = new Set(['0.3', '1.0']);
135
+ const DEFAULT_LIST_TASKS_PAGE_SIZE = 50;
136
+ const MAX_LIST_TASKS_PAGE_SIZE = 100;
137
+ const RFC3339_TIMESTAMP_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,9}))?(?:Z|([+-])(\d{2}):(\d{2}))$/;
138
+ const TASK_STATE_FILTERS = {
139
+ TASK_STATE_UNSPECIFIED: undefined,
140
+ TASK_STATE_SUBMITTED: 'submitted',
141
+ TASK_STATE_WORKING: 'working',
142
+ TASK_STATE_COMPLETED: 'completed',
143
+ TASK_STATE_FAILED: 'failed',
144
+ TASK_STATE_CANCELED: 'canceled',
145
+ TASK_STATE_INPUT_REQUIRED: 'input-required',
146
+ TASK_STATE_REJECTED: 'rejected',
147
+ TASK_STATE_AUTH_REQUIRED: 'auth-required',
148
+ };
149
+ const TERMINAL_TASK_STATES = new Set([
150
+ 'completed',
151
+ 'failed',
152
+ 'canceled',
153
+ 'rejected',
154
+ ]);
155
+ function resolveRequiredJsonRpcTenant(agentCard) {
156
+ const supportedInterfaces = agentCard.supportedInterfaces;
157
+ return supportedInterfaces?.find((entry) => entry.protocolBinding === 'JSONRPC')?.tenant;
158
+ }
159
+ function applyHistoryLength(task, historyLength) {
160
+ const normalized = { ...task };
161
+ if (historyLength === undefined) {
162
+ return normalized;
163
+ }
164
+ if (historyLength === 0) {
165
+ delete normalized.history;
166
+ return normalized;
167
+ }
168
+ if (historyLength > 0 && normalized.history) {
169
+ normalized.history = normalized.history.slice(-historyLength);
170
+ }
171
+ return normalized;
172
+ }
173
+ function supportsExtendedAgentCard(agentCard) {
174
+ const capabilities = agentCard.capabilities;
175
+ return (capabilities?.extendedAgentCard === true || agentCard.supportsAuthenticatedExtendedCard === true);
176
+ }
177
+ function supportsPushNotifications(agentCard) {
178
+ return agentCard.capabilities.pushNotifications === true;
179
+ }
180
+ function supportsExtendedAgentCardForA2AVersion(agentCard, version) {
181
+ const capabilities = agentCard.capabilities;
182
+ if (version === '1.0') {
183
+ return capabilities?.extendedAgentCard === true;
184
+ }
185
+ return supportsExtendedAgentCard(agentCard);
186
+ }
187
+ function readRequestedA2AVersion(req) {
188
+ const headerVersion = req.header('A2A-Version');
189
+ if (headerVersion && headerVersion.trim().length > 0) {
190
+ return headerVersion.trim();
191
+ }
192
+ const queryVersion = req.query['A2A-Version'] ?? req.query['a2a-version'];
193
+ if (typeof queryVersion === 'string' && queryVersion.trim().length > 0) {
194
+ return queryVersion.trim();
195
+ }
196
+ return '0.3';
197
+ }
198
+ function readRequestedExtensions(req) {
199
+ const rawHeader = req.header(OFFICIAL_A2A_EXTENSIONS_HEADER) ?? req.header(LEGACY_A2A_EXTENSIONS_HEADER);
200
+ if (!rawHeader) {
201
+ return new Set();
202
+ }
203
+ return new Set(rawHeader
204
+ .split(',')
205
+ .map((entry) => entry.trim())
206
+ .filter((entry) => entry.length > 0));
207
+ }
208
+ function createJsonRpcErrorResponse(requestId, error) {
209
+ return {
210
+ jsonrpc: '2.0',
211
+ id: typeof requestId === 'string' ||
212
+ (typeof requestId === 'number' && Number.isInteger(requestId))
213
+ ? requestId
214
+ : null,
215
+ error,
216
+ };
217
+ }
218
+ function normalizeHeaderValue(value) {
219
+ if (Array.isArray(value)) {
220
+ return value.join(',');
221
+ }
222
+ return String(value);
223
+ }
224
+ function stripTenantFromParams(params) {
225
+ if (typeof params !== 'object' || params === null || Array.isArray(params)) {
226
+ return params;
227
+ }
228
+ const { tenant: _tenant, ...rest } = params;
229
+ return Object.keys(rest).length > 0 ? rest : undefined;
230
+ }
231
+ function normalizeSendMessageParamsForV1(params) {
232
+ if (typeof params !== 'object' || params === null || Array.isArray(params)) {
233
+ return params;
234
+ }
235
+ const rawParams = params;
236
+ const rawConfiguration = rawParams.configuration;
237
+ if (typeof rawConfiguration !== 'object' ||
238
+ rawConfiguration === null ||
239
+ Array.isArray(rawConfiguration)) {
240
+ return params;
241
+ }
242
+ const configuration = rawConfiguration;
243
+ const hasTaskPushNotificationConfig = 'taskPushNotificationConfig' in configuration;
244
+ const hasReturnImmediately = 'returnImmediately' in configuration;
245
+ if (!hasTaskPushNotificationConfig && !hasReturnImmediately) {
246
+ return params;
247
+ }
248
+ const normalizedPushNotificationConfig = (() => {
249
+ if (configuration.pushNotificationConfig !== undefined) {
250
+ return configuration.pushNotificationConfig;
251
+ }
252
+ const taskPushNotificationConfig = configuration.taskPushNotificationConfig;
253
+ if (typeof taskPushNotificationConfig !== 'object' ||
254
+ taskPushNotificationConfig === null ||
255
+ Array.isArray(taskPushNotificationConfig)) {
256
+ return taskPushNotificationConfig;
257
+ }
258
+ const { taskId: _taskId, tenant: _tenant, ...rest } = taskPushNotificationConfig;
259
+ const rawAuthentication = rest.authentication;
260
+ const normalizedAuthentication = typeof rawAuthentication === 'object' &&
261
+ rawAuthentication !== null &&
262
+ !Array.isArray(rawAuthentication) &&
263
+ 'scheme' in rawAuthentication &&
264
+ !('schemes' in rawAuthentication)
265
+ ? {
266
+ schemes: [rawAuthentication.scheme],
267
+ credentials: (() => {
268
+ const credentials = rawAuthentication.credentials;
269
+ return typeof credentials === 'string' ? credentials : '';
270
+ })(),
271
+ }
272
+ : rawAuthentication;
273
+ return {
274
+ ...rest,
275
+ authentication: normalizedAuthentication,
276
+ };
277
+ })();
278
+ const { taskPushNotificationConfig: _taskPushNotificationConfig, returnImmediately: rawReturnImmediately, ...restConfiguration } = configuration;
279
+ const normalizedBlocking = typeof rawReturnImmediately === 'boolean' && restConfiguration.blocking === undefined
280
+ ? !rawReturnImmediately
281
+ : restConfiguration.blocking;
282
+ return {
283
+ ...rawParams,
284
+ configuration: {
285
+ ...restConfiguration,
286
+ ...(normalizedBlocking !== undefined ? { blocking: normalizedBlocking } : {}),
287
+ pushNotificationConfig: normalizedPushNotificationConfig,
288
+ },
289
+ };
290
+ }
291
+ function hasListableTaskStore(store) {
292
+ return typeof store.listTasks === 'function';
293
+ }
294
+ function isRfc3339Timestamp(value) {
295
+ const match = value.match(RFC3339_TIMESTAMP_PATTERN);
296
+ if (!match) {
297
+ return false;
298
+ }
299
+ const [, yearText, monthText, dayText, hourText, minuteText, secondText, _fraction, _sign, offsetHourText, offsetMinuteText,] = match;
300
+ const year = Number(yearText);
301
+ const month = Number(monthText);
302
+ const day = Number(dayText);
303
+ const hour = Number(hourText);
304
+ const minute = Number(minuteText);
305
+ const second = Number(secondText);
306
+ if (!Number.isInteger(year) ||
307
+ month < 1 ||
308
+ month > 12 ||
309
+ hour > 23 ||
310
+ minute > 59 ||
311
+ second > 59) {
312
+ return false;
313
+ }
314
+ const maxDay = new Date(Date.UTC(year, month, 0)).getUTCDate();
315
+ if (day < 1 || day > maxDay) {
316
+ return false;
317
+ }
318
+ if (offsetHourText !== undefined && offsetMinuteText !== undefined) {
319
+ const offsetHour = Number(offsetHourText);
320
+ const offsetMinute = Number(offsetMinuteText);
321
+ if (offsetHour > 23 || offsetMinute > 59) {
322
+ return false;
323
+ }
324
+ }
325
+ return Number.isFinite(Date.parse(value));
326
+ }
327
+ function parseListTasksParams(rawParams) {
328
+ const params = (rawParams ?? {});
329
+ if (typeof params !== 'object' || params === null || Array.isArray(params)) {
330
+ throw A2AError.invalidParams('ListTasks params must be an object.');
331
+ }
332
+ if (params.tenant !== undefined && typeof params.tenant !== 'string') {
333
+ throw A2AError.invalidParams('ListTasks tenant must be a string.');
334
+ }
335
+ if (params.contextId !== undefined && typeof params.contextId !== 'string') {
336
+ throw A2AError.invalidParams('ListTasks contextId must be a string.');
337
+ }
338
+ if (params.pageToken !== undefined && typeof params.pageToken !== 'string') {
339
+ throw A2AError.invalidParams('ListTasks pageToken must be a string.');
340
+ }
341
+ if (params.pageSize !== undefined && !Number.isInteger(params.pageSize)) {
342
+ throw A2AError.invalidParams('ListTasks pageSize must be an integer.');
343
+ }
344
+ if (params.pageSize !== undefined && (params.pageSize < 1 || params.pageSize > 100)) {
345
+ throw A2AError.invalidParams(`ListTasks pageSize must be between 1 and ${MAX_LIST_TASKS_PAGE_SIZE}.`);
346
+ }
347
+ if (params.historyLength !== undefined && !Number.isInteger(params.historyLength)) {
348
+ throw A2AError.invalidParams('ListTasks historyLength must be an integer.');
349
+ }
350
+ if (params.historyLength !== undefined && params.historyLength < 0) {
351
+ throw A2AError.invalidParams('ListTasks historyLength must be greater than or equal to 0.');
352
+ }
353
+ if (params.statusTimestampAfter !== undefined) {
354
+ if (typeof params.statusTimestampAfter !== 'string') {
355
+ throw A2AError.invalidParams('ListTasks statusTimestampAfter must be an ISO 8601 string.');
356
+ }
357
+ if (!isRfc3339Timestamp(params.statusTimestampAfter)) {
358
+ throw A2AError.invalidParams('ListTasks statusTimestampAfter must be a valid timestamp.');
359
+ }
360
+ }
361
+ if (params.includeArtifacts !== undefined && typeof params.includeArtifacts !== 'boolean') {
362
+ throw A2AError.invalidParams('ListTasks includeArtifacts must be a boolean.');
363
+ }
364
+ let status;
365
+ if (params.status !== undefined) {
366
+ if (typeof params.status !== 'string') {
367
+ throw A2AError.invalidParams('ListTasks status must be a string enum value.');
368
+ }
369
+ if (!(params.status in TASK_STATE_FILTERS)) {
370
+ throw A2AError.invalidParams(`Unsupported ListTasks status: ${params.status}`);
371
+ }
372
+ status = TASK_STATE_FILTERS[params.status];
373
+ }
374
+ return {
375
+ contextId: params.contextId,
376
+ status,
377
+ pageSize: params.pageSize ?? DEFAULT_LIST_TASKS_PAGE_SIZE,
378
+ pageToken: params.pageToken,
379
+ historyLength: params.historyLength,
380
+ statusTimestampAfter: params.statusTimestampAfter,
381
+ includeArtifacts: params.includeArtifacts ?? false,
382
+ };
383
+ }
384
+ function normalizeListedTask(task, options) {
385
+ const normalized = structuredClone(task);
386
+ const withHistory = applyHistoryLength(normalized, options.historyLength);
387
+ if (!options.includeArtifacts) {
388
+ delete withHistory.artifacts;
389
+ }
390
+ return withHistory;
391
+ }
392
+ const normalizeA2AExtensionHeadersByVersion = (req, res, next) => {
393
+ const requestedVersion = readRequestedA2AVersion(req);
394
+ const officialExtensions = req.header(OFFICIAL_A2A_EXTENSIONS_HEADER);
395
+ if (officialExtensions && !req.header(LEGACY_A2A_EXTENSIONS_HEADER)) {
396
+ req.headers['x-a2a-extensions'] = officialExtensions;
397
+ }
398
+ const originalSetHeader = res.setHeader.bind(res);
399
+ res.setHeader = ((name, value) => {
400
+ if (requestedVersion === '1.0' &&
401
+ typeof name === 'string' &&
402
+ name.toLowerCase() === LEGACY_A2A_EXTENSIONS_HEADER.toLowerCase()) {
403
+ return originalSetHeader(OFFICIAL_A2A_EXTENSIONS_HEADER, normalizeHeaderValue(value));
404
+ }
405
+ return originalSetHeader(name, value);
406
+ });
407
+ next();
408
+ };
409
+ function isObjectRecord(value) {
410
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
411
+ }
412
+ function looksLikeTask(result) {
413
+ return typeof result.id === 'string' && 'status' in result;
414
+ }
415
+ function looksLikeMessage(result) {
416
+ return typeof result.messageId === 'string' && 'role' in result;
417
+ }
418
+ function looksLikeStatusUpdate(result) {
419
+ return (typeof result.taskId === 'string' && typeof result.contextId === 'string' && 'status' in result);
420
+ }
421
+ function looksLikeArtifactUpdate(result) {
422
+ return (typeof result.taskId === 'string' &&
423
+ typeof result.contextId === 'string' &&
424
+ 'artifact' in result);
425
+ }
426
+ function normalizeTaskStateForV1(state) {
427
+ switch (state) {
428
+ case 'unknown':
429
+ return 'TASK_STATE_UNSPECIFIED';
430
+ case 'submitted':
431
+ return 'TASK_STATE_SUBMITTED';
432
+ case 'working':
433
+ return 'TASK_STATE_WORKING';
434
+ case 'completed':
435
+ return 'TASK_STATE_COMPLETED';
436
+ case 'failed':
437
+ return 'TASK_STATE_FAILED';
438
+ case 'canceled':
439
+ case 'cancelled':
440
+ return 'TASK_STATE_CANCELED';
441
+ case 'input-required':
442
+ return 'TASK_STATE_INPUT_REQUIRED';
443
+ case 'rejected':
444
+ return 'TASK_STATE_REJECTED';
445
+ case 'auth-required':
446
+ return 'TASK_STATE_AUTH_REQUIRED';
447
+ default:
448
+ return state;
449
+ }
450
+ }
451
+ function normalizeRoleForV1(role) {
452
+ switch (role) {
453
+ case 'user':
454
+ return 'ROLE_USER';
455
+ case 'agent':
456
+ return 'ROLE_AGENT';
457
+ default:
458
+ return role;
459
+ }
460
+ }
461
+ function normalizePartForV1(part) {
462
+ if (!isObjectRecord(part)) {
463
+ return part;
464
+ }
465
+ if ('kind' in part) {
466
+ if (part.kind === 'text') {
467
+ const normalized = {};
468
+ if (part.text !== undefined) {
469
+ normalized.text = part.text;
470
+ }
471
+ return normalized;
472
+ }
473
+ if (part.kind === 'data') {
474
+ const normalized = {};
475
+ if (part.data !== undefined) {
476
+ normalized.data = part.data;
477
+ }
478
+ return normalized;
479
+ }
480
+ if (part.kind === 'file' && isObjectRecord(part.file)) {
481
+ const normalized = {};
482
+ if (part.file.uri !== undefined) {
483
+ normalized.url = part.file.uri;
484
+ }
485
+ if (part.file.bytes !== undefined) {
486
+ normalized.raw = Buffer.isBuffer(part.file.bytes)
487
+ ? part.file.bytes.toString('base64')
488
+ : part.file.bytes;
489
+ }
490
+ if (part.file.name !== undefined) {
491
+ normalized.filename = part.file.name;
492
+ }
493
+ if (part.file.mimeType !== undefined) {
494
+ normalized.mediaType = part.file.mimeType;
495
+ }
496
+ return normalized;
497
+ }
498
+ }
499
+ return part;
500
+ }
501
+ function normalizeMessageForV1(message) {
502
+ if (!isObjectRecord(message)) {
503
+ return message;
504
+ }
505
+ const normalized = {};
506
+ if (message.messageId !== undefined) {
507
+ normalized.messageId = message.messageId;
508
+ }
509
+ if (message.contextId !== undefined) {
510
+ normalized.contextId = message.contextId;
511
+ }
512
+ if (message.taskId !== undefined) {
513
+ normalized.taskId = message.taskId;
514
+ }
515
+ if (message.role !== undefined) {
516
+ normalized.role = normalizeRoleForV1(message.role);
517
+ }
518
+ const rawParts = Array.isArray(message.parts)
519
+ ? message.parts
520
+ : Array.isArray(message.content)
521
+ ? message.content
522
+ : undefined;
523
+ if (rawParts) {
524
+ normalized.parts = rawParts.map((part) => normalizePartForV1(part));
525
+ }
526
+ if (message.metadata !== undefined) {
527
+ normalized.metadata = message.metadata;
528
+ }
529
+ if (message.extensions !== undefined) {
530
+ normalized.extensions = message.extensions;
531
+ }
532
+ if (message.referenceTaskIds !== undefined) {
533
+ normalized.referenceTaskIds = message.referenceTaskIds;
534
+ }
535
+ return normalized;
536
+ }
537
+ function normalizeArtifactForV1(artifact) {
538
+ if (!isObjectRecord(artifact)) {
539
+ return artifact;
540
+ }
541
+ const normalized = {};
542
+ if (artifact.artifactId !== undefined) {
543
+ normalized.artifactId = artifact.artifactId;
544
+ }
545
+ if (artifact.name !== undefined) {
546
+ normalized.name = artifact.name;
547
+ }
548
+ if (artifact.description !== undefined) {
549
+ normalized.description = artifact.description;
550
+ }
551
+ if (Array.isArray(artifact.parts)) {
552
+ normalized.parts = artifact.parts.map((part) => normalizePartForV1(part));
553
+ }
554
+ if (artifact.metadata !== undefined) {
555
+ normalized.metadata = artifact.metadata;
556
+ }
557
+ if (artifact.extensions !== undefined) {
558
+ normalized.extensions = artifact.extensions;
559
+ }
560
+ return normalized;
561
+ }
562
+ function normalizeTaskStatusForV1(status) {
563
+ if (!isObjectRecord(status)) {
564
+ return status;
565
+ }
566
+ const normalized = {};
567
+ if (status.state !== undefined) {
568
+ normalized.state = normalizeTaskStateForV1(status.state);
569
+ }
570
+ const statusMessage = status.message ?? status.update;
571
+ if (statusMessage !== undefined) {
572
+ normalized.message = normalizeMessageForV1(statusMessage);
573
+ }
574
+ if (status.timestamp !== undefined) {
575
+ normalized.timestamp = status.timestamp;
576
+ }
577
+ return normalized;
578
+ }
579
+ function normalizeTaskForV1(task) {
580
+ if (!isObjectRecord(task)) {
581
+ return task;
582
+ }
583
+ const normalized = {};
584
+ if (task.id !== undefined) {
585
+ normalized.id = task.id;
586
+ }
587
+ if (task.contextId !== undefined) {
588
+ normalized.contextId = task.contextId;
589
+ }
590
+ if (task.status !== undefined) {
591
+ normalized.status = normalizeTaskStatusForV1(task.status);
592
+ }
593
+ if (Array.isArray(task.artifacts)) {
594
+ normalized.artifacts = task.artifacts.map((artifact) => normalizeArtifactForV1(artifact));
595
+ }
596
+ if (Array.isArray(task.history)) {
597
+ normalized.history = task.history.map((message) => normalizeMessageForV1(message));
598
+ }
599
+ if (task.metadata !== undefined) {
600
+ normalized.metadata = task.metadata;
601
+ }
602
+ return normalized;
603
+ }
604
+ function normalizeListTasksResultForV1(result) {
605
+ if (!isObjectRecord(result)) {
606
+ return result;
607
+ }
608
+ const normalized = { ...result };
609
+ if (Array.isArray(result.tasks)) {
610
+ normalized.tasks = result.tasks.map((task) => normalizeTaskForV1(task));
611
+ }
612
+ return normalized;
613
+ }
614
+ function normalizeAgentCardForV1(card) {
615
+ if (!isObjectRecord(card)) {
616
+ return card;
617
+ }
618
+ const normalized = { ...card };
619
+ const supportedInterfaces = Array.isArray(card.supportedInterfaces)
620
+ ? card.supportedInterfaces
621
+ : undefined;
622
+ if (supportedInterfaces === undefined) {
623
+ const synthesizedInterfaces = [];
624
+ if (typeof card.url === 'string') {
625
+ synthesizedInterfaces.push({
626
+ url: card.url,
627
+ protocolBinding: typeof card.preferredTransport === 'string' ? card.preferredTransport : 'JSONRPC',
628
+ protocolVersion: '1.0',
629
+ });
630
+ }
631
+ if (Array.isArray(card.additionalInterfaces)) {
632
+ for (const entry of card.additionalInterfaces) {
633
+ if (!isObjectRecord(entry) || typeof entry.url !== 'string') {
634
+ continue;
635
+ }
636
+ synthesizedInterfaces.push({
637
+ url: entry.url,
638
+ protocolBinding: typeof entry.transport === 'string' ? entry.transport : 'JSONRPC',
639
+ protocolVersion: '1.0',
640
+ });
641
+ }
642
+ }
643
+ if (synthesizedInterfaces.length > 0) {
644
+ normalized.supportedInterfaces = synthesizedInterfaces;
645
+ }
646
+ }
647
+ if (normalized.securityRequirements === undefined && Array.isArray(card.security)) {
648
+ normalized.securityRequirements = card.security;
649
+ }
650
+ delete normalized.url;
651
+ delete normalized.protocolVersion;
652
+ delete normalized.preferredTransport;
653
+ delete normalized.additionalInterfaces;
654
+ delete normalized.security;
655
+ delete normalized.supportsAuthenticatedExtendedCard;
656
+ if (isObjectRecord(normalized.capabilities)) {
657
+ const capabilities = normalized.capabilities;
658
+ const nextCapabilities = { ...capabilities };
659
+ if (card.supportsAuthenticatedExtendedCard === true &&
660
+ nextCapabilities.extendedAgentCard !== true) {
661
+ nextCapabilities.extendedAgentCard = true;
662
+ }
663
+ if ('stateTransitionHistory' in nextCapabilities) {
664
+ const { stateTransitionHistory: _stateTransitionHistory, ...restCapabilities } = nextCapabilities;
665
+ normalized.capabilities = restCapabilities;
666
+ }
667
+ else {
668
+ normalized.capabilities = nextCapabilities;
669
+ }
670
+ }
671
+ else if (card.supportsAuthenticatedExtendedCard === true) {
672
+ normalized.capabilities = { extendedAgentCard: true };
673
+ }
674
+ if (Array.isArray(card.skills)) {
675
+ normalized.skills = card.skills.map((skill) => {
676
+ if (!isObjectRecord(skill)) {
677
+ return skill;
678
+ }
679
+ const normalizedSkill = { ...skill };
680
+ if (normalizedSkill.securityRequirements === undefined && Array.isArray(skill.security)) {
681
+ normalizedSkill.securityRequirements = skill.security;
682
+ }
683
+ delete normalizedSkill.security;
684
+ return normalizedSkill;
685
+ });
686
+ }
687
+ return normalized;
688
+ }
689
+ function normalizeStatusUpdateForV1(result) {
690
+ const normalized = {};
691
+ if (result.taskId !== undefined) {
692
+ normalized.taskId = result.taskId;
693
+ }
694
+ if (result.contextId !== undefined) {
695
+ normalized.contextId = result.contextId;
696
+ }
697
+ if (result.status !== undefined) {
698
+ normalized.status = normalizeTaskStatusForV1(result.status);
699
+ }
700
+ if (result.metadata !== undefined) {
701
+ normalized.metadata = result.metadata;
702
+ }
703
+ return normalized;
704
+ }
705
+ function normalizeArtifactUpdateForV1(result) {
706
+ const normalized = {};
707
+ if (result.taskId !== undefined) {
708
+ normalized.taskId = result.taskId;
709
+ }
710
+ if (result.contextId !== undefined) {
711
+ normalized.contextId = result.contextId;
712
+ }
713
+ if (result.artifact !== undefined) {
714
+ normalized.artifact = normalizeArtifactForV1(result.artifact);
715
+ }
716
+ if (result.append !== undefined) {
717
+ normalized.append = result.append;
718
+ }
719
+ if (result.lastChunk !== undefined) {
720
+ normalized.lastChunk = result.lastChunk;
721
+ }
722
+ if (result.metadata !== undefined) {
723
+ normalized.metadata = result.metadata;
724
+ }
725
+ return normalized;
726
+ }
727
+ function stripLegacyStatusUpdateFields(result) {
728
+ if (!('final' in result)) {
729
+ return result;
730
+ }
731
+ const { final: _final, ...rest } = result;
732
+ return rest;
733
+ }
734
+ function wrapSendMessageResult(result) {
735
+ if (!isObjectRecord(result)) {
736
+ return result;
737
+ }
738
+ if ('task' in result || 'message' in result) {
739
+ return {
740
+ ...(result.task !== undefined ? { task: normalizeTaskForV1(result.task) } : {}),
741
+ ...(result.message !== undefined ? { message: normalizeMessageForV1(result.message) } : {}),
742
+ };
743
+ }
744
+ if (looksLikeTask(result)) {
745
+ return { task: normalizeTaskForV1(result) };
746
+ }
747
+ if (looksLikeMessage(result)) {
748
+ return { message: normalizeMessageForV1(result) };
749
+ }
750
+ return result;
751
+ }
752
+ function wrapStreamResponseResult(result) {
753
+ if (!isObjectRecord(result)) {
754
+ return result;
755
+ }
756
+ if ('task' in result ||
757
+ 'message' in result ||
758
+ 'statusUpdate' in result ||
759
+ 'artifactUpdate' in result) {
760
+ if (isObjectRecord(result.statusUpdate)) {
761
+ return {
762
+ ...result,
763
+ statusUpdate: normalizeStatusUpdateForV1(stripLegacyStatusUpdateFields(result.statusUpdate)),
764
+ };
765
+ }
766
+ if (isObjectRecord(result.task)) {
767
+ return { ...result, task: normalizeTaskForV1(result.task) };
768
+ }
769
+ if (isObjectRecord(result.message)) {
770
+ return { ...result, message: normalizeMessageForV1(result.message) };
771
+ }
772
+ if (isObjectRecord(result.artifactUpdate)) {
773
+ return { ...result, artifactUpdate: normalizeArtifactUpdateForV1(result.artifactUpdate) };
774
+ }
775
+ return result;
776
+ }
777
+ if (looksLikeTask(result)) {
778
+ return { task: normalizeTaskForV1(result) };
779
+ }
780
+ if (looksLikeMessage(result)) {
781
+ return { message: normalizeMessageForV1(result) };
782
+ }
783
+ if (looksLikeStatusUpdate(result)) {
784
+ return { statusUpdate: normalizeStatusUpdateForV1(stripLegacyStatusUpdateFields(result)) };
785
+ }
786
+ if (looksLikeArtifactUpdate(result)) {
787
+ return { artifactUpdate: normalizeArtifactUpdateForV1(result) };
788
+ }
789
+ return result;
790
+ }
791
+ function normalizeJsonRpcResultByMethod(method, result) {
792
+ if (method === 'message/send') {
793
+ return wrapSendMessageResult(result);
794
+ }
795
+ if (method === 'tasks/get' || method === 'tasks/cancel') {
796
+ return normalizeTaskForV1(result);
797
+ }
798
+ if (method === 'ListTasks') {
799
+ return normalizeListTasksResultForV1(result);
800
+ }
801
+ if (method === 'agent/getAuthenticatedExtendedCard') {
802
+ return normalizeAgentCardForV1(result);
803
+ }
804
+ if (method === 'message/stream' || method === 'tasks/resubscribe') {
805
+ return wrapStreamResponseResult(result);
806
+ }
807
+ return result;
808
+ }
809
+ function normalizeJsonRpcPayloadByMethod(method, payload) {
810
+ if (!isObjectRecord(payload) || !('result' in payload)) {
811
+ return payload;
812
+ }
813
+ return {
814
+ ...payload,
815
+ result: normalizeJsonRpcResultByMethod(method, payload.result),
816
+ };
817
+ }
818
+ function normalizeSseJsonRpcChunkByMethod(chunk, method) {
819
+ return chunk
820
+ .split('\n')
821
+ .map((line) => {
822
+ if (!line.startsWith('data: ')) {
823
+ return line;
824
+ }
825
+ try {
826
+ const payload = JSON.parse(line.slice('data: '.length));
827
+ return `data: ${JSON.stringify(normalizeJsonRpcPayloadByMethod(method, payload))}`;
828
+ }
829
+ catch {
830
+ return line;
831
+ }
832
+ })
833
+ .join('\n');
834
+ }
835
+ const normalizeJsonRpcResponsesByVersion = (req, res, next) => {
836
+ if (readRequestedA2AVersion(req) !== '1.0') {
837
+ next();
838
+ return;
839
+ }
840
+ const method = typeof req.body?.method === 'string' ? req.body.method : '';
841
+ const originalSetHeader = res.setHeader.bind(res);
842
+ res.setHeader = ((name, value) => {
843
+ if (typeof name === 'string' &&
844
+ name.toLowerCase() === 'content-type' &&
845
+ typeof value === 'string' &&
846
+ /^application\/json\b/i.test(value)) {
847
+ return originalSetHeader('Content-Type', value.replace(/^application\/json/i, 'application/a2a+json'));
848
+ }
849
+ return originalSetHeader(name, value);
850
+ });
851
+ const originalJson = res.json.bind(res);
852
+ res.json = ((body) => originalJson(normalizeJsonRpcPayloadByMethod(method, body)));
853
+ const originalWrite = res.write.bind(res);
854
+ res.write = ((...writeArgs) => {
855
+ const [chunk, second, third] = writeArgs;
856
+ if (typeof chunk === 'string') {
857
+ return originalWrite(normalizeSseJsonRpcChunkByMethod(chunk, method), second, third);
858
+ }
859
+ if (Buffer.isBuffer(chunk)) {
860
+ return originalWrite(Buffer.from(normalizeSseJsonRpcChunkByMethod(chunk.toString('utf8'), method)), second, third);
861
+ }
862
+ return originalWrite(chunk, second, third);
863
+ });
864
+ next();
865
+ };
866
+ const normalizeJsonRpcRequestByVersion = (req, res, next) => {
867
+ if (req.method === 'POST' &&
868
+ typeof req.body === 'object' &&
869
+ req.body !== null &&
870
+ typeof req.body.method === 'string') {
871
+ const requestedVersion = readRequestedA2AVersion(req);
872
+ if (!SUPPORTED_A2A_VERSIONS.has(requestedVersion)) {
873
+ res.status(200).json(createJsonRpcErrorResponse(req.body.id, {
874
+ code: -32009,
875
+ message: `A2A protocol version ${requestedVersion} is not supported.`,
876
+ }));
877
+ return;
878
+ }
879
+ if (requestedVersion === '1.0') {
880
+ if (LEGACY_JSON_RPC_METHODS.has(req.body.method)) {
881
+ res.status(200).json(createJsonRpcErrorResponse(req.body.id, {
882
+ code: -32601,
883
+ message: `Method not found: ${req.body.method}`,
884
+ }));
885
+ return;
886
+ }
887
+ req.body.method = JSON_RPC_METHOD_ALIASES[req.body.method] ?? req.body.method;
888
+ if (req.body.method === 'message/send' || req.body.method === 'message/stream') {
889
+ req.body.params = normalizeSendMessageParamsForV1(req.body.params);
890
+ }
891
+ }
892
+ }
893
+ next();
894
+ };
895
+ function createTenantValidationHandler(agentCard) {
896
+ const requiredTenant = resolveRequiredJsonRpcTenant(agentCard);
897
+ return (req, res, next) => {
898
+ if (!requiredTenant ||
899
+ req.method !== 'POST' ||
900
+ typeof req.body !== 'object' ||
901
+ req.body === null ||
902
+ typeof req.body.method !== 'string') {
903
+ next();
904
+ return;
905
+ }
906
+ const requestedVersion = readRequestedA2AVersion(req);
907
+ if (requestedVersion !== '1.0') {
908
+ next();
909
+ return;
910
+ }
911
+ const rawParams = req.body.params;
912
+ if (typeof rawParams !== 'object' || rawParams === null || Array.isArray(rawParams)) {
913
+ res.status(200).json(createJsonRpcErrorResponse(req.body.id, {
914
+ code: A2AError.invalidParams(`tenant must be exactly "${requiredTenant}" for this agent interface.`).code,
915
+ message: `Invalid params: tenant must be exactly "${requiredTenant}" for this agent interface.`,
916
+ }));
917
+ return;
918
+ }
919
+ if (rawParams.tenant !== requiredTenant) {
920
+ res.status(200).json(createJsonRpcErrorResponse(req.body.id, {
921
+ code: A2AError.invalidParams(`tenant must be exactly "${requiredTenant}" for this agent interface.`).code,
922
+ message: `Invalid params: tenant must be exactly "${requiredTenant}" for this agent interface.`,
923
+ }));
924
+ return;
925
+ }
926
+ req.body.params = stripTenantFromParams(rawParams);
927
+ next();
928
+ };
929
+ }
930
+ function createListTasksHandler(taskStore) {
931
+ return async (req, res, next) => {
932
+ if (req.method !== 'POST' ||
933
+ typeof req.body !== 'object' ||
934
+ req.body === null ||
935
+ req.body.method !== 'ListTasks') {
936
+ next();
937
+ return;
938
+ }
939
+ const requestedVersion = readRequestedA2AVersion(req);
940
+ if (requestedVersion !== '1.0') {
941
+ next();
942
+ return;
943
+ }
944
+ if (!hasListableTaskStore(taskStore)) {
945
+ res.status(200).json(createJsonRpcErrorResponse(req.body.id, {
946
+ code: A2AError.methodNotFound('ListTasks').code,
947
+ message: A2AError.methodNotFound('ListTasks').message,
948
+ }));
949
+ return;
950
+ }
951
+ try {
952
+ const parsed = parseListTasksParams(req.body.params);
953
+ const result = await taskStore.listTasks({
954
+ contextId: parsed.contextId,
955
+ status: parsed.status,
956
+ pageSize: parsed.pageSize,
957
+ pageToken: parsed.pageToken,
958
+ statusTimestampAfter: parsed.statusTimestampAfter,
959
+ });
960
+ res.status(200).json({
961
+ jsonrpc: '2.0',
962
+ id: typeof req.body.id === 'string' ||
963
+ (typeof req.body.id === 'number' && Number.isInteger(req.body.id))
964
+ ? req.body.id
965
+ : null,
966
+ result: {
967
+ tasks: result.tasks.map((task) => normalizeListedTask(task, {
968
+ historyLength: parsed.historyLength,
969
+ includeArtifacts: parsed.includeArtifacts,
970
+ })),
971
+ nextPageToken: result.nextPageToken,
972
+ pageSize: result.pageSize,
973
+ totalSize: result.totalSize,
974
+ },
975
+ });
976
+ }
977
+ catch (error) {
978
+ const a2aError = error instanceof A2AError
979
+ ? error
980
+ : A2AError.internalError(error instanceof Error ? error.message : 'ListTasks failed.');
981
+ res.status(200).json(createJsonRpcErrorResponse(req.body.id, a2aError.toJSONRPCError()));
982
+ }
983
+ };
984
+ }
985
+ function createExtendedAgentCardCapabilityValidationHandler(agentCard) {
986
+ return (req, res, next) => {
987
+ if (req.method !== 'POST' ||
988
+ typeof req.body !== 'object' ||
989
+ req.body === null ||
990
+ req.body.method !== 'agent/getAuthenticatedExtendedCard') {
991
+ next();
992
+ return;
993
+ }
994
+ const requestedVersion = readRequestedA2AVersion(req);
995
+ if (supportsExtendedAgentCardForA2AVersion(agentCard, requestedVersion)) {
996
+ next();
997
+ return;
998
+ }
999
+ const error = A2AError.unsupportedOperation('Agent does not support authenticated extended card.');
1000
+ res.status(200).json(createJsonRpcErrorResponse(req.body.id, error.toJSONRPCError()));
1001
+ };
1002
+ }
1003
+ function createRequiredExtensionsValidationHandler(agentCard) {
1004
+ const capabilities = agentCard.capabilities;
1005
+ const requiredExtensions = capabilities?.extensions
1006
+ ?.filter((extension) => extension?.required === true &&
1007
+ typeof extension.uri === 'string' &&
1008
+ extension.uri.length > 0)
1009
+ .map((extension) => extension.uri) ?? [];
1010
+ return (req, res, next) => {
1011
+ if (requiredExtensions.length === 0 ||
1012
+ req.method !== 'POST' ||
1013
+ typeof req.body !== 'object' ||
1014
+ req.body === null ||
1015
+ typeof req.body.method !== 'string') {
1016
+ next();
1017
+ return;
1018
+ }
1019
+ if (readRequestedA2AVersion(req) !== '1.0') {
1020
+ next();
1021
+ return;
1022
+ }
1023
+ const requestedExtensions = readRequestedExtensions(req);
1024
+ const missingExtension = requiredExtensions.find((uri) => !requestedExtensions.has(uri));
1025
+ if (!missingExtension) {
1026
+ next();
1027
+ return;
1028
+ }
1029
+ const error = new A2AError(-32008, `Extension support required: ${missingExtension}`);
1030
+ res.status(200).json(createJsonRpcErrorResponse(req.body.id, error.toJSONRPCError()));
1031
+ };
1032
+ }
1033
+ function createTerminalResubscribeValidationHandler(taskStore) {
1034
+ return async (req, res, next) => {
1035
+ if (req.method !== 'POST' ||
1036
+ typeof req.body !== 'object' ||
1037
+ req.body === null ||
1038
+ req.body.method !== 'tasks/resubscribe') {
1039
+ next();
1040
+ return;
1041
+ }
1042
+ const requestedVersion = readRequestedA2AVersion(req);
1043
+ if (requestedVersion !== '1.0') {
1044
+ next();
1045
+ return;
1046
+ }
1047
+ const params = req.body.params;
1048
+ if (typeof params !== 'object' || params === null || Array.isArray(params)) {
1049
+ next();
1050
+ return;
1051
+ }
1052
+ const taskId = params.id;
1053
+ if (typeof taskId !== 'string') {
1054
+ next();
1055
+ return;
1056
+ }
1057
+ const task = await taskStore.load(taskId);
1058
+ if (!task || !TERMINAL_TASK_STATES.has(task.status.state)) {
1059
+ next();
1060
+ return;
1061
+ }
1062
+ const error = A2AError.unsupportedOperation('SubscribeToTask is not available for terminal tasks.');
1063
+ res.status(200).json(createJsonRpcErrorResponse(req.body.id, error.toJSONRPCError()));
1064
+ };
1065
+ }
1066
+ function createPushNotificationConfigHandler(agentCard, taskStore, pushNotificationStore) {
1067
+ return async (req, res, next) => {
1068
+ if (req.method !== 'POST' ||
1069
+ typeof req.body !== 'object' ||
1070
+ req.body === null ||
1071
+ typeof req.body.method !== 'string') {
1072
+ next();
1073
+ return;
1074
+ }
1075
+ const requestedVersion = readRequestedA2AVersion(req);
1076
+ if (requestedVersion !== '1.0') {
1077
+ next();
1078
+ return;
1079
+ }
1080
+ const method = req.body.method;
1081
+ if (method !== 'tasks/pushNotificationConfig/set' &&
1082
+ method !== 'tasks/pushNotificationConfig/get' &&
1083
+ method !== 'tasks/pushNotificationConfig/list' &&
1084
+ method !== 'tasks/pushNotificationConfig/delete') {
1085
+ next();
1086
+ return;
1087
+ }
1088
+ if (!supportsPushNotifications(agentCard)) {
1089
+ const error = A2AError.pushNotificationNotSupported();
1090
+ res.status(200).json(createJsonRpcErrorResponse(req.body.id, error.toJSONRPCError()));
1091
+ return;
1092
+ }
1093
+ if (!pushNotificationStore) {
1094
+ const error = A2AError.internalError('Push notification store is not configured.');
1095
+ res.status(200).json(createJsonRpcErrorResponse(req.body.id, error.toJSONRPCError()));
1096
+ return;
1097
+ }
1098
+ try {
1099
+ if (method === 'tasks/pushNotificationConfig/set') {
1100
+ const config = parsePushNotificationConfigParams(req.body.params);
1101
+ const task = await taskStore.load(config.taskId);
1102
+ if (!task) {
1103
+ throw A2AError.taskNotFound(config.taskId);
1104
+ }
1105
+ const storedConfig = toStoredPushNotificationConfig(config);
1106
+ await pushNotificationStore.save(config.taskId, storedConfig);
1107
+ res.status(200).json({
1108
+ jsonrpc: '2.0',
1109
+ id: typeof req.body.id === 'string' ||
1110
+ (typeof req.body.id === 'number' && Number.isInteger(req.body.id))
1111
+ ? req.body.id
1112
+ : null,
1113
+ result: toExternalPushNotificationConfig(config.taskId, storedConfig),
1114
+ });
1115
+ return;
1116
+ }
1117
+ if (method === 'tasks/pushNotificationConfig/get') {
1118
+ const params = parsePushNotificationConfigLookupParams(req.body.params);
1119
+ const task = await taskStore.load(params.taskId);
1120
+ if (!task) {
1121
+ throw A2AError.taskNotFound(params.taskId);
1122
+ }
1123
+ const configs = await pushNotificationStore.load(params.taskId);
1124
+ const config = configs.find((entry) => entry.id === params.id);
1125
+ if (!config) {
1126
+ throw A2AError.taskNotFound(params.taskId);
1127
+ }
1128
+ res.status(200).json({
1129
+ jsonrpc: '2.0',
1130
+ id: typeof req.body.id === 'string' ||
1131
+ (typeof req.body.id === 'number' && Number.isInteger(req.body.id))
1132
+ ? req.body.id
1133
+ : null,
1134
+ result: toExternalPushNotificationConfig(params.taskId, config),
1135
+ });
1136
+ return;
1137
+ }
1138
+ if (method === 'tasks/pushNotificationConfig/list') {
1139
+ const params = parseListPushNotificationConfigsParams(req.body.params);
1140
+ const task = await taskStore.load(params.taskId);
1141
+ if (!task) {
1142
+ throw A2AError.taskNotFound(params.taskId);
1143
+ }
1144
+ let configs = await pushNotificationStore.load(params.taskId);
1145
+ const pageSize = params.pageSize ?? configs.length;
1146
+ if (params.pageToken) {
1147
+ const cursorIndex = configs.findIndex((entry) => entry.id === params.pageToken);
1148
+ if (cursorIndex < 0) {
1149
+ throw A2AError.invalidParams(`Unknown pageToken: ${params.pageToken}`);
1150
+ }
1151
+ configs = configs.slice(cursorIndex + 1);
1152
+ }
1153
+ const pageConfigs = configs.slice(0, pageSize);
1154
+ const nextPageToken = configs.length > pageConfigs.length && pageConfigs.length > 0
1155
+ ? (pageConfigs[pageConfigs.length - 1]?.id ?? '')
1156
+ : '';
1157
+ res.status(200).json({
1158
+ jsonrpc: '2.0',
1159
+ id: typeof req.body.id === 'string' ||
1160
+ (typeof req.body.id === 'number' && Number.isInteger(req.body.id))
1161
+ ? req.body.id
1162
+ : null,
1163
+ result: {
1164
+ configs: pageConfigs.map((config) => toExternalPushNotificationConfig(params.taskId, config)),
1165
+ nextPageToken,
1166
+ },
1167
+ });
1168
+ return;
1169
+ }
1170
+ const params = parsePushNotificationConfigLookupParams(req.body.params);
1171
+ const task = await taskStore.load(params.taskId);
1172
+ if (!task) {
1173
+ throw A2AError.taskNotFound(params.taskId);
1174
+ }
1175
+ await pushNotificationStore.delete(params.taskId, params.id);
1176
+ res.status(200).json({
1177
+ jsonrpc: '2.0',
1178
+ id: typeof req.body.id === 'string' ||
1179
+ (typeof req.body.id === 'number' && Number.isInteger(req.body.id))
1180
+ ? req.body.id
1181
+ : null,
1182
+ result: {},
1183
+ });
1184
+ }
1185
+ catch (error) {
1186
+ const a2aError = error instanceof A2AError
1187
+ ? error
1188
+ : A2AError.internalError(error instanceof Error ? error.message : 'Push notification config handling failed.');
1189
+ res.status(200).json(createJsonRpcErrorResponse(req.body.id, a2aError.toJSONRPCError()));
1190
+ }
1191
+ };
1192
+ }
1193
+ function parsePushNotificationConfigParams(rawParams) {
1194
+ if (typeof rawParams !== 'object' || rawParams === null || Array.isArray(rawParams)) {
1195
+ throw A2AError.invalidParams('Push notification config params must be an object.');
1196
+ }
1197
+ const params = rawParams;
1198
+ if (!params.taskId || typeof params.taskId !== 'string') {
1199
+ throw A2AError.invalidParams('Push notification config taskId must be a string.');
1200
+ }
1201
+ if (params.id !== undefined && typeof params.id !== 'string') {
1202
+ throw A2AError.invalidParams('Push notification config id must be a string.');
1203
+ }
1204
+ if (!params.url || typeof params.url !== 'string') {
1205
+ throw A2AError.invalidParams('Push notification config url must be a string.');
1206
+ }
1207
+ if (params.token !== undefined && typeof params.token !== 'string') {
1208
+ throw A2AError.invalidParams('Push notification config token must be a string.');
1209
+ }
1210
+ if (params.authentication !== undefined &&
1211
+ (typeof params.authentication !== 'object' ||
1212
+ params.authentication === null ||
1213
+ Array.isArray(params.authentication) ||
1214
+ typeof params.authentication.scheme !== 'string' ||
1215
+ (params.authentication.credentials !== undefined &&
1216
+ typeof params.authentication.credentials !== 'string'))) {
1217
+ throw A2AError.invalidParams('Push notification config authentication must be an object with a string scheme.');
1218
+ }
1219
+ return {
1220
+ taskId: params.taskId,
1221
+ id: params.id ?? params.taskId,
1222
+ url: params.url,
1223
+ token: params.token,
1224
+ authentication: params.authentication,
1225
+ };
1226
+ }
1227
+ function parsePushNotificationConfigLookupParams(rawParams) {
1228
+ if (typeof rawParams !== 'object' || rawParams === null || Array.isArray(rawParams)) {
1229
+ throw A2AError.invalidParams('Push notification config lookup params must be an object.');
1230
+ }
1231
+ const params = rawParams;
1232
+ if (!params.taskId || typeof params.taskId !== 'string') {
1233
+ throw A2AError.invalidParams('Push notification config taskId must be a string.');
1234
+ }
1235
+ if (!params.id || typeof params.id !== 'string') {
1236
+ throw A2AError.invalidParams('Push notification config id must be a string.');
1237
+ }
1238
+ return {
1239
+ taskId: params.taskId,
1240
+ id: params.id,
1241
+ };
1242
+ }
1243
+ function parseListPushNotificationConfigsParams(rawParams) {
1244
+ if (typeof rawParams !== 'object' || rawParams === null || Array.isArray(rawParams)) {
1245
+ throw A2AError.invalidParams('ListTaskPushNotificationConfigs params must be an object.');
1246
+ }
1247
+ const params = rawParams;
1248
+ if (!params.taskId || typeof params.taskId !== 'string') {
1249
+ throw A2AError.invalidParams('ListTaskPushNotificationConfigs taskId must be a string.');
1250
+ }
1251
+ if (params.pageSize !== undefined &&
1252
+ (!Number.isInteger(params.pageSize) || params.pageSize < 1)) {
1253
+ throw A2AError.invalidParams('ListTaskPushNotificationConfigs pageSize must be a positive integer.');
1254
+ }
1255
+ if (params.pageToken !== undefined && typeof params.pageToken !== 'string') {
1256
+ throw A2AError.invalidParams('ListTaskPushNotificationConfigs pageToken must be a string.');
1257
+ }
1258
+ return {
1259
+ taskId: params.taskId,
1260
+ pageSize: params.pageSize,
1261
+ pageToken: params.pageToken,
1262
+ };
1263
+ }
1264
+ function toStoredPushNotificationConfig(config) {
1265
+ const authentication = config.authentication?.scheme !== undefined
1266
+ ? {
1267
+ schemes: [config.authentication.scheme],
1268
+ credentials: config.authentication.credentials ?? '',
1269
+ }
1270
+ : undefined;
1271
+ return {
1272
+ id: config.id,
1273
+ url: config.url,
1274
+ token: config.token ?? '',
1275
+ authentication,
1276
+ };
1277
+ }
1278
+ function toExternalPushNotificationConfig(taskId, config) {
1279
+ const external = {
1280
+ id: config.id,
1281
+ taskId,
1282
+ url: config.url,
1283
+ };
1284
+ if (config.token !== undefined && config.token !== '') {
1285
+ external.token = config.token;
1286
+ }
1287
+ if (config.authentication !== undefined) {
1288
+ const authentication = {
1289
+ scheme: config.authentication.schemes[0],
1290
+ };
1291
+ if (config.authentication.credentials !== '') {
1292
+ authentication.credentials = config.authentication.credentials;
1293
+ }
1294
+ external.authentication = authentication;
1295
+ }
1296
+ return external;
1297
+ }
4
1298
  export function createA2ASdkExpressApp(options) {
5
- const store = options.taskStore ?? new InMemoryTaskStore();
6
- const requestHandler = new DefaultRequestHandler(options.agentCard, store, options.agentExecutor);
1299
+ const store = options.taskStore ?? new ProtocolAlignedInMemoryTaskStore();
1300
+ const pushNotificationStore = options.pushNotificationStore ??
1301
+ (supportsPushNotifications(options.agentCard)
1302
+ ? new InMemoryPushNotificationStore()
1303
+ : undefined);
1304
+ const requestHandler = new ProtocolAlignedRequestHandler(options.agentCard, store, options.agentExecutor, pushNotificationStore, options.pushNotificationSender, options.extendedAgentCardProvider);
7
1305
  const userBuilder = options.userBuilder ?? ((_) => UserBuilder.noAuthentication());
8
1306
  const app = express();
9
1307
  app.disable('x-powered-by');
@@ -16,6 +1314,16 @@ export function createA2ASdkExpressApp(options) {
16
1314
  if (options.authMiddleware) {
17
1315
  router.use(options.authMiddleware);
18
1316
  }
1317
+ router.use(express.json());
1318
+ router.use(normalizeA2AExtensionHeadersByVersion);
1319
+ router.use(normalizeJsonRpcRequestByVersion);
1320
+ router.use(normalizeJsonRpcResponsesByVersion);
1321
+ router.use(createRequiredExtensionsValidationHandler(options.agentCard));
1322
+ router.use(createTenantValidationHandler(options.agentCard));
1323
+ router.use(createListTasksHandler(store));
1324
+ router.use(createExtendedAgentCardCapabilityValidationHandler(options.agentCard));
1325
+ router.use(createTerminalResubscribeValidationHandler(store));
1326
+ router.use(createPushNotificationConfigHandler(options.agentCard, store, pushNotificationStore));
19
1327
  router.use(jsonRpcHandler({ requestHandler, userBuilder }));
20
1328
  app.use(rpcPath, router);
21
1329
  return app;