@a2a-js/sdk 0.3.5 → 0.3.7
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 +225 -187
- package/dist/a2a_request_handler-B3LxMq3P.d.cts +49 -0
- package/dist/a2a_request_handler-BuP9LgXH.d.ts +49 -0
- package/dist/chunk-3QDLXHKS.js +8 -0
- package/dist/{chunk-SY3G7ITG.js → chunk-LTPINR5K.js} +81 -56
- package/dist/chunk-SJNAG4AL.js +122 -0
- package/dist/chunk-ZX6KNMCP.js +38 -0
- package/dist/client/index.cjs +1304 -265
- package/dist/client/index.d.cts +477 -48
- package/dist/client/index.d.ts +477 -48
- package/dist/client/index.js +1165 -257
- package/dist/{types-DNKcmF0f.d.cts → extensions-DvruCIzw.d.cts} +28 -1
- package/dist/{types-DNKcmF0f.d.ts → extensions-DvruCIzw.d.ts} +28 -1
- package/dist/index.cjs +42 -2
- package/dist/index.d.cts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +9 -3
- package/dist/server/express/index.cjs +757 -146
- package/dist/server/express/index.d.cts +83 -6
- package/dist/server/express/index.d.ts +83 -6
- package/dist/server/express/index.js +620 -92
- package/dist/server/index.cjs +486 -142
- package/dist/server/index.d.cts +100 -33
- package/dist/server/index.d.ts +100 -33
- package/dist/server/index.js +379 -88
- package/package.json +21 -22
- package/dist/a2a_request_handler-B5t-IxgA.d.ts +0 -17
- package/dist/a2a_request_handler-DUvKWfix.d.cts +0 -17
- package/dist/chunk-67JNQ6TZ.js +0 -6
package/dist/client/index.js
CHANGED
|
@@ -1,14 +1,329 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
A2A_ERROR_CODE,
|
|
3
|
+
AuthenticatedExtendedCardNotConfiguredError,
|
|
4
|
+
ContentTypeNotSupportedError,
|
|
5
|
+
InvalidAgentResponseError,
|
|
6
|
+
PushNotificationNotSupportedError,
|
|
7
|
+
TaskNotCancelableError,
|
|
8
|
+
TaskNotFoundError,
|
|
9
|
+
UnsupportedOperationError,
|
|
10
|
+
parseSseStream
|
|
11
|
+
} from "../chunk-SJNAG4AL.js";
|
|
12
|
+
import {
|
|
13
|
+
AGENT_CARD_PATH,
|
|
14
|
+
HTTP_EXTENSION_HEADER
|
|
15
|
+
} from "../chunk-3QDLXHKS.js";
|
|
16
|
+
import {
|
|
17
|
+
Extensions
|
|
18
|
+
} from "../chunk-ZX6KNMCP.js";
|
|
19
|
+
|
|
20
|
+
// src/client/transports/json_rpc_transport.ts
|
|
21
|
+
var JsonRpcTransport = class _JsonRpcTransport {
|
|
22
|
+
customFetchImpl;
|
|
23
|
+
endpoint;
|
|
24
|
+
requestIdCounter = 1;
|
|
25
|
+
constructor(options) {
|
|
26
|
+
this.endpoint = options.endpoint;
|
|
27
|
+
this.customFetchImpl = options.fetchImpl;
|
|
28
|
+
}
|
|
29
|
+
async getExtendedAgentCard(options, idOverride) {
|
|
30
|
+
const rpcResponse = await this._sendRpcRequest("agent/getAuthenticatedExtendedCard", void 0, idOverride, options);
|
|
31
|
+
return rpcResponse.result;
|
|
32
|
+
}
|
|
33
|
+
async sendMessage(params, options, idOverride) {
|
|
34
|
+
const rpcResponse = await this._sendRpcRequest(
|
|
35
|
+
"message/send",
|
|
36
|
+
params,
|
|
37
|
+
idOverride,
|
|
38
|
+
options
|
|
39
|
+
);
|
|
40
|
+
return rpcResponse.result;
|
|
41
|
+
}
|
|
42
|
+
async *sendMessageStream(params, options) {
|
|
43
|
+
yield* this._sendStreamingRequest("message/stream", params, options);
|
|
44
|
+
}
|
|
45
|
+
async setTaskPushNotificationConfig(params, options, idOverride) {
|
|
46
|
+
const rpcResponse = await this._sendRpcRequest("tasks/pushNotificationConfig/set", params, idOverride, options);
|
|
47
|
+
return rpcResponse.result;
|
|
48
|
+
}
|
|
49
|
+
async getTaskPushNotificationConfig(params, options, idOverride) {
|
|
50
|
+
const rpcResponse = await this._sendRpcRequest("tasks/pushNotificationConfig/get", params, idOverride, options);
|
|
51
|
+
return rpcResponse.result;
|
|
52
|
+
}
|
|
53
|
+
async listTaskPushNotificationConfig(params, options, idOverride) {
|
|
54
|
+
const rpcResponse = await this._sendRpcRequest("tasks/pushNotificationConfig/list", params, idOverride, options);
|
|
55
|
+
return rpcResponse.result;
|
|
56
|
+
}
|
|
57
|
+
async deleteTaskPushNotificationConfig(params, options, idOverride) {
|
|
58
|
+
await this._sendRpcRequest("tasks/pushNotificationConfig/delete", params, idOverride, options);
|
|
59
|
+
}
|
|
60
|
+
async getTask(params, options, idOverride) {
|
|
61
|
+
const rpcResponse = await this._sendRpcRequest(
|
|
62
|
+
"tasks/get",
|
|
63
|
+
params,
|
|
64
|
+
idOverride,
|
|
65
|
+
options
|
|
66
|
+
);
|
|
67
|
+
return rpcResponse.result;
|
|
68
|
+
}
|
|
69
|
+
async cancelTask(params, options, idOverride) {
|
|
70
|
+
const rpcResponse = await this._sendRpcRequest(
|
|
71
|
+
"tasks/cancel",
|
|
72
|
+
params,
|
|
73
|
+
idOverride,
|
|
74
|
+
options
|
|
75
|
+
);
|
|
76
|
+
return rpcResponse.result;
|
|
77
|
+
}
|
|
78
|
+
async *resubscribeTask(params, options) {
|
|
79
|
+
yield* this._sendStreamingRequest("tasks/resubscribe", params, options);
|
|
80
|
+
}
|
|
81
|
+
async callExtensionMethod(method, params, idOverride, options) {
|
|
82
|
+
return await this._sendRpcRequest(
|
|
83
|
+
method,
|
|
84
|
+
params,
|
|
85
|
+
idOverride,
|
|
86
|
+
options
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
_fetch(...args) {
|
|
90
|
+
if (this.customFetchImpl) {
|
|
91
|
+
return this.customFetchImpl(...args);
|
|
92
|
+
}
|
|
93
|
+
if (typeof fetch === "function") {
|
|
94
|
+
return fetch(...args);
|
|
95
|
+
}
|
|
96
|
+
throw new Error(
|
|
97
|
+
"A `fetch` implementation was not provided and is not available in the global scope. Please provide a `fetchImpl` in the A2ATransportOptions. "
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
async _sendRpcRequest(method, params, idOverride, options) {
|
|
101
|
+
const requestId = idOverride ?? this.requestIdCounter++;
|
|
102
|
+
const rpcRequest = {
|
|
103
|
+
jsonrpc: "2.0",
|
|
104
|
+
method,
|
|
105
|
+
params,
|
|
106
|
+
id: requestId
|
|
107
|
+
};
|
|
108
|
+
const httpResponse = await this._fetchRpc(rpcRequest, "application/json", options);
|
|
109
|
+
if (!httpResponse.ok) {
|
|
110
|
+
let errorBodyText = "(empty or non-JSON response)";
|
|
111
|
+
let errorJson;
|
|
112
|
+
try {
|
|
113
|
+
errorBodyText = await httpResponse.text();
|
|
114
|
+
errorJson = JSON.parse(errorBodyText);
|
|
115
|
+
} catch (e) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`HTTP error for ${method}! Status: ${httpResponse.status} ${httpResponse.statusText}. Response: ${errorBodyText}`,
|
|
118
|
+
{ cause: e }
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
if (errorJson.jsonrpc && errorJson.error) {
|
|
122
|
+
throw _JsonRpcTransport.mapToError(errorJson);
|
|
123
|
+
} else {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`HTTP error for ${method}! Status: ${httpResponse.status} ${httpResponse.statusText}. Response: ${errorBodyText}`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const rpcResponse = await httpResponse.json();
|
|
130
|
+
if (rpcResponse.id !== requestId) {
|
|
131
|
+
console.error(
|
|
132
|
+
`CRITICAL: RPC response ID mismatch for method ${method}. Expected ${requestId}, got ${rpcResponse.id}.`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
if ("error" in rpcResponse) {
|
|
136
|
+
throw _JsonRpcTransport.mapToError(rpcResponse);
|
|
137
|
+
}
|
|
138
|
+
return rpcResponse;
|
|
139
|
+
}
|
|
140
|
+
async _fetchRpc(rpcRequest, acceptHeader = "application/json", options) {
|
|
141
|
+
const requestInit = {
|
|
142
|
+
method: "POST",
|
|
143
|
+
headers: {
|
|
144
|
+
...options?.serviceParameters,
|
|
145
|
+
"Content-Type": "application/json",
|
|
146
|
+
Accept: acceptHeader
|
|
147
|
+
},
|
|
148
|
+
body: JSON.stringify(rpcRequest),
|
|
149
|
+
signal: options?.signal
|
|
150
|
+
};
|
|
151
|
+
return this._fetch(this.endpoint, requestInit);
|
|
152
|
+
}
|
|
153
|
+
async *_sendStreamingRequest(method, params, options) {
|
|
154
|
+
const clientRequestId = this.requestIdCounter++;
|
|
155
|
+
const rpcRequest = {
|
|
156
|
+
jsonrpc: "2.0",
|
|
157
|
+
method,
|
|
158
|
+
params,
|
|
159
|
+
id: clientRequestId
|
|
160
|
+
};
|
|
161
|
+
const response = await this._fetchRpc(rpcRequest, "text/event-stream", options);
|
|
162
|
+
if (!response.ok) {
|
|
163
|
+
let errorBody = "";
|
|
164
|
+
let errorJson;
|
|
165
|
+
try {
|
|
166
|
+
errorBody = await response.text();
|
|
167
|
+
errorJson = JSON.parse(errorBody);
|
|
168
|
+
} catch (e) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`HTTP error establishing stream for ${method}: ${response.status} ${response.statusText}. Response: ${errorBody || "(empty)"}`,
|
|
171
|
+
{ cause: e }
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
if (errorJson.error) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`HTTP error establishing stream for ${method}: ${response.status} ${response.statusText}. RPC Error: ${errorJson.error.message} (Code: ${errorJson.error.code})`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
throw new Error(
|
|
180
|
+
`HTTP error establishing stream for ${method}: ${response.status} ${response.statusText}`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
if (!response.headers.get("Content-Type")?.startsWith("text/event-stream")) {
|
|
184
|
+
throw new Error(
|
|
185
|
+
`Invalid response Content-Type for SSE stream for ${method}. Expected 'text/event-stream'.`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
for await (const event of parseSseStream(response)) {
|
|
189
|
+
yield this._processSseEventData(event.data, clientRequestId);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
_processSseEventData(jsonData, originalRequestId) {
|
|
193
|
+
if (!jsonData.trim()) {
|
|
194
|
+
throw new Error("Attempted to process empty SSE event data.");
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const sseJsonRpcResponse = JSON.parse(jsonData);
|
|
198
|
+
const a2aStreamResponse = sseJsonRpcResponse;
|
|
199
|
+
if (a2aStreamResponse.id !== originalRequestId) {
|
|
200
|
+
console.warn(
|
|
201
|
+
`SSE Event's JSON-RPC response ID mismatch. Client request ID: ${originalRequestId}, event response ID: ${a2aStreamResponse.id}.`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
if ("error" in a2aStreamResponse) {
|
|
205
|
+
const err = a2aStreamResponse.error;
|
|
206
|
+
throw new Error(
|
|
207
|
+
`SSE event contained an error: ${err.message} (Code: ${err.code}) Data: ${JSON.stringify(err.data || {})}`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
if (!("result" in a2aStreamResponse) || typeof a2aStreamResponse.result === "undefined") {
|
|
211
|
+
throw new Error(`SSE event JSON-RPC response is missing 'result' field. Data: ${jsonData}`);
|
|
212
|
+
}
|
|
213
|
+
return a2aStreamResponse.result;
|
|
214
|
+
} catch (e) {
|
|
215
|
+
if (e instanceof Error && (e.message.startsWith("SSE event contained an error") || e.message.startsWith("SSE event JSON-RPC response is missing 'result' field"))) {
|
|
216
|
+
throw e;
|
|
217
|
+
}
|
|
218
|
+
console.error(
|
|
219
|
+
"Failed to parse SSE event data string or unexpected JSON-RPC structure:",
|
|
220
|
+
jsonData,
|
|
221
|
+
e
|
|
222
|
+
);
|
|
223
|
+
throw new Error(
|
|
224
|
+
`Failed to parse SSE event data: "${jsonData.substring(0, 100)}...". Original error: ${e instanceof Error && e.message || "Unknown error"}`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
static mapToError(response) {
|
|
229
|
+
switch (response.error.code) {
|
|
230
|
+
case -32001:
|
|
231
|
+
return new TaskNotFoundJSONRPCError(response);
|
|
232
|
+
case -32002:
|
|
233
|
+
return new TaskNotCancelableJSONRPCError(response);
|
|
234
|
+
case -32003:
|
|
235
|
+
return new PushNotificationNotSupportedJSONRPCError(response);
|
|
236
|
+
case -32004:
|
|
237
|
+
return new UnsupportedOperationJSONRPCError(response);
|
|
238
|
+
case -32005:
|
|
239
|
+
return new ContentTypeNotSupportedJSONRPCError(response);
|
|
240
|
+
case -32006:
|
|
241
|
+
return new InvalidAgentResponseJSONRPCError(response);
|
|
242
|
+
case -32007:
|
|
243
|
+
return new AuthenticatedExtendedCardNotConfiguredJSONRPCError(response);
|
|
244
|
+
default:
|
|
245
|
+
return new JSONRPCTransportError(response);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
var JsonRpcTransportFactory = class _JsonRpcTransportFactory {
|
|
250
|
+
constructor(options) {
|
|
251
|
+
this.options = options;
|
|
252
|
+
}
|
|
253
|
+
static name = "JSONRPC";
|
|
254
|
+
get protocolName() {
|
|
255
|
+
return _JsonRpcTransportFactory.name;
|
|
256
|
+
}
|
|
257
|
+
async create(url, _agentCard) {
|
|
258
|
+
return new JsonRpcTransport({
|
|
259
|
+
endpoint: url,
|
|
260
|
+
fetchImpl: this.options?.fetchImpl
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
var JSONRPCTransportError = class extends Error {
|
|
265
|
+
constructor(errorResponse) {
|
|
266
|
+
super(
|
|
267
|
+
`JSON-RPC error: ${errorResponse.error.message} (Code: ${errorResponse.error.code}) Data: ${JSON.stringify(errorResponse.error.data || {})}`
|
|
268
|
+
);
|
|
269
|
+
this.errorResponse = errorResponse;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
var TaskNotFoundJSONRPCError = class extends TaskNotFoundError {
|
|
273
|
+
constructor(errorResponse) {
|
|
274
|
+
super();
|
|
275
|
+
this.errorResponse = errorResponse;
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
var TaskNotCancelableJSONRPCError = class extends TaskNotCancelableError {
|
|
279
|
+
constructor(errorResponse) {
|
|
280
|
+
super();
|
|
281
|
+
this.errorResponse = errorResponse;
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
var PushNotificationNotSupportedJSONRPCError = class extends PushNotificationNotSupportedError {
|
|
285
|
+
constructor(errorResponse) {
|
|
286
|
+
super();
|
|
287
|
+
this.errorResponse = errorResponse;
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
var UnsupportedOperationJSONRPCError = class extends UnsupportedOperationError {
|
|
291
|
+
constructor(errorResponse) {
|
|
292
|
+
super();
|
|
293
|
+
this.errorResponse = errorResponse;
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
var ContentTypeNotSupportedJSONRPCError = class extends ContentTypeNotSupportedError {
|
|
297
|
+
constructor(errorResponse) {
|
|
298
|
+
super();
|
|
299
|
+
this.errorResponse = errorResponse;
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
var InvalidAgentResponseJSONRPCError = class extends InvalidAgentResponseError {
|
|
303
|
+
constructor(errorResponse) {
|
|
304
|
+
super();
|
|
305
|
+
this.errorResponse = errorResponse;
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
var AuthenticatedExtendedCardNotConfiguredJSONRPCError = class extends AuthenticatedExtendedCardNotConfiguredError {
|
|
309
|
+
constructor(errorResponse) {
|
|
310
|
+
super();
|
|
311
|
+
this.errorResponse = errorResponse;
|
|
312
|
+
}
|
|
313
|
+
};
|
|
4
314
|
|
|
5
315
|
// src/client/client.ts
|
|
6
316
|
var A2AClient = class _A2AClient {
|
|
317
|
+
static emptyOptions = void 0;
|
|
7
318
|
agentCardPromise;
|
|
8
|
-
requestIdCounter = 1;
|
|
9
|
-
serviceEndpointUrl;
|
|
10
|
-
// To be populated from AgentCard after fetching
|
|
11
319
|
customFetchImpl;
|
|
320
|
+
serviceEndpointUrl;
|
|
321
|
+
// To be populated from AgentCard after fetchin
|
|
322
|
+
// A2AClient is built around JSON-RPC types, so it will only support JSON-RPC transport, new client with transport agnostic interface is going to be created for multi-transport.
|
|
323
|
+
// New transport abstraction isn't going to expose individual transport specific fields, so to keep returning JSON-RPC IDs here for compatibility,
|
|
324
|
+
// keep counter here and pass it to JsonRpcTransport via an optional idOverride parameter (which is not visible via transport-agnostic A2ATransport interface).
|
|
325
|
+
transport;
|
|
326
|
+
requestIdCounter = 1;
|
|
12
327
|
/**
|
|
13
328
|
* Constructs an A2AClient instance from an AgentCard.
|
|
14
329
|
* @param agentCard The AgentCard object.
|
|
@@ -17,18 +332,22 @@ var A2AClient = class _A2AClient {
|
|
|
17
332
|
constructor(agentCard, options) {
|
|
18
333
|
this.customFetchImpl = options?.fetchImpl;
|
|
19
334
|
if (typeof agentCard === "string") {
|
|
20
|
-
console.warn(
|
|
335
|
+
console.warn(
|
|
336
|
+
"Warning: Constructing A2AClient with a URL is deprecated. Please use A2AClient.fromCardUrl() instead."
|
|
337
|
+
);
|
|
21
338
|
this.agentCardPromise = this._fetchAndCacheAgentCard(agentCard, options?.agentCardPath);
|
|
22
339
|
} else {
|
|
23
340
|
if (!agentCard.url) {
|
|
24
|
-
throw new Error(
|
|
341
|
+
throw new Error(
|
|
342
|
+
"Provided Agent Card does not contain a valid 'url' for the service endpoint."
|
|
343
|
+
);
|
|
25
344
|
}
|
|
26
345
|
this.serviceEndpointUrl = agentCard.url;
|
|
27
346
|
this.agentCardPromise = Promise.resolve(agentCard);
|
|
28
347
|
}
|
|
29
348
|
}
|
|
30
349
|
/**
|
|
31
|
-
* Dynamically resolves the fetch implementation to use for requests.
|
|
350
|
+
* Dynamically resolves the fetch implementation to use for requests.
|
|
32
351
|
* Prefers a custom implementation if provided, otherwise falls back to the global fetch.
|
|
33
352
|
* @returns The fetch implementation.
|
|
34
353
|
* @param args Arguments to pass to the fetch implementation.
|
|
@@ -54,7 +373,7 @@ var A2AClient = class _A2AClient {
|
|
|
54
373
|
static async fromCardUrl(agentCardUrl, options) {
|
|
55
374
|
const fetchImpl = options?.fetchImpl;
|
|
56
375
|
const requestInit = {
|
|
57
|
-
headers: {
|
|
376
|
+
headers: { Accept: "application/json" }
|
|
58
377
|
};
|
|
59
378
|
let response;
|
|
60
379
|
if (fetchImpl) {
|
|
@@ -67,76 +386,21 @@ var A2AClient = class _A2AClient {
|
|
|
67
386
|
);
|
|
68
387
|
}
|
|
69
388
|
if (!response.ok) {
|
|
70
|
-
throw new Error(
|
|
389
|
+
throw new Error(
|
|
390
|
+
`Failed to fetch Agent Card from ${agentCardUrl}: ${response.status} ${response.statusText}`
|
|
391
|
+
);
|
|
71
392
|
}
|
|
72
393
|
let agentCard;
|
|
73
394
|
try {
|
|
74
395
|
agentCard = await response.json();
|
|
75
396
|
} catch (error) {
|
|
76
397
|
console.error("Failed to parse Agent Card JSON:", error);
|
|
77
|
-
throw new Error(
|
|
398
|
+
throw new Error(
|
|
399
|
+
`Failed to parse Agent Card JSON from ${agentCardUrl}. Original error: ${error.message}`
|
|
400
|
+
);
|
|
78
401
|
}
|
|
79
402
|
return new _A2AClient(agentCard, options);
|
|
80
403
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Helper method to make a generic JSON-RPC POST request.
|
|
83
|
-
* @param method The RPC method name.
|
|
84
|
-
* @param params The parameters for the RPC method.
|
|
85
|
-
* @returns A Promise that resolves to the RPC response.
|
|
86
|
-
*/
|
|
87
|
-
async _postRpcRequest(method, params) {
|
|
88
|
-
const endpoint = await this._getServiceEndpoint();
|
|
89
|
-
const requestId = this.requestIdCounter++;
|
|
90
|
-
const rpcRequest = {
|
|
91
|
-
jsonrpc: "2.0",
|
|
92
|
-
method,
|
|
93
|
-
params,
|
|
94
|
-
// Cast because TParams structure varies per method
|
|
95
|
-
id: requestId
|
|
96
|
-
};
|
|
97
|
-
const httpResponse = await this._fetchRpc(endpoint, rpcRequest);
|
|
98
|
-
if (!httpResponse.ok) {
|
|
99
|
-
let errorBodyText = "(empty or non-JSON response)";
|
|
100
|
-
try {
|
|
101
|
-
errorBodyText = await httpResponse.text();
|
|
102
|
-
const errorJson = JSON.parse(errorBodyText);
|
|
103
|
-
if (errorJson.jsonrpc && errorJson.error) {
|
|
104
|
-
return errorJson;
|
|
105
|
-
} else if (!errorJson.jsonrpc && errorJson.error) {
|
|
106
|
-
throw new Error(`RPC error for ${method}: ${errorJson.error.message} (Code: ${errorJson.error.code}, HTTP Status: ${httpResponse.status}) Data: ${JSON.stringify(errorJson.error.data || {})}`);
|
|
107
|
-
} else if (!errorJson.jsonrpc) {
|
|
108
|
-
throw new Error(`HTTP error for ${method}! Status: ${httpResponse.status} ${httpResponse.statusText}. Response: ${errorBodyText}`);
|
|
109
|
-
}
|
|
110
|
-
} catch (e) {
|
|
111
|
-
if (e.message.startsWith("RPC error for") || e.message.startsWith("HTTP error for")) throw e;
|
|
112
|
-
throw new Error(`HTTP error for ${method}! Status: ${httpResponse.status} ${httpResponse.statusText}. Response: ${errorBodyText}`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
const rpcResponse = await httpResponse.json();
|
|
116
|
-
if (rpcResponse.id !== requestId) {
|
|
117
|
-
console.error(`CRITICAL: RPC response ID mismatch for method ${method}. Expected ${requestId}, got ${rpcResponse.id}. This may lead to incorrect response handling.`);
|
|
118
|
-
}
|
|
119
|
-
return rpcResponse;
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Internal helper method to fetch the RPC service endpoint.
|
|
123
|
-
* @param url The URL to fetch.
|
|
124
|
-
* @param rpcRequest The JSON-RPC request to send.
|
|
125
|
-
* @param acceptHeader The Accept header to use. Defaults to "application/json".
|
|
126
|
-
* @returns A Promise that resolves to the fetch HTTP response.
|
|
127
|
-
*/
|
|
128
|
-
async _fetchRpc(url, rpcRequest, acceptHeader = "application/json") {
|
|
129
|
-
const requestInit = {
|
|
130
|
-
method: "POST",
|
|
131
|
-
headers: {
|
|
132
|
-
"Content-Type": "application/json",
|
|
133
|
-
"Accept": acceptHeader
|
|
134
|
-
// Expect JSON response for non-streaming requests
|
|
135
|
-
},
|
|
136
|
-
body: JSON.stringify(rpcRequest)
|
|
137
|
-
};
|
|
138
|
-
return this._fetch(url, requestInit);
|
|
139
|
-
}
|
|
140
404
|
/**
|
|
141
405
|
* Sends a message to the agent.
|
|
142
406
|
* The behavior (blocking/non-blocking) and push notification configuration
|
|
@@ -146,7 +410,10 @@ var A2AClient = class _A2AClient {
|
|
|
146
410
|
* @returns A Promise resolving to SendMessageResponse, which can be a Message, Task, or an error.
|
|
147
411
|
*/
|
|
148
412
|
async sendMessage(params) {
|
|
149
|
-
return this.
|
|
413
|
+
return await this.invokeJsonRpc(
|
|
414
|
+
(t, p, id) => t.sendMessage(p, _A2AClient.emptyOptions, id),
|
|
415
|
+
params
|
|
416
|
+
);
|
|
150
417
|
}
|
|
151
418
|
/**
|
|
152
419
|
* Sends a message to the agent and streams back responses using Server-Sent Events (SSE).
|
|
@@ -160,36 +427,12 @@ var A2AClient = class _A2AClient {
|
|
|
160
427
|
async *sendMessageStream(params) {
|
|
161
428
|
const agentCard = await this.agentCardPromise;
|
|
162
429
|
if (!agentCard.capabilities?.streaming) {
|
|
163
|
-
throw new Error(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const clientRequestId = this.requestIdCounter++;
|
|
167
|
-
const rpcRequest = {
|
|
168
|
-
// This is the initial JSON-RPC request to establish the stream
|
|
169
|
-
jsonrpc: "2.0",
|
|
170
|
-
method: "message/stream",
|
|
171
|
-
params,
|
|
172
|
-
id: clientRequestId
|
|
173
|
-
};
|
|
174
|
-
const response = await this._fetchRpc(endpoint, rpcRequest, "text/event-stream");
|
|
175
|
-
if (!response.ok) {
|
|
176
|
-
let errorBody = "";
|
|
177
|
-
try {
|
|
178
|
-
errorBody = await response.text();
|
|
179
|
-
const errorJson = JSON.parse(errorBody);
|
|
180
|
-
if (errorJson.error) {
|
|
181
|
-
throw new Error(`HTTP error establishing stream for message/stream: ${response.status} ${response.statusText}. RPC Error: ${errorJson.error.message} (Code: ${errorJson.error.code})`);
|
|
182
|
-
}
|
|
183
|
-
} catch (e) {
|
|
184
|
-
if (e.message.startsWith("HTTP error establishing stream")) throw e;
|
|
185
|
-
throw new Error(`HTTP error establishing stream for message/stream: ${response.status} ${response.statusText}. Response: ${errorBody || "(empty)"}`);
|
|
186
|
-
}
|
|
187
|
-
throw new Error(`HTTP error establishing stream for message/stream: ${response.status} ${response.statusText}`);
|
|
188
|
-
}
|
|
189
|
-
if (!response.headers.get("Content-Type")?.startsWith("text/event-stream")) {
|
|
190
|
-
throw new Error("Invalid response Content-Type for SSE stream. Expected 'text/event-stream'.");
|
|
430
|
+
throw new Error(
|
|
431
|
+
"Agent does not support streaming (AgentCard.capabilities.streaming is not true)."
|
|
432
|
+
);
|
|
191
433
|
}
|
|
192
|
-
|
|
434
|
+
const transport = await this._getOrCreateTransport();
|
|
435
|
+
yield* transport.sendMessageStream(params);
|
|
193
436
|
}
|
|
194
437
|
/**
|
|
195
438
|
* Sets or updates the push notification configuration for a given task.
|
|
@@ -200,12 +443,11 @@ var A2AClient = class _A2AClient {
|
|
|
200
443
|
async setTaskPushNotificationConfig(params) {
|
|
201
444
|
const agentCard = await this.agentCardPromise;
|
|
202
445
|
if (!agentCard.capabilities?.pushNotifications) {
|
|
203
|
-
throw new Error(
|
|
446
|
+
throw new Error(
|
|
447
|
+
"Agent does not support push notifications (AgentCard.capabilities.pushNotifications is not true)."
|
|
448
|
+
);
|
|
204
449
|
}
|
|
205
|
-
return this.
|
|
206
|
-
"tasks/pushNotificationConfig/set",
|
|
207
|
-
params
|
|
208
|
-
);
|
|
450
|
+
return await this.invokeJsonRpc((t, p, id) => t.setTaskPushNotificationConfig(p, _A2AClient.emptyOptions, id), params);
|
|
209
451
|
}
|
|
210
452
|
/**
|
|
211
453
|
* Gets the push notification configuration for a given task.
|
|
@@ -213,8 +455,8 @@ var A2AClient = class _A2AClient {
|
|
|
213
455
|
* @returns A Promise resolving to GetTaskPushNotificationConfigResponse.
|
|
214
456
|
*/
|
|
215
457
|
async getTaskPushNotificationConfig(params) {
|
|
216
|
-
return this.
|
|
217
|
-
|
|
458
|
+
return await this.invokeJsonRpc(
|
|
459
|
+
(t, p, id) => t.getTaskPushNotificationConfig(p, _A2AClient.emptyOptions, id),
|
|
218
460
|
params
|
|
219
461
|
);
|
|
220
462
|
}
|
|
@@ -224,10 +466,7 @@ var A2AClient = class _A2AClient {
|
|
|
224
466
|
* @returns A Promise resolving to ListTaskPushNotificationConfigResponse.
|
|
225
467
|
*/
|
|
226
468
|
async listTaskPushNotificationConfig(params) {
|
|
227
|
-
return this.
|
|
228
|
-
"tasks/pushNotificationConfig/list",
|
|
229
|
-
params
|
|
230
|
-
);
|
|
469
|
+
return await this.invokeJsonRpc((t, p, id) => t.listTaskPushNotificationConfig(p, _A2AClient.emptyOptions, id), params);
|
|
231
470
|
}
|
|
232
471
|
/**
|
|
233
472
|
* Deletes the push notification configuration for a given task.
|
|
@@ -235,10 +474,7 @@ var A2AClient = class _A2AClient {
|
|
|
235
474
|
* @returns A Promise resolving to DeleteTaskPushNotificationConfigResponse.
|
|
236
475
|
*/
|
|
237
476
|
async deleteTaskPushNotificationConfig(params) {
|
|
238
|
-
return this.
|
|
239
|
-
"tasks/pushNotificationConfig/delete",
|
|
240
|
-
params
|
|
241
|
-
);
|
|
477
|
+
return await this.invokeJsonRpc((t, p, id) => t.deleteTaskPushNotificationConfig(p, _A2AClient.emptyOptions, id), params);
|
|
242
478
|
}
|
|
243
479
|
/**
|
|
244
480
|
* Retrieves a task by its ID.
|
|
@@ -246,7 +482,10 @@ var A2AClient = class _A2AClient {
|
|
|
246
482
|
* @returns A Promise resolving to GetTaskResponse, which contains the Task object or an error.
|
|
247
483
|
*/
|
|
248
484
|
async getTask(params) {
|
|
249
|
-
return this.
|
|
485
|
+
return await this.invokeJsonRpc(
|
|
486
|
+
(t, p, id) => t.getTask(p, _A2AClient.emptyOptions, id),
|
|
487
|
+
params
|
|
488
|
+
);
|
|
250
489
|
}
|
|
251
490
|
/**
|
|
252
491
|
* Cancels a task by its ID.
|
|
@@ -254,18 +493,34 @@ var A2AClient = class _A2AClient {
|
|
|
254
493
|
* @returns A Promise resolving to CancelTaskResponse, which contains the updated Task object or an error.
|
|
255
494
|
*/
|
|
256
495
|
async cancelTask(params) {
|
|
257
|
-
return this.
|
|
496
|
+
return await this.invokeJsonRpc(
|
|
497
|
+
(t, p, id) => t.cancelTask(p, _A2AClient.emptyOptions, id),
|
|
498
|
+
params
|
|
499
|
+
);
|
|
258
500
|
}
|
|
259
501
|
/**
|
|
260
502
|
* @template TExtensionParams The type of parameters for the custom extension method.
|
|
261
|
-
* @template TExtensionResponse The type of response expected from the custom extension method.
|
|
503
|
+
* @template TExtensionResponse The type of response expected from the custom extension method.
|
|
262
504
|
* This should extend JSONRPCResponse. This ensures the extension response is still a valid A2A response.
|
|
263
505
|
* @param method Custom JSON-RPC method defined in the AgentCard's extensions.
|
|
264
506
|
* @param params Extension paramters defined in the AgentCard's extensions.
|
|
265
507
|
* @returns A Promise that resolves to the RPC response.
|
|
266
508
|
*/
|
|
267
509
|
async callExtensionMethod(method, params) {
|
|
268
|
-
|
|
510
|
+
const transport = await this._getOrCreateTransport();
|
|
511
|
+
try {
|
|
512
|
+
return await transport.callExtensionMethod(
|
|
513
|
+
method,
|
|
514
|
+
params,
|
|
515
|
+
this.requestIdCounter++
|
|
516
|
+
);
|
|
517
|
+
} catch (e) {
|
|
518
|
+
const errorResponse = extractJSONRPCError(e);
|
|
519
|
+
if (errorResponse) {
|
|
520
|
+
return errorResponse;
|
|
521
|
+
}
|
|
522
|
+
throw e;
|
|
523
|
+
}
|
|
269
524
|
}
|
|
270
525
|
/**
|
|
271
526
|
* Resubscribes to a task's event stream using Server-Sent Events (SSE).
|
|
@@ -279,129 +534,8 @@ var A2AClient = class _A2AClient {
|
|
|
279
534
|
if (!agentCard.capabilities?.streaming) {
|
|
280
535
|
throw new Error("Agent does not support streaming (required for tasks/resubscribe).");
|
|
281
536
|
}
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
const rpcRequest = {
|
|
285
|
-
// Initial JSON-RPC request to establish the stream
|
|
286
|
-
jsonrpc: "2.0",
|
|
287
|
-
method: "tasks/resubscribe",
|
|
288
|
-
params,
|
|
289
|
-
id: clientRequestId
|
|
290
|
-
};
|
|
291
|
-
const response = await this._fetch(endpoint, {
|
|
292
|
-
method: "POST",
|
|
293
|
-
headers: {
|
|
294
|
-
"Content-Type": "application/json",
|
|
295
|
-
"Accept": "text/event-stream"
|
|
296
|
-
},
|
|
297
|
-
body: JSON.stringify(rpcRequest)
|
|
298
|
-
});
|
|
299
|
-
if (!response.ok) {
|
|
300
|
-
let errorBody = "";
|
|
301
|
-
try {
|
|
302
|
-
errorBody = await response.text();
|
|
303
|
-
const errorJson = JSON.parse(errorBody);
|
|
304
|
-
if (errorJson.error) {
|
|
305
|
-
throw new Error(`HTTP error establishing stream for tasks/resubscribe: ${response.status} ${response.statusText}. RPC Error: ${errorJson.error.message} (Code: ${errorJson.error.code})`);
|
|
306
|
-
}
|
|
307
|
-
} catch (e) {
|
|
308
|
-
if (e.message.startsWith("HTTP error establishing stream")) throw e;
|
|
309
|
-
throw new Error(`HTTP error establishing stream for tasks/resubscribe: ${response.status} ${response.statusText}. Response: ${errorBody || "(empty)"}`);
|
|
310
|
-
}
|
|
311
|
-
throw new Error(`HTTP error establishing stream for tasks/resubscribe: ${response.status} ${response.statusText}`);
|
|
312
|
-
}
|
|
313
|
-
if (!response.headers.get("Content-Type")?.startsWith("text/event-stream")) {
|
|
314
|
-
throw new Error("Invalid response Content-Type for SSE stream on resubscribe. Expected 'text/event-stream'.");
|
|
315
|
-
}
|
|
316
|
-
yield* this._parseA2ASseStream(response, clientRequestId);
|
|
317
|
-
}
|
|
318
|
-
/**
|
|
319
|
-
* Parses an HTTP response body as an A2A Server-Sent Event stream.
|
|
320
|
-
* Each 'data' field of an SSE event is expected to be a JSON-RPC 2.0 Response object,
|
|
321
|
-
* specifically a SendStreamingMessageResponse (or similar structure for resubscribe).
|
|
322
|
-
* @param response The HTTP Response object whose body is the SSE stream.
|
|
323
|
-
* @param originalRequestId The ID of the client's JSON-RPC request that initiated this stream.
|
|
324
|
-
* Used to validate the `id` in the streamed JSON-RPC responses.
|
|
325
|
-
* @returns An AsyncGenerator yielding the `result` field of each valid JSON-RPC success response from the stream.
|
|
326
|
-
*/
|
|
327
|
-
async *_parseA2ASseStream(response, originalRequestId) {
|
|
328
|
-
if (!response.body) {
|
|
329
|
-
throw new Error("SSE response body is undefined. Cannot read stream.");
|
|
330
|
-
}
|
|
331
|
-
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
332
|
-
let buffer = "";
|
|
333
|
-
let eventDataBuffer = "";
|
|
334
|
-
try {
|
|
335
|
-
while (true) {
|
|
336
|
-
const { done, value } = await reader.read();
|
|
337
|
-
if (done) {
|
|
338
|
-
if (eventDataBuffer.trim()) {
|
|
339
|
-
const result = this._processSseEventData(eventDataBuffer, originalRequestId);
|
|
340
|
-
yield result;
|
|
341
|
-
}
|
|
342
|
-
break;
|
|
343
|
-
}
|
|
344
|
-
buffer += value;
|
|
345
|
-
let lineEndIndex;
|
|
346
|
-
while ((lineEndIndex = buffer.indexOf("\n")) >= 0) {
|
|
347
|
-
const line = buffer.substring(0, lineEndIndex).trim();
|
|
348
|
-
buffer = buffer.substring(lineEndIndex + 1);
|
|
349
|
-
if (line === "") {
|
|
350
|
-
if (eventDataBuffer) {
|
|
351
|
-
const result = this._processSseEventData(eventDataBuffer, originalRequestId);
|
|
352
|
-
yield result;
|
|
353
|
-
eventDataBuffer = "";
|
|
354
|
-
}
|
|
355
|
-
} else if (line.startsWith("data:")) {
|
|
356
|
-
eventDataBuffer += line.substring(5).trimStart() + "\n";
|
|
357
|
-
} else if (line.startsWith(":")) {
|
|
358
|
-
} else if (line.includes(":")) {
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
} catch (error) {
|
|
363
|
-
console.error("Error reading or parsing SSE stream:", error.message);
|
|
364
|
-
throw error;
|
|
365
|
-
} finally {
|
|
366
|
-
reader.releaseLock();
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
/**
|
|
370
|
-
* Processes a single SSE event's data string, expecting it to be a JSON-RPC response.
|
|
371
|
-
* @param jsonData The string content from one or more 'data:' lines of an SSE event.
|
|
372
|
-
* @param originalRequestId The ID of the client's request that initiated the stream.
|
|
373
|
-
* @returns The `result` field of the parsed JSON-RPC success response.
|
|
374
|
-
* @throws Error if data is not valid JSON, not a valid JSON-RPC response, an error response, or ID mismatch.
|
|
375
|
-
*/
|
|
376
|
-
_processSseEventData(jsonData, originalRequestId) {
|
|
377
|
-
if (!jsonData.trim()) {
|
|
378
|
-
throw new Error("Attempted to process empty SSE event data.");
|
|
379
|
-
}
|
|
380
|
-
try {
|
|
381
|
-
const sseJsonRpcResponse = JSON.parse(jsonData.replace(/\n$/, ""));
|
|
382
|
-
const a2aStreamResponse = sseJsonRpcResponse;
|
|
383
|
-
if (a2aStreamResponse.id !== originalRequestId) {
|
|
384
|
-
console.warn(`SSE Event's JSON-RPC response ID mismatch. Client request ID: ${originalRequestId}, event response ID: ${a2aStreamResponse.id}.`);
|
|
385
|
-
}
|
|
386
|
-
if (this.isErrorResponse(a2aStreamResponse)) {
|
|
387
|
-
const err = a2aStreamResponse.error;
|
|
388
|
-
throw new Error(`SSE event contained an error: ${err.message} (Code: ${err.code}) Data: ${JSON.stringify(err.data || {})}`);
|
|
389
|
-
}
|
|
390
|
-
if (!("result" in a2aStreamResponse) || typeof a2aStreamResponse.result === "undefined") {
|
|
391
|
-
throw new Error(`SSE event JSON-RPC response is missing 'result' field. Data: ${jsonData}`);
|
|
392
|
-
}
|
|
393
|
-
const successResponse = a2aStreamResponse;
|
|
394
|
-
return successResponse.result;
|
|
395
|
-
} catch (e) {
|
|
396
|
-
if (e.message.startsWith("SSE event contained an error") || e.message.startsWith("SSE event JSON-RPC response is missing 'result' field")) {
|
|
397
|
-
throw e;
|
|
398
|
-
}
|
|
399
|
-
console.error("Failed to parse SSE event data string or unexpected JSON-RPC structure:", jsonData, e);
|
|
400
|
-
throw new Error(`Failed to parse SSE event data: "${jsonData.substring(0, 100)}...". Original error: ${e.message}`);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
isErrorResponse(response) {
|
|
404
|
-
return "error" in response;
|
|
537
|
+
const transport = await this._getOrCreateTransport();
|
|
538
|
+
yield* transport.resubscribeTask(params);
|
|
405
539
|
}
|
|
406
540
|
////////////////////////////////////////////////////////////////////////////////
|
|
407
541
|
// Functions used to support old A2AClient Constructor to be deprecated soon
|
|
@@ -412,7 +546,16 @@ var A2AClient = class _A2AClient {
|
|
|
412
546
|
// * getAgentCard changed to this.agentCard
|
|
413
547
|
// * delete resolveAgentCardUrl(), _fetchAndCacheAgentCard(),
|
|
414
548
|
// agentCardPath from A2AClientOptions
|
|
549
|
+
// * delete _getOrCreateTransport
|
|
415
550
|
////////////////////////////////////////////////////////////////////////////////
|
|
551
|
+
async _getOrCreateTransport() {
|
|
552
|
+
if (this.transport) {
|
|
553
|
+
return this.transport;
|
|
554
|
+
}
|
|
555
|
+
const endpoint = await this._getServiceEndpoint();
|
|
556
|
+
this.transport = new JsonRpcTransport({ fetchImpl: this.customFetchImpl, endpoint });
|
|
557
|
+
return this.transport;
|
|
558
|
+
}
|
|
416
559
|
/**
|
|
417
560
|
* Fetches the Agent Card from the agent's well-known URI and caches its service endpoint URL.
|
|
418
561
|
* This method is called by the constructor.
|
|
@@ -424,14 +567,18 @@ var A2AClient = class _A2AClient {
|
|
|
424
567
|
try {
|
|
425
568
|
const agentCardUrl = this.resolveAgentCardUrl(agentBaseUrl, agentCardPath);
|
|
426
569
|
const response = await this._fetch(agentCardUrl, {
|
|
427
|
-
headers: {
|
|
570
|
+
headers: { Accept: "application/json" }
|
|
428
571
|
});
|
|
429
572
|
if (!response.ok) {
|
|
430
|
-
throw new Error(
|
|
573
|
+
throw new Error(
|
|
574
|
+
`Failed to fetch Agent Card from ${agentCardUrl}: ${response.status} ${response.statusText}`
|
|
575
|
+
);
|
|
431
576
|
}
|
|
432
577
|
const agentCard = await response.json();
|
|
433
578
|
if (!agentCard.url) {
|
|
434
|
-
throw new Error(
|
|
579
|
+
throw new Error(
|
|
580
|
+
"Fetched Agent Card does not contain a valid 'url' for the service endpoint."
|
|
581
|
+
);
|
|
435
582
|
}
|
|
436
583
|
this.serviceEndpointUrl = agentCard.url;
|
|
437
584
|
return agentCard;
|
|
@@ -441,22 +588,24 @@ var A2AClient = class _A2AClient {
|
|
|
441
588
|
}
|
|
442
589
|
}
|
|
443
590
|
/**
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
591
|
+
* Retrieves the Agent Card.
|
|
592
|
+
* If an `agentBaseUrl` is provided, it fetches the card from that specific URL.
|
|
593
|
+
* Otherwise, it returns the card fetched and cached during client construction.
|
|
594
|
+
* @param agentBaseUrl Optional. The base URL of the agent to fetch the card from.
|
|
595
|
+
* @param agentCardPath path to the agent card, defaults to .well-known/agent-card.json
|
|
596
|
+
* If provided, this will fetch a new card, not use the cached one from the constructor's URL.
|
|
597
|
+
* @returns A Promise that resolves to the AgentCard.
|
|
598
|
+
*/
|
|
452
599
|
async getAgentCard(agentBaseUrl, agentCardPath) {
|
|
453
600
|
if (agentBaseUrl) {
|
|
454
601
|
const agentCardUrl = this.resolveAgentCardUrl(agentBaseUrl, agentCardPath);
|
|
455
602
|
const response = await this._fetch(agentCardUrl, {
|
|
456
|
-
headers: {
|
|
603
|
+
headers: { Accept: "application/json" }
|
|
457
604
|
});
|
|
458
605
|
if (!response.ok) {
|
|
459
|
-
throw new Error(
|
|
606
|
+
throw new Error(
|
|
607
|
+
`Failed to fetch Agent Card from ${agentCardUrl}: ${response.status} ${response.statusText}`
|
|
608
|
+
);
|
|
460
609
|
}
|
|
461
610
|
return await response.json();
|
|
462
611
|
}
|
|
@@ -480,11 +629,39 @@ var A2AClient = class _A2AClient {
|
|
|
480
629
|
}
|
|
481
630
|
await this.agentCardPromise;
|
|
482
631
|
if (!this.serviceEndpointUrl) {
|
|
483
|
-
throw new Error(
|
|
632
|
+
throw new Error(
|
|
633
|
+
"Agent Card URL for RPC endpoint is not available. Fetching might have failed."
|
|
634
|
+
);
|
|
484
635
|
}
|
|
485
636
|
return this.serviceEndpointUrl;
|
|
486
637
|
}
|
|
638
|
+
async invokeJsonRpc(caller, params) {
|
|
639
|
+
const transport = await this._getOrCreateTransport();
|
|
640
|
+
const requestId = this.requestIdCounter++;
|
|
641
|
+
try {
|
|
642
|
+
const result = await caller(transport, params, requestId);
|
|
643
|
+
return {
|
|
644
|
+
id: requestId,
|
|
645
|
+
jsonrpc: "2.0",
|
|
646
|
+
result: result ?? null
|
|
647
|
+
// JSON-RPC requires result property on success, it will be null for "void" methods.
|
|
648
|
+
};
|
|
649
|
+
} catch (e) {
|
|
650
|
+
const errorResponse = extractJSONRPCError(e);
|
|
651
|
+
if (errorResponse) {
|
|
652
|
+
return errorResponse;
|
|
653
|
+
}
|
|
654
|
+
throw e;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
487
657
|
};
|
|
658
|
+
function extractJSONRPCError(error) {
|
|
659
|
+
if (error instanceof Object && "errorResponse" in error && error.errorResponse instanceof Object && "jsonrpc" in error.errorResponse && error.errorResponse.jsonrpc === "2.0" && "error" in error.errorResponse && error.errorResponse.error !== null) {
|
|
660
|
+
return error.errorResponse;
|
|
661
|
+
} else {
|
|
662
|
+
return void 0;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
488
665
|
|
|
489
666
|
// src/client/auth-handler.ts
|
|
490
667
|
function createAuthenticatingFetchWithRetry(fetchImpl, authHandler) {
|
|
@@ -518,7 +695,738 @@ function createAuthenticatingFetchWithRetry(fetchImpl, authHandler) {
|
|
|
518
695
|
Object.defineProperties(authFetch, Object.getOwnPropertyDescriptors(fetchImpl));
|
|
519
696
|
return authFetch;
|
|
520
697
|
}
|
|
698
|
+
|
|
699
|
+
// src/client/card-resolver.ts
|
|
700
|
+
var DefaultAgentCardResolver = class {
|
|
701
|
+
constructor(options) {
|
|
702
|
+
this.options = options;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Fetches the agent card based on provided base URL and path.
|
|
706
|
+
* Path is selected in the following order:
|
|
707
|
+
* 1) path parameter
|
|
708
|
+
* 2) path from options
|
|
709
|
+
* 3) .well-known/agent-card.json
|
|
710
|
+
*/
|
|
711
|
+
async resolve(baseUrl, path) {
|
|
712
|
+
const agentCardUrl = new URL(path ?? this.options?.path ?? AGENT_CARD_PATH, baseUrl);
|
|
713
|
+
const response = await this.fetchImpl(agentCardUrl);
|
|
714
|
+
if (!response.ok) {
|
|
715
|
+
throw new Error(`Failed to fetch Agent Card from ${agentCardUrl}: ${response.status}`);
|
|
716
|
+
}
|
|
717
|
+
return await response.json();
|
|
718
|
+
}
|
|
719
|
+
fetchImpl(...args) {
|
|
720
|
+
if (this.options?.fetchImpl) {
|
|
721
|
+
return this.options.fetchImpl(...args);
|
|
722
|
+
}
|
|
723
|
+
return fetch(...args);
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
var AgentCardResolver = {
|
|
727
|
+
default: new DefaultAgentCardResolver()
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
// src/client/multitransport-client.ts
|
|
731
|
+
var Client = class {
|
|
732
|
+
constructor(transport, agentCard, config) {
|
|
733
|
+
this.transport = transport;
|
|
734
|
+
this.agentCard = agentCard;
|
|
735
|
+
this.config = config;
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* If the current agent card supports the extended feature, it will try to fetch the extended agent card from the server,
|
|
739
|
+
* Otherwise it will return the current agent card value.
|
|
740
|
+
*/
|
|
741
|
+
async getAgentCard(options) {
|
|
742
|
+
if (this.agentCard.supportsAuthenticatedExtendedCard) {
|
|
743
|
+
this.agentCard = await this.executeWithInterceptors(
|
|
744
|
+
{ method: "getAgentCard" },
|
|
745
|
+
options,
|
|
746
|
+
(_, options2) => this.transport.getExtendedAgentCard(options2)
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
return this.agentCard;
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Sends a message to an agent to initiate a new interaction or to continue an existing one.
|
|
753
|
+
* Uses blocking mode by default.
|
|
754
|
+
*/
|
|
755
|
+
sendMessage(params, options) {
|
|
756
|
+
params = this.applyClientConfig({
|
|
757
|
+
params,
|
|
758
|
+
blocking: !(this.config?.polling ?? false)
|
|
759
|
+
});
|
|
760
|
+
return this.executeWithInterceptors(
|
|
761
|
+
{ method: "sendMessage", value: params },
|
|
762
|
+
options,
|
|
763
|
+
this.transport.sendMessage.bind(this.transport)
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Sends a message to an agent to initiate/continue a task AND subscribes the client to real-time updates for that task.
|
|
768
|
+
* Performs fallback to non-streaming if not supported by the agent.
|
|
769
|
+
*/
|
|
770
|
+
async *sendMessageStream(params, options) {
|
|
771
|
+
const method = "sendMessageStream";
|
|
772
|
+
params = this.applyClientConfig({ params, blocking: true });
|
|
773
|
+
const beforeArgs = {
|
|
774
|
+
input: { method, value: params },
|
|
775
|
+
agentCard: this.agentCard,
|
|
776
|
+
options
|
|
777
|
+
};
|
|
778
|
+
const beforeResult = await this.interceptBefore(beforeArgs);
|
|
779
|
+
if (beforeResult) {
|
|
780
|
+
const earlyReturn = beforeResult.earlyReturn.value;
|
|
781
|
+
const afterArgs = {
|
|
782
|
+
result: { method, value: earlyReturn },
|
|
783
|
+
agentCard: this.agentCard,
|
|
784
|
+
options: beforeArgs.options
|
|
785
|
+
};
|
|
786
|
+
await this.interceptAfter(afterArgs, beforeResult.executed);
|
|
787
|
+
yield afterArgs.result.value;
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
if (!this.agentCard.capabilities.streaming) {
|
|
791
|
+
const result = await this.transport.sendMessage(beforeArgs.input.value, beforeArgs.options);
|
|
792
|
+
const afterArgs = {
|
|
793
|
+
result: { method, value: result },
|
|
794
|
+
agentCard: this.agentCard,
|
|
795
|
+
options: beforeArgs.options
|
|
796
|
+
};
|
|
797
|
+
await this.interceptAfter(afterArgs);
|
|
798
|
+
yield afterArgs.result.value;
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
for await (const event of this.transport.sendMessageStream(
|
|
802
|
+
beforeArgs.input.value,
|
|
803
|
+
beforeArgs.options
|
|
804
|
+
)) {
|
|
805
|
+
const afterArgs = {
|
|
806
|
+
result: { method, value: event },
|
|
807
|
+
agentCard: this.agentCard,
|
|
808
|
+
options: beforeArgs.options
|
|
809
|
+
};
|
|
810
|
+
await this.interceptAfter(afterArgs);
|
|
811
|
+
yield afterArgs.result.value;
|
|
812
|
+
if (afterArgs.earlyReturn) {
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Sets or updates the push notification configuration for a specified task.
|
|
819
|
+
* Requires the server to have AgentCard.capabilities.pushNotifications: true.
|
|
820
|
+
*/
|
|
821
|
+
setTaskPushNotificationConfig(params, options) {
|
|
822
|
+
if (!this.agentCard.capabilities.pushNotifications) {
|
|
823
|
+
throw new PushNotificationNotSupportedError();
|
|
824
|
+
}
|
|
825
|
+
return this.executeWithInterceptors(
|
|
826
|
+
{ method: "setTaskPushNotificationConfig", value: params },
|
|
827
|
+
options,
|
|
828
|
+
this.transport.setTaskPushNotificationConfig.bind(this.transport)
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Retrieves the current push notification configuration for a specified task.
|
|
833
|
+
* Requires the server to have AgentCard.capabilities.pushNotifications: true.
|
|
834
|
+
*/
|
|
835
|
+
getTaskPushNotificationConfig(params, options) {
|
|
836
|
+
if (!this.agentCard.capabilities.pushNotifications) {
|
|
837
|
+
throw new PushNotificationNotSupportedError();
|
|
838
|
+
}
|
|
839
|
+
return this.executeWithInterceptors(
|
|
840
|
+
{ method: "getTaskPushNotificationConfig", value: params },
|
|
841
|
+
options,
|
|
842
|
+
this.transport.getTaskPushNotificationConfig.bind(this.transport)
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Retrieves the associated push notification configurations for a specified task.
|
|
847
|
+
* Requires the server to have AgentCard.capabilities.pushNotifications: true.
|
|
848
|
+
*/
|
|
849
|
+
listTaskPushNotificationConfig(params, options) {
|
|
850
|
+
if (!this.agentCard.capabilities.pushNotifications) {
|
|
851
|
+
throw new PushNotificationNotSupportedError();
|
|
852
|
+
}
|
|
853
|
+
return this.executeWithInterceptors(
|
|
854
|
+
{ method: "listTaskPushNotificationConfig", value: params },
|
|
855
|
+
options,
|
|
856
|
+
this.transport.listTaskPushNotificationConfig.bind(this.transport)
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Deletes an associated push notification configuration for a task.
|
|
861
|
+
*/
|
|
862
|
+
deleteTaskPushNotificationConfig(params, options) {
|
|
863
|
+
return this.executeWithInterceptors(
|
|
864
|
+
{ method: "deleteTaskPushNotificationConfig", value: params },
|
|
865
|
+
options,
|
|
866
|
+
this.transport.deleteTaskPushNotificationConfig.bind(this.transport)
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Retrieves the current state (including status, artifacts, and optionally history) of a previously initiated task.
|
|
871
|
+
*/
|
|
872
|
+
getTask(params, options) {
|
|
873
|
+
return this.executeWithInterceptors(
|
|
874
|
+
{ method: "getTask", value: params },
|
|
875
|
+
options,
|
|
876
|
+
this.transport.getTask.bind(this.transport)
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Requests the cancellation of an ongoing task. The server will attempt to cancel the task,
|
|
881
|
+
* but success is not guaranteed (e.g., the task might have already completed or failed, or cancellation might not be supported at its current stage).
|
|
882
|
+
*/
|
|
883
|
+
cancelTask(params, options) {
|
|
884
|
+
return this.executeWithInterceptors(
|
|
885
|
+
{ method: "cancelTask", value: params },
|
|
886
|
+
options,
|
|
887
|
+
this.transport.cancelTask.bind(this.transport)
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Allows a client to reconnect to an updates stream for an ongoing task after a previous connection was interrupted.
|
|
892
|
+
*/
|
|
893
|
+
async *resubscribeTask(params, options) {
|
|
894
|
+
const method = "resubscribeTask";
|
|
895
|
+
const beforeArgs = {
|
|
896
|
+
input: { method, value: params },
|
|
897
|
+
agentCard: this.agentCard,
|
|
898
|
+
options
|
|
899
|
+
};
|
|
900
|
+
const beforeResult = await this.interceptBefore(beforeArgs);
|
|
901
|
+
if (beforeResult) {
|
|
902
|
+
const earlyReturn = beforeResult.earlyReturn.value;
|
|
903
|
+
const afterArgs = {
|
|
904
|
+
result: { method, value: earlyReturn },
|
|
905
|
+
agentCard: this.agentCard,
|
|
906
|
+
options: beforeArgs.options
|
|
907
|
+
};
|
|
908
|
+
await this.interceptAfter(afterArgs, beforeResult.executed);
|
|
909
|
+
yield afterArgs.result.value;
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
for await (const event of this.transport.resubscribeTask(
|
|
913
|
+
beforeArgs.input.value,
|
|
914
|
+
beforeArgs.options
|
|
915
|
+
)) {
|
|
916
|
+
const afterArgs = {
|
|
917
|
+
result: { method, value: event },
|
|
918
|
+
agentCard: this.agentCard,
|
|
919
|
+
options: beforeArgs.options
|
|
920
|
+
};
|
|
921
|
+
await this.interceptAfter(afterArgs);
|
|
922
|
+
yield afterArgs.result.value;
|
|
923
|
+
if (afterArgs.earlyReturn) {
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
applyClientConfig({
|
|
929
|
+
params,
|
|
930
|
+
blocking
|
|
931
|
+
}) {
|
|
932
|
+
const result = { ...params, configuration: params.configuration ?? {} };
|
|
933
|
+
if (!result.configuration.acceptedOutputModes && this.config?.acceptedOutputModes) {
|
|
934
|
+
result.configuration.acceptedOutputModes = this.config.acceptedOutputModes;
|
|
935
|
+
}
|
|
936
|
+
if (!result.configuration.pushNotificationConfig && this.config?.pushNotificationConfig) {
|
|
937
|
+
result.configuration.pushNotificationConfig = this.config.pushNotificationConfig;
|
|
938
|
+
}
|
|
939
|
+
result.configuration.blocking ??= blocking;
|
|
940
|
+
return result;
|
|
941
|
+
}
|
|
942
|
+
async executeWithInterceptors(input, options, transportCall) {
|
|
943
|
+
const beforeArgs = {
|
|
944
|
+
input,
|
|
945
|
+
agentCard: this.agentCard,
|
|
946
|
+
options
|
|
947
|
+
};
|
|
948
|
+
const beforeResult = await this.interceptBefore(beforeArgs);
|
|
949
|
+
if (beforeResult) {
|
|
950
|
+
const afterArgs2 = {
|
|
951
|
+
result: {
|
|
952
|
+
method: input.method,
|
|
953
|
+
value: beforeResult.earlyReturn.value
|
|
954
|
+
},
|
|
955
|
+
agentCard: this.agentCard,
|
|
956
|
+
options: beforeArgs.options
|
|
957
|
+
};
|
|
958
|
+
await this.interceptAfter(afterArgs2, beforeResult.executed);
|
|
959
|
+
return afterArgs2.result.value;
|
|
960
|
+
}
|
|
961
|
+
const result = await transportCall(beforeArgs.input.value, beforeArgs.options);
|
|
962
|
+
const afterArgs = {
|
|
963
|
+
result: { method: input.method, value: result },
|
|
964
|
+
agentCard: this.agentCard,
|
|
965
|
+
options: beforeArgs.options
|
|
966
|
+
};
|
|
967
|
+
await this.interceptAfter(afterArgs);
|
|
968
|
+
return afterArgs.result.value;
|
|
969
|
+
}
|
|
970
|
+
async interceptBefore(args) {
|
|
971
|
+
if (!this.config?.interceptors || this.config.interceptors.length === 0) {
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
const executed = [];
|
|
975
|
+
for (const interceptor of this.config.interceptors) {
|
|
976
|
+
await interceptor.before(args);
|
|
977
|
+
executed.push(interceptor);
|
|
978
|
+
if (args.earlyReturn) {
|
|
979
|
+
return {
|
|
980
|
+
earlyReturn: args.earlyReturn,
|
|
981
|
+
executed
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
async interceptAfter(args, interceptors) {
|
|
987
|
+
const reversedInterceptors = [...interceptors ?? this.config?.interceptors ?? []].reverse();
|
|
988
|
+
for (const interceptor of reversedInterceptors) {
|
|
989
|
+
await interceptor.after(args);
|
|
990
|
+
if (args.earlyReturn) {
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
|
|
997
|
+
// src/client/transports/rest_transport.ts
|
|
998
|
+
var RestTransport = class _RestTransport {
|
|
999
|
+
customFetchImpl;
|
|
1000
|
+
endpoint;
|
|
1001
|
+
constructor(options) {
|
|
1002
|
+
this.endpoint = options.endpoint.replace(/\/+$/, "");
|
|
1003
|
+
this.customFetchImpl = options.fetchImpl;
|
|
1004
|
+
}
|
|
1005
|
+
async getExtendedAgentCard(options) {
|
|
1006
|
+
return this._sendRequest("GET", "/v1/card", void 0, options);
|
|
1007
|
+
}
|
|
1008
|
+
async sendMessage(params, options) {
|
|
1009
|
+
return this._sendRequest("POST", "/v1/message:send", params, options);
|
|
1010
|
+
}
|
|
1011
|
+
async *sendMessageStream(params, options) {
|
|
1012
|
+
yield* this._sendStreamingRequest("/v1/message:stream", params, options);
|
|
1013
|
+
}
|
|
1014
|
+
async setTaskPushNotificationConfig(params, options) {
|
|
1015
|
+
return this._sendRequest(
|
|
1016
|
+
"POST",
|
|
1017
|
+
`/v1/tasks/${encodeURIComponent(params.taskId)}/pushNotificationConfigs`,
|
|
1018
|
+
{
|
|
1019
|
+
pushNotificationConfig: params.pushNotificationConfig
|
|
1020
|
+
},
|
|
1021
|
+
options
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
1024
|
+
async getTaskPushNotificationConfig(params, options) {
|
|
1025
|
+
const { pushNotificationConfigId } = params;
|
|
1026
|
+
if (!pushNotificationConfigId) {
|
|
1027
|
+
throw new Error(
|
|
1028
|
+
"pushNotificationConfigId is required for getTaskPushNotificationConfig with REST transport."
|
|
1029
|
+
);
|
|
1030
|
+
}
|
|
1031
|
+
return this._sendRequest(
|
|
1032
|
+
"GET",
|
|
1033
|
+
`/v1/tasks/${encodeURIComponent(params.id)}/pushNotificationConfigs/${encodeURIComponent(pushNotificationConfigId)}`,
|
|
1034
|
+
void 0,
|
|
1035
|
+
options
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
async listTaskPushNotificationConfig(params, options) {
|
|
1039
|
+
return this._sendRequest(
|
|
1040
|
+
"GET",
|
|
1041
|
+
`/v1/tasks/${encodeURIComponent(params.id)}/pushNotificationConfigs`,
|
|
1042
|
+
void 0,
|
|
1043
|
+
options
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
async deleteTaskPushNotificationConfig(params, options) {
|
|
1047
|
+
await this._sendRequest(
|
|
1048
|
+
"DELETE",
|
|
1049
|
+
`/v1/tasks/${encodeURIComponent(params.id)}/pushNotificationConfigs/${encodeURIComponent(params.pushNotificationConfigId)}`,
|
|
1050
|
+
void 0,
|
|
1051
|
+
options
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
async getTask(params, options) {
|
|
1055
|
+
const queryParams = new URLSearchParams();
|
|
1056
|
+
if (params.historyLength !== void 0) {
|
|
1057
|
+
queryParams.set("historyLength", String(params.historyLength));
|
|
1058
|
+
}
|
|
1059
|
+
const queryString = queryParams.toString();
|
|
1060
|
+
const path = `/v1/tasks/${encodeURIComponent(params.id)}${queryString ? `?${queryString}` : ""}`;
|
|
1061
|
+
return this._sendRequest("GET", path, void 0, options);
|
|
1062
|
+
}
|
|
1063
|
+
async cancelTask(params, options) {
|
|
1064
|
+
return this._sendRequest(
|
|
1065
|
+
"POST",
|
|
1066
|
+
`/v1/tasks/${encodeURIComponent(params.id)}:cancel`,
|
|
1067
|
+
void 0,
|
|
1068
|
+
options
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
async *resubscribeTask(params, options) {
|
|
1072
|
+
yield* this._sendStreamingRequest(
|
|
1073
|
+
`/v1/tasks/${encodeURIComponent(params.id)}:subscribe`,
|
|
1074
|
+
void 0,
|
|
1075
|
+
options
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
_fetch(...args) {
|
|
1079
|
+
if (this.customFetchImpl) {
|
|
1080
|
+
return this.customFetchImpl(...args);
|
|
1081
|
+
}
|
|
1082
|
+
if (typeof fetch === "function") {
|
|
1083
|
+
return fetch(...args);
|
|
1084
|
+
}
|
|
1085
|
+
throw new Error(
|
|
1086
|
+
"A `fetch` implementation was not provided and is not available in the global scope. Please provide a `fetchImpl` in the RestTransportOptions."
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
_buildHeaders(options, acceptHeader = "application/json") {
|
|
1090
|
+
return {
|
|
1091
|
+
...options?.serviceParameters,
|
|
1092
|
+
"Content-Type": "application/json",
|
|
1093
|
+
Accept: acceptHeader
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
async _sendRequest(method, path, body, options) {
|
|
1097
|
+
const url = `${this.endpoint}${path}`;
|
|
1098
|
+
const requestInit = {
|
|
1099
|
+
method,
|
|
1100
|
+
headers: this._buildHeaders(options),
|
|
1101
|
+
signal: options?.signal
|
|
1102
|
+
};
|
|
1103
|
+
if (body !== void 0 && method !== "GET") {
|
|
1104
|
+
requestInit.body = JSON.stringify(body);
|
|
1105
|
+
}
|
|
1106
|
+
const response = await this._fetch(url, requestInit);
|
|
1107
|
+
if (!response.ok) {
|
|
1108
|
+
await this._handleErrorResponse(response, path);
|
|
1109
|
+
}
|
|
1110
|
+
if (response.status === 204) {
|
|
1111
|
+
return void 0;
|
|
1112
|
+
}
|
|
1113
|
+
const result = await response.json();
|
|
1114
|
+
return result;
|
|
1115
|
+
}
|
|
1116
|
+
async _handleErrorResponse(response, path) {
|
|
1117
|
+
let errorBodyText = "(empty or non-JSON response)";
|
|
1118
|
+
let errorBody;
|
|
1119
|
+
try {
|
|
1120
|
+
errorBodyText = await response.text();
|
|
1121
|
+
if (errorBodyText) {
|
|
1122
|
+
errorBody = JSON.parse(errorBodyText);
|
|
1123
|
+
}
|
|
1124
|
+
} catch (e) {
|
|
1125
|
+
throw new Error(
|
|
1126
|
+
`HTTP error for ${path}! Status: ${response.status} ${response.statusText}. Response: ${errorBodyText}`,
|
|
1127
|
+
{ cause: e }
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
if (errorBody && typeof errorBody.code === "number") {
|
|
1131
|
+
throw _RestTransport.mapToError(errorBody);
|
|
1132
|
+
}
|
|
1133
|
+
throw new Error(
|
|
1134
|
+
`HTTP error for ${path}! Status: ${response.status} ${response.statusText}. Response: ${errorBodyText}`
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
async *_sendStreamingRequest(path, body, options) {
|
|
1138
|
+
const url = `${this.endpoint}${path}`;
|
|
1139
|
+
const requestInit = {
|
|
1140
|
+
method: "POST",
|
|
1141
|
+
headers: this._buildHeaders(options, "text/event-stream"),
|
|
1142
|
+
signal: options?.signal
|
|
1143
|
+
};
|
|
1144
|
+
if (body !== void 0) {
|
|
1145
|
+
requestInit.body = JSON.stringify(body);
|
|
1146
|
+
}
|
|
1147
|
+
const response = await this._fetch(url, requestInit);
|
|
1148
|
+
if (!response.ok) {
|
|
1149
|
+
await this._handleErrorResponse(response, path);
|
|
1150
|
+
}
|
|
1151
|
+
const contentType = response.headers.get("Content-Type");
|
|
1152
|
+
if (!contentType?.startsWith("text/event-stream")) {
|
|
1153
|
+
throw new Error(
|
|
1154
|
+
`Invalid response Content-Type for SSE stream. Expected 'text/event-stream', got '${contentType}'.`
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
for await (const event of parseSseStream(response)) {
|
|
1158
|
+
if (event.type === "error") {
|
|
1159
|
+
const errorData = JSON.parse(event.data);
|
|
1160
|
+
throw _RestTransport.mapToError(errorData);
|
|
1161
|
+
}
|
|
1162
|
+
yield this._processSseEventData(event.data);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
_processSseEventData(jsonData) {
|
|
1166
|
+
if (!jsonData.trim()) {
|
|
1167
|
+
throw new Error("Attempted to process empty SSE event data.");
|
|
1168
|
+
}
|
|
1169
|
+
try {
|
|
1170
|
+
const data = JSON.parse(jsonData);
|
|
1171
|
+
return data;
|
|
1172
|
+
} catch (e) {
|
|
1173
|
+
console.error("Failed to parse SSE event data:", jsonData, e);
|
|
1174
|
+
throw new Error(
|
|
1175
|
+
`Failed to parse SSE event data: "${jsonData.substring(0, 100)}...". Original error: ${e instanceof Error && e.message || "Unknown error"}`
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
static mapToError(error) {
|
|
1180
|
+
switch (error.code) {
|
|
1181
|
+
case A2A_ERROR_CODE.TASK_NOT_FOUND:
|
|
1182
|
+
return new TaskNotFoundError(error.message);
|
|
1183
|
+
case A2A_ERROR_CODE.TASK_NOT_CANCELABLE:
|
|
1184
|
+
return new TaskNotCancelableError(error.message);
|
|
1185
|
+
case A2A_ERROR_CODE.PUSH_NOTIFICATION_NOT_SUPPORTED:
|
|
1186
|
+
return new PushNotificationNotSupportedError(error.message);
|
|
1187
|
+
case A2A_ERROR_CODE.UNSUPPORTED_OPERATION:
|
|
1188
|
+
return new UnsupportedOperationError(error.message);
|
|
1189
|
+
case A2A_ERROR_CODE.CONTENT_TYPE_NOT_SUPPORTED:
|
|
1190
|
+
return new ContentTypeNotSupportedError(error.message);
|
|
1191
|
+
case A2A_ERROR_CODE.INVALID_AGENT_RESPONSE:
|
|
1192
|
+
return new InvalidAgentResponseError(error.message);
|
|
1193
|
+
case A2A_ERROR_CODE.AUTHENTICATED_EXTENDED_CARD_NOT_CONFIGURED:
|
|
1194
|
+
return new AuthenticatedExtendedCardNotConfiguredError(error.message);
|
|
1195
|
+
default:
|
|
1196
|
+
return new Error(
|
|
1197
|
+
`REST error: ${error.message} (Code: ${error.code})${error.data ? ` Data: ${JSON.stringify(error.data)}` : ""}`
|
|
1198
|
+
);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
};
|
|
1202
|
+
var RestTransportFactory = class _RestTransportFactory {
|
|
1203
|
+
constructor(options) {
|
|
1204
|
+
this.options = options;
|
|
1205
|
+
}
|
|
1206
|
+
static name = "HTTP+JSON";
|
|
1207
|
+
get protocolName() {
|
|
1208
|
+
return _RestTransportFactory.name;
|
|
1209
|
+
}
|
|
1210
|
+
async create(url, _agentCard) {
|
|
1211
|
+
return new RestTransport({
|
|
1212
|
+
endpoint: url,
|
|
1213
|
+
fetchImpl: this.options?.fetchImpl
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
};
|
|
1217
|
+
|
|
1218
|
+
// src/client/factory.ts
|
|
1219
|
+
var ClientFactoryOptions = {
|
|
1220
|
+
/**
|
|
1221
|
+
* SDK default options for {@link ClientFactory}.
|
|
1222
|
+
*/
|
|
1223
|
+
default: {
|
|
1224
|
+
transports: [new JsonRpcTransportFactory(), new RestTransportFactory()]
|
|
1225
|
+
},
|
|
1226
|
+
/**
|
|
1227
|
+
* Creates new options by merging an original and an override object.
|
|
1228
|
+
* Transports are merged based on `TransportFactory.protocolName`,
|
|
1229
|
+
* interceptors are concatenated, other fields are overriden.
|
|
1230
|
+
*
|
|
1231
|
+
* @example
|
|
1232
|
+
* ```ts
|
|
1233
|
+
* const options = ClientFactoryOptions.createFrom(ClientFactoryOptions.default, {
|
|
1234
|
+
* transports: [new MyCustomTransportFactory()], // adds a custom transport
|
|
1235
|
+
* clientConfig: { interceptors: [new MyInterceptor()] }, // adds a custom interceptor
|
|
1236
|
+
* });
|
|
1237
|
+
* ```
|
|
1238
|
+
*/
|
|
1239
|
+
createFrom(original, overrides) {
|
|
1240
|
+
return {
|
|
1241
|
+
...original,
|
|
1242
|
+
...overrides,
|
|
1243
|
+
transports: mergeTransports(original.transports, overrides.transports),
|
|
1244
|
+
clientConfig: {
|
|
1245
|
+
...original.clientConfig ?? {},
|
|
1246
|
+
...overrides.clientConfig ?? {},
|
|
1247
|
+
interceptors: mergeArrays(
|
|
1248
|
+
original.clientConfig?.interceptors,
|
|
1249
|
+
overrides.clientConfig?.interceptors
|
|
1250
|
+
),
|
|
1251
|
+
acceptedOutputModes: overrides.clientConfig?.acceptedOutputModes ?? original.clientConfig?.acceptedOutputModes
|
|
1252
|
+
},
|
|
1253
|
+
preferredTransports: overrides.preferredTransports ?? original.preferredTransports
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
var ClientFactory = class {
|
|
1258
|
+
constructor(options = ClientFactoryOptions.default) {
|
|
1259
|
+
this.options = options;
|
|
1260
|
+
if (!options.transports || options.transports.length === 0) {
|
|
1261
|
+
throw new Error("No transports provided");
|
|
1262
|
+
}
|
|
1263
|
+
this.transportsByName = transportsByName(options.transports);
|
|
1264
|
+
for (const transport of options.preferredTransports ?? []) {
|
|
1265
|
+
const factory = this.options.transports.find((t) => t.protocolName === transport);
|
|
1266
|
+
if (!factory) {
|
|
1267
|
+
throw new Error(
|
|
1268
|
+
`Unknown preferred transport: ${transport}, available transports: ${[...this.transportsByName.keys()].join()}`
|
|
1269
|
+
);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
this.agentCardResolver = options.cardResolver ?? AgentCardResolver.default;
|
|
1273
|
+
}
|
|
1274
|
+
transportsByName;
|
|
1275
|
+
agentCardResolver;
|
|
1276
|
+
/**
|
|
1277
|
+
* Creates a new client from the provided agent card.
|
|
1278
|
+
*/
|
|
1279
|
+
async createFromAgentCard(agentCard) {
|
|
1280
|
+
const agentCardPreferred = agentCard.preferredTransport ?? JsonRpcTransportFactory.name;
|
|
1281
|
+
const additionalInterfaces = agentCard.additionalInterfaces ?? [];
|
|
1282
|
+
const urlsPerAgentTransports = new Map([
|
|
1283
|
+
[agentCardPreferred, agentCard.url],
|
|
1284
|
+
...additionalInterfaces.map((i) => [i.transport, i.url])
|
|
1285
|
+
]);
|
|
1286
|
+
const transportsByPreference = [
|
|
1287
|
+
...this.options.preferredTransports ?? [],
|
|
1288
|
+
agentCardPreferred,
|
|
1289
|
+
...additionalInterfaces.map((i) => i.transport)
|
|
1290
|
+
];
|
|
1291
|
+
for (const transport of transportsByPreference) {
|
|
1292
|
+
if (!urlsPerAgentTransports.has(transport)) {
|
|
1293
|
+
continue;
|
|
1294
|
+
}
|
|
1295
|
+
const factory = this.transportsByName.get(transport);
|
|
1296
|
+
if (!factory) {
|
|
1297
|
+
continue;
|
|
1298
|
+
}
|
|
1299
|
+
return new Client(
|
|
1300
|
+
await factory.create(urlsPerAgentTransports.get(transport), agentCard),
|
|
1301
|
+
agentCard,
|
|
1302
|
+
this.options.clientConfig
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
throw new Error(
|
|
1306
|
+
"No compatible transport found, available transports: " + [...this.transportsByName.keys()].join()
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1309
|
+
/**
|
|
1310
|
+
* Downloads agent card using AgentCardResolver from options
|
|
1311
|
+
* and creates a new client from the downloaded card.
|
|
1312
|
+
*
|
|
1313
|
+
* @example
|
|
1314
|
+
* ```ts
|
|
1315
|
+
* const factory = new ClientFactory(); // use default options and default {@link AgentCardResolver}.
|
|
1316
|
+
* const client1 = await factory.createFromUrl('https://example.com'); // /.well-known/agent-card.json is used by default
|
|
1317
|
+
* const client2 = await factory.createFromUrl('https://example.com', '/my-agent-card.json'); // specify custom path
|
|
1318
|
+
* const client3 = await factory.createFromUrl('https://example.com/my-agent-card.json', ''); // specify full URL and set path to empty
|
|
1319
|
+
* ```
|
|
1320
|
+
*/
|
|
1321
|
+
async createFromUrl(baseUrl, path) {
|
|
1322
|
+
const agentCard = await this.agentCardResolver.resolve(baseUrl, path);
|
|
1323
|
+
return this.createFromAgentCard(agentCard);
|
|
1324
|
+
}
|
|
1325
|
+
};
|
|
1326
|
+
function mergeTransports(original, overrides) {
|
|
1327
|
+
if (!overrides) {
|
|
1328
|
+
return original;
|
|
1329
|
+
}
|
|
1330
|
+
const result = transportsByName(original);
|
|
1331
|
+
const overridesByName = transportsByName(overrides);
|
|
1332
|
+
for (const [name, factory] of overridesByName) {
|
|
1333
|
+
result.set(name, factory);
|
|
1334
|
+
}
|
|
1335
|
+
return Array.from(result.values());
|
|
1336
|
+
}
|
|
1337
|
+
function transportsByName(transports) {
|
|
1338
|
+
const result = /* @__PURE__ */ new Map();
|
|
1339
|
+
if (!transports) {
|
|
1340
|
+
return result;
|
|
1341
|
+
}
|
|
1342
|
+
for (const t of transports) {
|
|
1343
|
+
if (result.has(t.protocolName)) {
|
|
1344
|
+
throw new Error(`Duplicate protocol name: ${t.protocolName}`);
|
|
1345
|
+
}
|
|
1346
|
+
result.set(t.protocolName, t);
|
|
1347
|
+
}
|
|
1348
|
+
return result;
|
|
1349
|
+
}
|
|
1350
|
+
function mergeArrays(a1, a2) {
|
|
1351
|
+
if (!a1 && !a2) {
|
|
1352
|
+
return void 0;
|
|
1353
|
+
}
|
|
1354
|
+
return [...a1 ?? [], ...a2 ?? []];
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
// src/client/service-parameters.ts
|
|
1358
|
+
var ServiceParameters = {
|
|
1359
|
+
create(...updates) {
|
|
1360
|
+
return ServiceParameters.createFrom(void 0, ...updates);
|
|
1361
|
+
},
|
|
1362
|
+
createFrom: (serviceParameters, ...updates) => {
|
|
1363
|
+
const result = serviceParameters ? { ...serviceParameters } : {};
|
|
1364
|
+
for (const update of updates) {
|
|
1365
|
+
update(result);
|
|
1366
|
+
}
|
|
1367
|
+
return result;
|
|
1368
|
+
}
|
|
1369
|
+
};
|
|
1370
|
+
function withA2AExtensions(...extensions) {
|
|
1371
|
+
return (parameters) => {
|
|
1372
|
+
parameters[HTTP_EXTENSION_HEADER] = Extensions.toServiceParameter(extensions);
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// src/client/context.ts
|
|
1377
|
+
var ClientCallContext = {
|
|
1378
|
+
/**
|
|
1379
|
+
* Create a new {@link ClientCallContext} with optional updates applied.
|
|
1380
|
+
*/
|
|
1381
|
+
create: (...updates) => {
|
|
1382
|
+
return ClientCallContext.createFrom(void 0, ...updates);
|
|
1383
|
+
},
|
|
1384
|
+
/**
|
|
1385
|
+
* Create a new {@link ClientCallContext} based on an existing one with updates applied.
|
|
1386
|
+
*/
|
|
1387
|
+
createFrom: (context, ...updates) => {
|
|
1388
|
+
const result = context ? { ...context } : {};
|
|
1389
|
+
for (const update of updates) {
|
|
1390
|
+
update(result);
|
|
1391
|
+
}
|
|
1392
|
+
return result;
|
|
1393
|
+
}
|
|
1394
|
+
};
|
|
1395
|
+
var ClientCallContextKey = class {
|
|
1396
|
+
symbol;
|
|
1397
|
+
constructor(description) {
|
|
1398
|
+
this.symbol = Symbol(description);
|
|
1399
|
+
}
|
|
1400
|
+
set(value) {
|
|
1401
|
+
return (context) => {
|
|
1402
|
+
context[this.symbol] = value;
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
get(context) {
|
|
1406
|
+
return context[this.symbol];
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
521
1409
|
export {
|
|
522
1410
|
A2AClient,
|
|
523
|
-
|
|
1411
|
+
AgentCardResolver,
|
|
1412
|
+
AuthenticatedExtendedCardNotConfiguredError,
|
|
1413
|
+
Client,
|
|
1414
|
+
ClientCallContext,
|
|
1415
|
+
ClientCallContextKey,
|
|
1416
|
+
ClientFactory,
|
|
1417
|
+
ClientFactoryOptions,
|
|
1418
|
+
ContentTypeNotSupportedError,
|
|
1419
|
+
DefaultAgentCardResolver,
|
|
1420
|
+
InvalidAgentResponseError,
|
|
1421
|
+
JsonRpcTransport,
|
|
1422
|
+
JsonRpcTransportFactory,
|
|
1423
|
+
PushNotificationNotSupportedError,
|
|
1424
|
+
RestTransport,
|
|
1425
|
+
RestTransportFactory,
|
|
1426
|
+
ServiceParameters,
|
|
1427
|
+
TaskNotCancelableError,
|
|
1428
|
+
TaskNotFoundError,
|
|
1429
|
+
UnsupportedOperationError,
|
|
1430
|
+
createAuthenticatingFetchWithRetry,
|
|
1431
|
+
withA2AExtensions
|
|
524
1432
|
};
|