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