salmon-loop 0.2.16 → 0.3.1
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/dist/cli/authorization/non-interactive.js +7 -21
- package/dist/cli/commands/chat.js +1 -1
- package/dist/cli/commands/parallel.js +46 -41
- package/dist/cli/commands/run/assistant-message.js +3 -0
- package/dist/cli/commands/run/handler.js +2 -1
- package/dist/cli/commands/serve.js +112 -156
- package/dist/cli/headless/json-protocol.js +1 -1
- package/dist/cli/headless/stream-json-protocol.js +3 -2
- package/dist/cli/program-bootstrap.js +2 -2
- package/dist/cli/slash/runtime.js +5 -1
- package/dist/core/adapters/fs/node-fs.js +1 -0
- package/dist/core/backends/salmon-loop/task-executor.js +1 -0
- package/dist/core/benchmark/patch-artifact.js +1 -1
- package/dist/core/context/service.js +5 -2
- package/dist/core/extensions/index.js +2 -35
- package/dist/core/extensions/merge.js +14 -0
- package/dist/core/extensions/redact.js +9 -3
- package/dist/core/extensions/schemas.js +2 -51
- package/dist/core/facades/cli-authorization-non-interactive.js +1 -1
- package/dist/core/facades/cli-program-bootstrap.js +1 -0
- package/dist/core/facades/cli-serve.js +2 -1
- package/dist/core/grizzco/dsl/strategies.js +1 -3
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +12 -7
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +23 -23
- package/dist/core/grizzco/engine/transaction/report-mapper.js +3 -0
- package/dist/core/grizzco/engine/transaction/transaction-runner.js +14 -0
- package/dist/core/grizzco/flows/AutopilotFlow.js +1 -0
- package/dist/core/grizzco/flows/SalmonLoopFlow.js +1 -0
- package/dist/core/grizzco/steps/apply.js +0 -7
- package/dist/core/grizzco/steps/autopilot.js +108 -6
- package/dist/core/grizzco/steps/preflight.js +10 -0
- package/dist/core/grizzco/steps/tool-runtime.js +1 -0
- package/dist/core/interaction/events/bus.js +14 -0
- package/dist/core/interaction/orchestration/facade.js +11 -1
- package/dist/core/llm/ai-sdk/request-params.js +40 -1
- package/dist/core/mcp/bridge/index.js +4 -0
- package/dist/core/mcp/bridge/prompt-command-provider.js +261 -0
- package/dist/core/mcp/bridge/resource-context-provider.js +259 -0
- package/dist/core/mcp/bridge/tool-bridge.js +303 -0
- package/dist/core/mcp/cache/resource-cache.js +41 -0
- package/dist/core/mcp/catalog/discovery.js +51 -0
- package/dist/core/mcp/catalog/notification-router.js +28 -0
- package/dist/core/mcp/catalog/prompt-catalog.js +4 -0
- package/dist/core/mcp/catalog/resource-catalog.js +7 -0
- package/dist/core/mcp/catalog/tool-catalog.js +4 -0
- package/dist/core/mcp/client/connection-manager.js +239 -0
- package/dist/core/mcp/client/lifecycle.js +13 -0
- package/dist/core/mcp/client/transport-factory.js +168 -0
- package/dist/core/mcp/config/index.js +32 -0
- package/dist/core/mcp/config/schema-v2.js +129 -0
- package/dist/core/mcp/host/elicitation-provider.js +209 -0
- package/dist/core/mcp/host/roots-provider.js +70 -0
- package/dist/core/mcp/host/sampling-provider.js +170 -0
- package/dist/core/mcp/index.js +4 -0
- package/dist/core/mcp/observability/events.js +19 -0
- package/dist/core/mcp/policy/approval-policy.js +2 -0
- package/dist/core/mcp/policy/classifier.js +172 -0
- package/dist/core/mcp/policy/grants.js +356 -0
- package/dist/core/mcp/policy/uri-policy.js +60 -0
- package/dist/core/mcp/schema/json-schema-to-zod.js +511 -0
- package/dist/core/mcp/types.js +2 -0
- package/dist/core/protocols/a2a/agent-card.js +38 -12
- package/dist/core/protocols/a2a/sdk/executor.js +105 -36
- package/dist/core/protocols/a2a/sdk/server.js +1311 -3
- package/dist/core/protocols/acp/acp-checkpoint-probe.js +113 -0
- package/dist/core/protocols/acp/acp-session-persistence.js +336 -0
- package/dist/core/protocols/acp/acp-types.js +17 -0
- package/dist/core/protocols/acp/formal-agent.js +389 -502
- package/dist/core/protocols/acp/handlers.js +3 -0
- package/dist/core/protocols/acp/permission-provider.js +11 -39
- package/dist/core/protocols/acp/stdio-server.js +20 -1
- package/dist/core/protocols/acp/tool-kind-mapping.js +62 -0
- package/dist/core/protocols/shared/flow-mode-mapping.js +0 -8
- package/dist/core/public-capabilities/flow-mode-metadata.js +0 -6
- package/dist/core/public-capabilities/projections.js +1 -0
- package/dist/core/runtime/agent-server-runtime.js +2 -3
- package/dist/core/runtime/spawn-command.js +8 -2
- package/dist/core/runtime/spawn-interactive.js +26 -0
- package/dist/core/session/manager.js +48 -25
- package/dist/core/tools/builtin/index.js +6 -1
- package/dist/core/tools/builtin/proposal.js +0 -7
- package/dist/core/tools/builtin/workspace.js +76 -0
- package/dist/core/tools/dispatcher.js +1 -0
- package/dist/core/tools/loader.js +92 -46
- package/dist/core/verification/runner.js +60 -31
- package/dist/core/version.js +17 -0
- package/dist/core/workspace/capabilities.js +80 -0
- package/dist/locales/en.js +17 -3
- package/package.json +4 -2
- package/dist/core/protocols/a2a/mapper.js +0 -14
- package/dist/core/protocols/a2a/sdk/auth-middleware.js +0 -31
- package/dist/core/protocols/a2a/task-projection.js +0 -45
- package/dist/core/protocols/acp/checkpoint-meta.js +0 -2
- package/dist/core/tools/mcp/client.js +0 -308
- package/dist/core/tools/mcp/loader.js +0 -110
- package/dist/core/tools/mcp/schema.js +0 -54
- package/dist/core/tools/mcp/streamable-http.js +0 -101
- package/dist/core/tools/mcp/types.js +0 -26
|
@@ -1,9 +1,1307 @@
|
|
|
1
|
-
import {
|
|
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
|
|
6
|
-
const
|
|
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;
|