@inkeep/agents-run-api 0.1.2 → 0.1.6
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/LICENSE.md +7 -0
- package/README.md +1 -1
- package/SUPPLEMENTAL_TERMS.md +40 -0
- package/dist/chunk-P6IQZWFC.js +239 -0
- package/dist/conversations-EUPRCMQZ.js +1 -0
- package/dist/index.cjs +8992 -0
- package/dist/index.d.cts +15 -0
- package/dist/index.d.ts +13 -21
- package/dist/index.js +8644 -27
- package/package.json +14 -11
- package/templates/v1/artifact.xml +7 -0
- package/templates/v1/data-component.xml +9 -0
- package/templates/v1/system-prompt.xml +52 -0
- package/templates/v1/thinking-preparation.xml +34 -0
- package/templates/v1/tool.xml +12 -0
- package/dist/AgentExecutionServer.d.ts +0 -28
- package/dist/AgentExecutionServer.d.ts.map +0 -1
- package/dist/AgentExecutionServer.js +0 -41
- package/dist/__tests__/setup.d.ts +0 -4
- package/dist/__tests__/setup.d.ts.map +0 -1
- package/dist/__tests__/setup.js +0 -50
- package/dist/__tests__/utils/testProject.d.ts +0 -18
- package/dist/__tests__/utils/testProject.d.ts.map +0 -1
- package/dist/__tests__/utils/testProject.js +0 -26
- package/dist/__tests__/utils/testRequest.d.ts +0 -8
- package/dist/__tests__/utils/testRequest.d.ts.map +0 -1
- package/dist/__tests__/utils/testRequest.js +0 -32
- package/dist/__tests__/utils/testTenant.d.ts +0 -64
- package/dist/__tests__/utils/testTenant.d.ts.map +0 -1
- package/dist/__tests__/utils/testTenant.js +0 -71
- package/dist/a2a/client.d.ts +0 -182
- package/dist/a2a/client.d.ts.map +0 -1
- package/dist/a2a/client.js +0 -645
- package/dist/a2a/handlers.d.ts +0 -4
- package/dist/a2a/handlers.d.ts.map +0 -1
- package/dist/a2a/handlers.js +0 -657
- package/dist/a2a/transfer.d.ts +0 -18
- package/dist/a2a/transfer.d.ts.map +0 -1
- package/dist/a2a/transfer.js +0 -22
- package/dist/a2a/types.d.ts +0 -63
- package/dist/a2a/types.d.ts.map +0 -1
- package/dist/a2a/types.js +0 -1
- package/dist/agents/Agent.d.ts +0 -154
- package/dist/agents/Agent.d.ts.map +0 -1
- package/dist/agents/Agent.js +0 -1107
- package/dist/agents/ModelFactory.d.ts +0 -62
- package/dist/agents/ModelFactory.d.ts.map +0 -1
- package/dist/agents/ModelFactory.js +0 -208
- package/dist/agents/SystemPromptBuilder.d.ts +0 -14
- package/dist/agents/SystemPromptBuilder.d.ts.map +0 -1
- package/dist/agents/SystemPromptBuilder.js +0 -62
- package/dist/agents/ToolSessionManager.d.ts +0 -61
- package/dist/agents/ToolSessionManager.d.ts.map +0 -1
- package/dist/agents/ToolSessionManager.js +0 -143
- package/dist/agents/artifactTools.d.ts +0 -30
- package/dist/agents/artifactTools.d.ts.map +0 -1
- package/dist/agents/artifactTools.js +0 -463
- package/dist/agents/generateTaskHandler.d.ts +0 -41
- package/dist/agents/generateTaskHandler.d.ts.map +0 -1
- package/dist/agents/generateTaskHandler.js +0 -350
- package/dist/agents/relationTools.d.ts +0 -35
- package/dist/agents/relationTools.d.ts.map +0 -1
- package/dist/agents/relationTools.js +0 -244
- package/dist/agents/types.d.ts +0 -23
- package/dist/agents/types.d.ts.map +0 -1
- package/dist/agents/types.js +0 -1
- package/dist/agents/versions/V1Config.d.ts +0 -21
- package/dist/agents/versions/V1Config.d.ts.map +0 -1
- package/dist/agents/versions/V1Config.js +0 -285
- package/dist/app.d.ts +0 -12
- package/dist/app.d.ts.map +0 -1
- package/dist/app.js +0 -202
- package/dist/data/agentGraph.d.ts +0 -4
- package/dist/data/agentGraph.d.ts.map +0 -1
- package/dist/data/agentGraph.js +0 -73
- package/dist/data/agents.d.ts +0 -4
- package/dist/data/agents.d.ts.map +0 -1
- package/dist/data/agents.js +0 -73
- package/dist/data/conversations.d.ts +0 -59
- package/dist/data/conversations.d.ts.map +0 -1
- package/dist/data/conversations.js +0 -216
- package/dist/data/db/clean.d.ts +0 -6
- package/dist/data/db/clean.d.ts.map +0 -1
- package/dist/data/db/clean.js +0 -77
- package/dist/data/db/dbClient.d.ts +0 -3
- package/dist/data/db/dbClient.d.ts.map +0 -1
- package/dist/data/db/dbClient.js +0 -13
- package/dist/env.d.ts +0 -45
- package/dist/env.d.ts.map +0 -1
- package/dist/env.js +0 -64
- package/dist/handlers/executionHandler.d.ts +0 -36
- package/dist/handlers/executionHandler.d.ts.map +0 -1
- package/dist/handlers/executionHandler.js +0 -399
- package/dist/index.d.ts.map +0 -1
- package/dist/instrumentation.d.ts +0 -13
- package/dist/instrumentation.d.ts.map +0 -1
- package/dist/instrumentation.js +0 -66
- package/dist/logger.d.ts +0 -4
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js +0 -32
- package/dist/middleware/api-key-auth.d.ts +0 -22
- package/dist/middleware/api-key-auth.d.ts.map +0 -1
- package/dist/middleware/api-key-auth.js +0 -139
- package/dist/middleware/index.d.ts +0 -2
- package/dist/middleware/index.d.ts.map +0 -1
- package/dist/middleware/index.js +0 -1
- package/dist/openapi.d.ts +0 -2
- package/dist/openapi.d.ts.map +0 -1
- package/dist/openapi.js +0 -36
- package/dist/routes/agents.d.ts +0 -10
- package/dist/routes/agents.d.ts.map +0 -1
- package/dist/routes/agents.js +0 -158
- package/dist/routes/chat.d.ts +0 -4
- package/dist/routes/chat.d.ts.map +0 -1
- package/dist/routes/chat.js +0 -308
- package/dist/routes/chatDataStream.d.ts +0 -4
- package/dist/routes/chatDataStream.d.ts.map +0 -1
- package/dist/routes/chatDataStream.js +0 -179
- package/dist/routes/mcp.d.ts +0 -4
- package/dist/routes/mcp.d.ts.map +0 -1
- package/dist/routes/mcp.js +0 -500
- package/dist/server.d.ts +0 -5
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js +0 -61
- package/dist/tracer.d.ts +0 -24
- package/dist/tracer.d.ts.map +0 -1
- package/dist/tracer.js +0 -106
- package/dist/types/chat.d.ts +0 -25
- package/dist/types/chat.d.ts.map +0 -1
- package/dist/types/chat.js +0 -1
- package/dist/types/execution-context.d.ts +0 -14
- package/dist/types/execution-context.d.ts.map +0 -1
- package/dist/types/execution-context.js +0 -14
- package/dist/utils/agent-operations.d.ts +0 -92
- package/dist/utils/agent-operations.d.ts.map +0 -1
- package/dist/utils/agent-operations.js +0 -78
- package/dist/utils/artifact-component-schema.d.ts +0 -29
- package/dist/utils/artifact-component-schema.d.ts.map +0 -1
- package/dist/utils/artifact-component-schema.js +0 -119
- package/dist/utils/artifact-parser.d.ts +0 -71
- package/dist/utils/artifact-parser.d.ts.map +0 -1
- package/dist/utils/artifact-parser.js +0 -251
- package/dist/utils/cleanup.d.ts +0 -19
- package/dist/utils/cleanup.d.ts.map +0 -1
- package/dist/utils/cleanup.js +0 -66
- package/dist/utils/data-component-schema.d.ts +0 -6
- package/dist/utils/data-component-schema.d.ts.map +0 -1
- package/dist/utils/data-component-schema.js +0 -43
- package/dist/utils/graph-session.d.ts +0 -200
- package/dist/utils/graph-session.d.ts.map +0 -1
- package/dist/utils/graph-session.js +0 -1012
- package/dist/utils/incremental-stream-parser.d.ts +0 -57
- package/dist/utils/incremental-stream-parser.d.ts.map +0 -1
- package/dist/utils/incremental-stream-parser.js +0 -287
- package/dist/utils/response-formatter.d.ts +0 -27
- package/dist/utils/response-formatter.d.ts.map +0 -1
- package/dist/utils/response-formatter.js +0 -160
- package/dist/utils/stream-helpers.d.ts +0 -174
- package/dist/utils/stream-helpers.d.ts.map +0 -1
- package/dist/utils/stream-helpers.js +0 -466
- package/dist/utils/stream-registry.d.ts +0 -18
- package/dist/utils/stream-registry.d.ts.map +0 -1
- package/dist/utils/stream-registry.js +0 -33
package/dist/a2a/client.js
DELETED
|
@@ -1,645 +0,0 @@
|
|
|
1
|
-
import { getLogger } from '../logger.js';
|
|
2
|
-
const logger = getLogger('a2aClient');
|
|
3
|
-
const DEFAULT_BACKOFF = {
|
|
4
|
-
initialInterval: 500,
|
|
5
|
-
maxInterval: 60000,
|
|
6
|
-
exponent: 1.5,
|
|
7
|
-
maxElapsedTime: 30000, // 30 seconds for A2A calls
|
|
8
|
-
};
|
|
9
|
-
const DEFAULT_RETRY_STATUS_CODES = ['429', '500', '502', '503', '504'];
|
|
10
|
-
/**
|
|
11
|
-
* Error classes for retry logic
|
|
12
|
-
*/
|
|
13
|
-
class PermanentError extends Error {
|
|
14
|
-
cause;
|
|
15
|
-
constructor(message, options) {
|
|
16
|
-
let msg = message;
|
|
17
|
-
if (options?.cause) {
|
|
18
|
-
msg += `: ${options.cause}`;
|
|
19
|
-
}
|
|
20
|
-
super(msg, options);
|
|
21
|
-
this.name = 'PermanentError';
|
|
22
|
-
if (typeof this.cause === 'undefined') {
|
|
23
|
-
this.cause = options?.cause;
|
|
24
|
-
}
|
|
25
|
-
Object.setPrototypeOf(this, PermanentError.prototype);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
class TemporaryError extends Error {
|
|
29
|
-
response;
|
|
30
|
-
constructor(message, response) {
|
|
31
|
-
super(message);
|
|
32
|
-
this.response = response;
|
|
33
|
-
this.name = 'TemporaryError';
|
|
34
|
-
Object.setPrototypeOf(this, TemporaryError.prototype);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* A2AClient is a TypeScript HTTP client for interacting with A2A-compliant agents.
|
|
39
|
-
*
|
|
40
|
-
* Features:
|
|
41
|
-
* - Configurable retry behavior with exponential backoff
|
|
42
|
-
* - Automatic retry on network errors and configurable HTTP status codes
|
|
43
|
-
* - Support for custom retry strategies and timeouts
|
|
44
|
-
*
|
|
45
|
-
* Default retry behavior:
|
|
46
|
-
* - Retries on: 429, 500, 502, 503, 504 status codes and network errors
|
|
47
|
-
* - Initial delay: 500ms, max delay: 60s, exponential backoff (1.5x)
|
|
48
|
-
* - Max total retry time: 30 seconds
|
|
49
|
-
*
|
|
50
|
-
* @example
|
|
51
|
-
* // Default retry behavior
|
|
52
|
-
* const client = new A2AClient('https://agent.example.com');
|
|
53
|
-
*
|
|
54
|
-
* // Custom retry configuration
|
|
55
|
-
* const client = new A2AClient('https://agent.example.com', {
|
|
56
|
-
* retryConfig: {
|
|
57
|
-
* strategy: 'backoff',
|
|
58
|
-
* retryConnectionErrors: true,
|
|
59
|
-
* statusCodes: ['429', '502', '503', '504'],
|
|
60
|
-
* backoff: {
|
|
61
|
-
* initialInterval: 1000,
|
|
62
|
-
* maxInterval: 30000,
|
|
63
|
-
* exponent: 2,
|
|
64
|
-
* maxElapsedTime: 60000
|
|
65
|
-
* }
|
|
66
|
-
* }
|
|
67
|
-
* });
|
|
68
|
-
*
|
|
69
|
-
* // Disable retries
|
|
70
|
-
* const client = new A2AClient('https://agent.example.com', {
|
|
71
|
-
* retryConfig: { strategy: 'none' }
|
|
72
|
-
* });
|
|
73
|
-
*/
|
|
74
|
-
export class A2AClient {
|
|
75
|
-
agentBaseUrl;
|
|
76
|
-
agentCardPromise;
|
|
77
|
-
requestIdCounter = 1;
|
|
78
|
-
serviceEndpointUrl; // To be populated from AgentCard after fetching
|
|
79
|
-
options;
|
|
80
|
-
/**
|
|
81
|
-
* Constructs an A2AClient instance.
|
|
82
|
-
* It initiates fetching the agent card from the provided agent baseUrl.
|
|
83
|
-
* The Agent Card is expected at `${agentBaseUrl}/.well-known/agent.json`.
|
|
84
|
-
* The `url` field from the Agent Card will be used as the RPC service endpoint.
|
|
85
|
-
* @param agentBaseUrl The base URL of the A2A agent (e.g., https://agent.example.com).
|
|
86
|
-
* @param options Optional configuration including retry behavior.
|
|
87
|
-
*/
|
|
88
|
-
constructor(agentBaseUrl, options) {
|
|
89
|
-
this.agentBaseUrl = agentBaseUrl.replace(/\/$/, ''); // Remove trailing slash if any
|
|
90
|
-
this.options = {
|
|
91
|
-
retryConfig: {
|
|
92
|
-
strategy: 'backoff',
|
|
93
|
-
backoff: DEFAULT_BACKOFF,
|
|
94
|
-
retryConnectionErrors: true,
|
|
95
|
-
statusCodes: DEFAULT_RETRY_STATUS_CODES,
|
|
96
|
-
},
|
|
97
|
-
...options,
|
|
98
|
-
};
|
|
99
|
-
this.agentCardPromise = this._fetchAndCacheAgentCard();
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Fetches the Agent Card from the agent's well-known URI and caches its service endpoint URL.
|
|
103
|
-
* This method is called by the constructor.
|
|
104
|
-
* @returns A Promise that resolves to the AgentCard.
|
|
105
|
-
*/
|
|
106
|
-
async _fetchAndCacheAgentCard() {
|
|
107
|
-
const agentCardUrl = `${this.agentBaseUrl}/.well-known/agent.json`;
|
|
108
|
-
getLogger('a2a').info({ agentCardUrl, agentBaseUrl: this.agentBaseUrl }, 'agentCardUrl');
|
|
109
|
-
try {
|
|
110
|
-
const response = await fetch(agentCardUrl, {
|
|
111
|
-
headers: {
|
|
112
|
-
Accept: 'application/json',
|
|
113
|
-
...(this.options.headers || {}),
|
|
114
|
-
},
|
|
115
|
-
});
|
|
116
|
-
if (!response.ok) {
|
|
117
|
-
throw new Error(`Failed to fetch Agent Card from ${agentCardUrl}: ${response.status} ${response.statusText}`);
|
|
118
|
-
}
|
|
119
|
-
const agentCard = await response.json();
|
|
120
|
-
if (!agentCard.url) {
|
|
121
|
-
throw new Error("Fetched Agent Card does not contain a valid 'url' for the service endpoint.");
|
|
122
|
-
}
|
|
123
|
-
this.serviceEndpointUrl = agentCard.url; // Cache the service endpoint URL from the agent card
|
|
124
|
-
return agentCard;
|
|
125
|
-
}
|
|
126
|
-
catch (error) {
|
|
127
|
-
console.error('Error fetching or parsing Agent Card:');
|
|
128
|
-
// Allow the promise to reject so users of agentCardPromise can handle it.
|
|
129
|
-
throw error;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Retrieves the Agent Card.
|
|
134
|
-
* If an `agentBaseUrl` is provided, it fetches the card from that specific URL.
|
|
135
|
-
* Otherwise, it returns the card fetched and cached during client construction.
|
|
136
|
-
* @param agentBaseUrl Optional. The base URL of the agent to fetch the card from.
|
|
137
|
-
* If provided, this will fetch a new card, not use the cached one from the constructor's URL.
|
|
138
|
-
* @returns A Promise that resolves to the AgentCard.
|
|
139
|
-
*/
|
|
140
|
-
async getAgentCard(agentBaseUrl) {
|
|
141
|
-
if (agentBaseUrl) {
|
|
142
|
-
const specificAgentBaseUrl = agentBaseUrl.replace(/\/$/, '');
|
|
143
|
-
const agentCardUrl = `${specificAgentBaseUrl}/.well-known/agent.json`;
|
|
144
|
-
const response = await fetch(agentCardUrl, {
|
|
145
|
-
headers: {
|
|
146
|
-
Accept: 'application/json',
|
|
147
|
-
...(this.options.headers || {}),
|
|
148
|
-
},
|
|
149
|
-
});
|
|
150
|
-
if (!response.ok) {
|
|
151
|
-
throw new Error(`Failed to fetch Agent Card from ${agentCardUrl}: ${response.status} ${response.statusText}`);
|
|
152
|
-
}
|
|
153
|
-
return (await response.json());
|
|
154
|
-
}
|
|
155
|
-
// If no specific URL is given, return the promise for the initially configured agent's card.
|
|
156
|
-
return this.agentCardPromise;
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Gets the RPC service endpoint URL. Ensures the agent card has been fetched first.
|
|
160
|
-
* @returns A Promise that resolves to the service endpoint URL string.
|
|
161
|
-
*/
|
|
162
|
-
async _getServiceEndpoint() {
|
|
163
|
-
if (this.serviceEndpointUrl) {
|
|
164
|
-
return this.serviceEndpointUrl;
|
|
165
|
-
}
|
|
166
|
-
// If serviceEndpointUrl is not set, it means the agent card fetch is pending or failed.
|
|
167
|
-
// Awaiting agentCardPromise will either resolve it or throw if fetching failed.
|
|
168
|
-
await this.agentCardPromise;
|
|
169
|
-
if (!this.serviceEndpointUrl) {
|
|
170
|
-
// This case should ideally be covered by the error handling in _fetchAndCacheAgentCard
|
|
171
|
-
throw new Error('Agent Card URL for RPC endpoint is not available. Fetching might have failed.');
|
|
172
|
-
}
|
|
173
|
-
return this.serviceEndpointUrl;
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Retry utility functions
|
|
177
|
-
*/
|
|
178
|
-
async retry(fetchFn) {
|
|
179
|
-
const config = this.options.retryConfig;
|
|
180
|
-
if (!config || config.strategy === 'none') {
|
|
181
|
-
return await fetchFn();
|
|
182
|
-
}
|
|
183
|
-
const statusCodes = config.statusCodes || DEFAULT_RETRY_STATUS_CODES;
|
|
184
|
-
return this.retryBackoff(this.wrapFetcher(fetchFn, {
|
|
185
|
-
statusCodes,
|
|
186
|
-
retryConnectionErrors: !!config.retryConnectionErrors,
|
|
187
|
-
}), config.backoff ?? DEFAULT_BACKOFF);
|
|
188
|
-
}
|
|
189
|
-
wrapFetcher(fn, options) {
|
|
190
|
-
return async () => {
|
|
191
|
-
try {
|
|
192
|
-
const res = await fn();
|
|
193
|
-
if (this.isRetryableResponse(res, options.statusCodes)) {
|
|
194
|
-
throw new TemporaryError('Response failed with retryable status code', res);
|
|
195
|
-
}
|
|
196
|
-
return res;
|
|
197
|
-
}
|
|
198
|
-
catch (err) {
|
|
199
|
-
if (err instanceof TemporaryError) {
|
|
200
|
-
throw err;
|
|
201
|
-
}
|
|
202
|
-
if (options.retryConnectionErrors && this.isRetryableError(err)) {
|
|
203
|
-
throw err;
|
|
204
|
-
}
|
|
205
|
-
throw new PermanentError('Permanent error', { cause: err });
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
isRetryableResponse(res, statusCodes) {
|
|
210
|
-
const actual = `${res.status}`;
|
|
211
|
-
const codeRangeRE = /^[0-9]xx$/i;
|
|
212
|
-
return statusCodes.some((code) => {
|
|
213
|
-
if (!codeRangeRE.test(code)) {
|
|
214
|
-
return code === actual;
|
|
215
|
-
}
|
|
216
|
-
const expectFamily = code.charAt(0);
|
|
217
|
-
if (!expectFamily) {
|
|
218
|
-
throw new Error('Invalid status code range');
|
|
219
|
-
}
|
|
220
|
-
const actualFamily = actual.charAt(0);
|
|
221
|
-
if (!actualFamily) {
|
|
222
|
-
throw new Error(`Invalid response status code: ${actual}`);
|
|
223
|
-
}
|
|
224
|
-
return actualFamily === expectFamily;
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
isRetryableError(error) {
|
|
228
|
-
// Check for common network errors that should be retried
|
|
229
|
-
if (error instanceof Error) {
|
|
230
|
-
const message = error.message.toLowerCase();
|
|
231
|
-
return (message.includes('network') ||
|
|
232
|
-
message.includes('timeout') ||
|
|
233
|
-
message.includes('connection') ||
|
|
234
|
-
message.includes('econnreset') ||
|
|
235
|
-
message.includes('econnrefused') ||
|
|
236
|
-
message.includes('enotfound') ||
|
|
237
|
-
message.includes('fetch'));
|
|
238
|
-
}
|
|
239
|
-
return false;
|
|
240
|
-
}
|
|
241
|
-
async retryBackoff(fn, strategy) {
|
|
242
|
-
const { maxElapsedTime, initialInterval, exponent, maxInterval } = strategy;
|
|
243
|
-
const start = Date.now();
|
|
244
|
-
let attempt = 0;
|
|
245
|
-
while (true) {
|
|
246
|
-
try {
|
|
247
|
-
const res = await fn();
|
|
248
|
-
if (attempt > 0) {
|
|
249
|
-
logger.info({
|
|
250
|
-
attempts: attempt + 1,
|
|
251
|
-
elapsedTime: Date.now() - start,
|
|
252
|
-
}, 'A2A request succeeded after retries');
|
|
253
|
-
}
|
|
254
|
-
return res;
|
|
255
|
-
}
|
|
256
|
-
catch (err) {
|
|
257
|
-
if (err instanceof PermanentError) {
|
|
258
|
-
throw err.cause;
|
|
259
|
-
}
|
|
260
|
-
const elapsed = Date.now() - start;
|
|
261
|
-
if (elapsed > maxElapsedTime) {
|
|
262
|
-
logger.warn({
|
|
263
|
-
attempts: attempt + 1,
|
|
264
|
-
elapsedTime: elapsed,
|
|
265
|
-
maxElapsedTime,
|
|
266
|
-
}, 'A2A request max retry time exceeded');
|
|
267
|
-
if (err instanceof TemporaryError) {
|
|
268
|
-
return err.response;
|
|
269
|
-
}
|
|
270
|
-
throw err;
|
|
271
|
-
}
|
|
272
|
-
let retryInterval = 0;
|
|
273
|
-
if (err instanceof TemporaryError) {
|
|
274
|
-
retryInterval = this.retryIntervalFromResponse(err.response);
|
|
275
|
-
}
|
|
276
|
-
if (retryInterval <= 0) {
|
|
277
|
-
retryInterval = initialInterval * attempt ** exponent + Math.random() * 1000;
|
|
278
|
-
}
|
|
279
|
-
const delayMs = Math.min(retryInterval, maxInterval);
|
|
280
|
-
logger.info({
|
|
281
|
-
attempt: attempt + 1,
|
|
282
|
-
delayMs,
|
|
283
|
-
error: err instanceof TemporaryError ? `HTTP ${err.response.status}` : 'Network error',
|
|
284
|
-
}, 'A2A request failed, retrying after delay');
|
|
285
|
-
await this.delay(delayMs);
|
|
286
|
-
attempt++;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
retryIntervalFromResponse(res) {
|
|
291
|
-
const retryVal = res.headers.get('retry-after') || '';
|
|
292
|
-
if (!retryVal) {
|
|
293
|
-
return 0;
|
|
294
|
-
}
|
|
295
|
-
const parsedNumber = Number(retryVal);
|
|
296
|
-
if (Number.isInteger(parsedNumber)) {
|
|
297
|
-
return parsedNumber * 1000;
|
|
298
|
-
}
|
|
299
|
-
const parsedDate = Date.parse(retryVal);
|
|
300
|
-
if (Number.isInteger(parsedDate)) {
|
|
301
|
-
const deltaMS = parsedDate - Date.now();
|
|
302
|
-
return deltaMS > 0 ? Math.ceil(deltaMS) : 0;
|
|
303
|
-
}
|
|
304
|
-
return 0;
|
|
305
|
-
}
|
|
306
|
-
async delay(delayMs) {
|
|
307
|
-
return new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* Helper method to make a generic JSON-RPC POST request.
|
|
311
|
-
* @param method The RPC method name.
|
|
312
|
-
* @param params The parameters for the RPC method.
|
|
313
|
-
* @returns A Promise that resolves to the RPC response.
|
|
314
|
-
*/
|
|
315
|
-
async _postRpcRequest(method, params) {
|
|
316
|
-
const endpoint = await this._getServiceEndpoint();
|
|
317
|
-
const requestId = this.requestIdCounter++;
|
|
318
|
-
const rpcRequest = {
|
|
319
|
-
jsonrpc: '2.0',
|
|
320
|
-
method,
|
|
321
|
-
params: params, // Cast because TParams structure varies per method
|
|
322
|
-
id: requestId,
|
|
323
|
-
};
|
|
324
|
-
const httpResponse = await this.retry(async () => {
|
|
325
|
-
return fetch(endpoint, {
|
|
326
|
-
method: 'POST',
|
|
327
|
-
headers: {
|
|
328
|
-
'Content-Type': 'application/json',
|
|
329
|
-
Accept: 'application/json', // Expect JSON response for non-streaming requests
|
|
330
|
-
...(this.options.headers || {}),
|
|
331
|
-
},
|
|
332
|
-
body: JSON.stringify(rpcRequest),
|
|
333
|
-
});
|
|
334
|
-
});
|
|
335
|
-
if (!httpResponse.ok) {
|
|
336
|
-
let errorBodyText = '(empty or non-JSON response)';
|
|
337
|
-
try {
|
|
338
|
-
errorBodyText = await httpResponse.text();
|
|
339
|
-
const errorJson = JSON.parse(errorBodyText);
|
|
340
|
-
// If the body is a valid JSON-RPC error response, let it be handled by the standard parsing below.
|
|
341
|
-
// However, if it's not even a JSON-RPC structure but still an error, throw based on HTTP status.
|
|
342
|
-
if (!errorJson.jsonrpc && errorJson.error) {
|
|
343
|
-
// Check if it's a JSON-RPC error structure
|
|
344
|
-
throw new Error(`RPC error for ${method}: ${errorJson.error.message} (Code: ${errorJson.error.code}, HTTP Status: ${httpResponse.status}) Data: ${JSON.stringify(errorJson.error.data)}`);
|
|
345
|
-
}
|
|
346
|
-
if (!errorJson.jsonrpc) {
|
|
347
|
-
throw new Error(`HTTP error for ${method}! Status: ${httpResponse.status} ${httpResponse.statusText}. Response: ${errorBodyText}`);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
catch (e) {
|
|
351
|
-
// If parsing the error body fails or it's not a JSON-RPC error, throw a generic HTTP error.
|
|
352
|
-
// If it was already an error thrown from within the try block, rethrow it.
|
|
353
|
-
if (e.message.startsWith('RPC error for') || e.message.startsWith('HTTP error for'))
|
|
354
|
-
throw e;
|
|
355
|
-
throw new Error(`HTTP error for ${method}! Status: ${httpResponse.status} ${httpResponse.statusText}. Response: ${errorBodyText}`);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
const rpcResponse = await httpResponse.json();
|
|
359
|
-
if (rpcResponse.id !== requestId) {
|
|
360
|
-
// This is a significant issue for request-response matching.
|
|
361
|
-
logger.warn({
|
|
362
|
-
method,
|
|
363
|
-
expectedId: requestId,
|
|
364
|
-
receivedId: rpcResponse.id,
|
|
365
|
-
}, 'RPC response ID mismatch - this may lead to incorrect response handling');
|
|
366
|
-
// Depending on strictness, one might throw an error here.
|
|
367
|
-
// throw new Error(`RPC response ID mismatch for method ${method}. Expected ${requestId}, got ${rpcResponse.id}`);
|
|
368
|
-
}
|
|
369
|
-
return rpcResponse;
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* Sends a message to the agent.
|
|
373
|
-
* The behavior (blocking/non-blocking) and push notification configuration
|
|
374
|
-
* are specified within the `params.configuration` object.
|
|
375
|
-
* Optionally, `params.message.contextId` or `params.message.taskId` can be provided.
|
|
376
|
-
* @param params The parameters for sending the message, including the message content and configuration.
|
|
377
|
-
* @returns A Promise resolving to SendMessageResponse, which can be a Message, Task, or an error.
|
|
378
|
-
*/
|
|
379
|
-
async sendMessage(params) {
|
|
380
|
-
return this._postRpcRequest('message/send', params);
|
|
381
|
-
}
|
|
382
|
-
/**
|
|
383
|
-
* Sends a message to the agent and streams back responses using Server-Sent Events (SSE).
|
|
384
|
-
* Push notification configuration can be specified in `params.configuration`.
|
|
385
|
-
* Optionally, `params.message.contextId` or `params.message.taskId` can be provided.
|
|
386
|
-
* Requires the agent to support streaming (`capabilities.streaming: true` in AgentCard).
|
|
387
|
-
* @param params The parameters for sending the message.
|
|
388
|
-
* @returns An AsyncGenerator yielding A2AStreamEventData (Message, Task, TaskStatusUpdateEvent, or TaskArtifactUpdateEvent).
|
|
389
|
-
* The generator throws an error if streaming is not supported or if an HTTP/SSE error occurs.
|
|
390
|
-
*/
|
|
391
|
-
async *sendMessageStream(params) {
|
|
392
|
-
// const agentCard = await this.agentCardPromise; // Ensure agent card is fetched
|
|
393
|
-
// // if (!agentCard.capabilities?.streaming) {
|
|
394
|
-
// throw new Error("Agent does not support streaming (AgentCard.capabilities.streaming is not true).");
|
|
395
|
-
// }
|
|
396
|
-
const endpoint = await this._getServiceEndpoint();
|
|
397
|
-
const clientRequestId = this.requestIdCounter++; // Use a unique ID for this stream request
|
|
398
|
-
const rpcRequest = {
|
|
399
|
-
// This is the initial JSON-RPC request to establish the stream
|
|
400
|
-
jsonrpc: '2.0',
|
|
401
|
-
method: 'message/stream',
|
|
402
|
-
params: params,
|
|
403
|
-
id: clientRequestId,
|
|
404
|
-
};
|
|
405
|
-
const response = await fetch(endpoint, {
|
|
406
|
-
method: 'POST',
|
|
407
|
-
headers: {
|
|
408
|
-
'Content-Type': 'application/json',
|
|
409
|
-
Accept: 'text/event-stream', // Crucial for SSE
|
|
410
|
-
...(this.options.headers || {}),
|
|
411
|
-
},
|
|
412
|
-
body: JSON.stringify(rpcRequest),
|
|
413
|
-
});
|
|
414
|
-
if (!response.ok) {
|
|
415
|
-
// Attempt to read error body for more details
|
|
416
|
-
let errorBody = '';
|
|
417
|
-
try {
|
|
418
|
-
errorBody = await response.text();
|
|
419
|
-
const errorJson = JSON.parse(errorBody);
|
|
420
|
-
if (errorJson.error) {
|
|
421
|
-
throw new Error(`HTTP error establishing stream for message/stream: ${response.status} ${response.statusText}. RPC Error: ${errorJson.error.message} (Code: ${errorJson.error.code})`);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
catch (e) {
|
|
425
|
-
if (e.message.startsWith('HTTP error establishing stream'))
|
|
426
|
-
throw e;
|
|
427
|
-
// Fallback if body is not JSON or parsing fails
|
|
428
|
-
throw new Error(`HTTP error establishing stream for message/stream: ${response.status} ${response.statusText}. Response: ${errorBody || '(empty)'}`);
|
|
429
|
-
}
|
|
430
|
-
throw new Error(`HTTP error establishing stream for message/stream: ${response.status} ${response.statusText}`);
|
|
431
|
-
}
|
|
432
|
-
if (!response.headers.get('Content-Type')?.startsWith('text/event-stream')) {
|
|
433
|
-
// Server should explicitly set this content type for SSE.
|
|
434
|
-
throw new Error("Invalid response Content-Type for SSE stream. Expected 'text/event-stream'.");
|
|
435
|
-
}
|
|
436
|
-
// Yield events from the parsed SSE stream.
|
|
437
|
-
// Each event's 'data' field is a JSON-RPC response.
|
|
438
|
-
yield* this._parseA2ASseStream(response, clientRequestId);
|
|
439
|
-
}
|
|
440
|
-
/**
|
|
441
|
-
* Sets or updates the push notification configuration for a given task.
|
|
442
|
-
* Requires the agent to support push notifications (`capabilities.pushNotifications: true` in AgentCard).
|
|
443
|
-
* @param params Parameters containing the taskId and the TaskPushNotificationConfig.
|
|
444
|
-
* @returns A Promise resolving to SetTaskPushNotificationConfigResponse.
|
|
445
|
-
*/
|
|
446
|
-
async setTaskPushNotificationConfig(params) {
|
|
447
|
-
const agentCard = await this.agentCardPromise;
|
|
448
|
-
if (!agentCard.capabilities?.pushNotifications) {
|
|
449
|
-
throw new Error('Agent does not support push notifications (AgentCard.capabilities.pushNotifications is not true).');
|
|
450
|
-
}
|
|
451
|
-
// The 'params' directly matches the structure expected by the RPC method.
|
|
452
|
-
return this._postRpcRequest('tasks/pushNotificationConfig/set', params);
|
|
453
|
-
}
|
|
454
|
-
/**
|
|
455
|
-
* Gets the push notification configuration for a given task.
|
|
456
|
-
* @param params Parameters containing the taskId.
|
|
457
|
-
* @returns A Promise resolving to GetTaskPushNotificationConfigResponse.
|
|
458
|
-
*/
|
|
459
|
-
async getTaskPushNotificationConfig(params) {
|
|
460
|
-
// The 'params' (TaskIdParams) directly matches the structure expected by the RPC method.
|
|
461
|
-
return this._postRpcRequest('tasks/pushNotificationConfig/get', params);
|
|
462
|
-
}
|
|
463
|
-
/**
|
|
464
|
-
* Retrieves a task by its ID.
|
|
465
|
-
* @param params Parameters containing the taskId and optional historyLength.
|
|
466
|
-
* @returns A Promise resolving to GetTaskResponse, which contains the Task object or an error.
|
|
467
|
-
*/
|
|
468
|
-
async getTask(params) {
|
|
469
|
-
return this._postRpcRequest('tasks/get', params);
|
|
470
|
-
}
|
|
471
|
-
/**
|
|
472
|
-
* Cancels a task by its ID.
|
|
473
|
-
* @param params Parameters containing the taskId.
|
|
474
|
-
* @returns A Promise resolving to CancelTaskResponse, which contains the updated Task object or an error.
|
|
475
|
-
*/
|
|
476
|
-
async cancelTask(params) {
|
|
477
|
-
return this._postRpcRequest('tasks/cancel', params);
|
|
478
|
-
}
|
|
479
|
-
/**
|
|
480
|
-
* Resubscribes to a task's event stream using Server-Sent Events (SSE).
|
|
481
|
-
* This is used if a previous SSE connection for an active task was broken.
|
|
482
|
-
* Requires the agent to support streaming (`capabilities.streaming: true` in AgentCard).
|
|
483
|
-
* @param params Parameters containing the taskId.
|
|
484
|
-
* @returns An AsyncGenerator yielding A2AStreamEventData (Message, Task, TaskStatusUpdateEvent, or TaskArtifactUpdateEvent).
|
|
485
|
-
*/
|
|
486
|
-
async *resubscribeTask(params) {
|
|
487
|
-
const agentCard = await this.agentCardPromise;
|
|
488
|
-
if (!agentCard.capabilities?.streaming) {
|
|
489
|
-
throw new Error('Agent does not support streaming (required for tasks/resubscribe).');
|
|
490
|
-
}
|
|
491
|
-
const endpoint = await this._getServiceEndpoint();
|
|
492
|
-
const clientRequestId = this.requestIdCounter++; // Unique ID for this resubscribe request
|
|
493
|
-
const rpcRequest = {
|
|
494
|
-
// Initial JSON-RPC request to establish the stream
|
|
495
|
-
jsonrpc: '2.0',
|
|
496
|
-
method: 'tasks/resubscribe',
|
|
497
|
-
params: params,
|
|
498
|
-
id: clientRequestId,
|
|
499
|
-
};
|
|
500
|
-
const response = await fetch(endpoint, {
|
|
501
|
-
method: 'POST',
|
|
502
|
-
headers: {
|
|
503
|
-
'Content-Type': 'application/json',
|
|
504
|
-
Accept: 'text/event-stream',
|
|
505
|
-
...(this.options.headers || {}),
|
|
506
|
-
},
|
|
507
|
-
body: JSON.stringify(rpcRequest),
|
|
508
|
-
});
|
|
509
|
-
if (!response.ok) {
|
|
510
|
-
let errorBody = '';
|
|
511
|
-
try {
|
|
512
|
-
errorBody = await response.text();
|
|
513
|
-
const errorJson = JSON.parse(errorBody);
|
|
514
|
-
if (errorJson.error) {
|
|
515
|
-
throw new Error(`HTTP error establishing stream for tasks/resubscribe: ${response.status} ${response.statusText}. RPC Error: ${errorJson.error.message} (Code: ${errorJson.error.code})`);
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
catch (e) {
|
|
519
|
-
if (e.message.startsWith('HTTP error establishing stream'))
|
|
520
|
-
throw e;
|
|
521
|
-
throw new Error(`HTTP error establishing stream for tasks/resubscribe: ${response.status} ${response.statusText}. Response: ${errorBody || '(empty)'}`);
|
|
522
|
-
}
|
|
523
|
-
throw new Error(`HTTP error establishing stream for tasks/resubscribe: ${response.status} ${response.statusText}`);
|
|
524
|
-
}
|
|
525
|
-
if (!response.headers.get('Content-Type')?.startsWith('text/event-stream')) {
|
|
526
|
-
throw new Error("Invalid response Content-Type for SSE stream on resubscribe. Expected 'text/event-stream'.");
|
|
527
|
-
}
|
|
528
|
-
// The events structure for resubscribe is assumed to be the same as message/stream.
|
|
529
|
-
// Each event's 'data' field is a JSON-RPC response.
|
|
530
|
-
yield* this._parseA2ASseStream(response, clientRequestId);
|
|
531
|
-
}
|
|
532
|
-
/**
|
|
533
|
-
* Parses an HTTP response body as an A2A Server-Sent Event stream.
|
|
534
|
-
* Each 'data' field of an SSE event is expected to be a JSON-RPC 2.0 Response object,
|
|
535
|
-
* specifically a SendStreamingMessageResponse (or similar structure for resubscribe).
|
|
536
|
-
* @param response The HTTP Response object whose body is the SSE stream.
|
|
537
|
-
* @param originalRequestId The ID of the client's JSON-RPC request that initiated this stream.
|
|
538
|
-
* Used to validate the `id` in the streamed JSON-RPC responses.
|
|
539
|
-
* @returns An AsyncGenerator yielding the `result` field of each valid JSON-RPC success response from the stream.
|
|
540
|
-
*/
|
|
541
|
-
async *_parseA2ASseStream(response, originalRequestId) {
|
|
542
|
-
if (!response.body) {
|
|
543
|
-
throw new Error('SSE response body is undefined. Cannot read stream.');
|
|
544
|
-
}
|
|
545
|
-
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
546
|
-
let buffer = ''; // Holds incomplete lines from the stream
|
|
547
|
-
let eventDataBuffer = ''; // Holds accumulated 'data:' lines for the current event
|
|
548
|
-
try {
|
|
549
|
-
while (true) {
|
|
550
|
-
const { done, value } = await reader.read();
|
|
551
|
-
logger.info({ done, value }, 'parseA2ASseStream');
|
|
552
|
-
if (done) {
|
|
553
|
-
// Process any final buffered event data if the stream ends abruptly after a 'data:' line
|
|
554
|
-
if (eventDataBuffer.trim()) {
|
|
555
|
-
const result = this._processSseEventData(eventDataBuffer, originalRequestId);
|
|
556
|
-
yield result;
|
|
557
|
-
}
|
|
558
|
-
break; // Stream finished
|
|
559
|
-
}
|
|
560
|
-
buffer += value; // Append new chunk to buffer
|
|
561
|
-
let lineEndIndex;
|
|
562
|
-
// Process all complete lines in the buffer
|
|
563
|
-
lineEndIndex = buffer.indexOf('\n');
|
|
564
|
-
while (lineEndIndex >= 0) {
|
|
565
|
-
const line = buffer.substring(0, lineEndIndex).trim(); // Get and trim the line
|
|
566
|
-
buffer = buffer.substring(lineEndIndex + 1); // Remove processed line from buffer
|
|
567
|
-
if (line === '') {
|
|
568
|
-
// Empty line: signifies the end of an event
|
|
569
|
-
if (eventDataBuffer) {
|
|
570
|
-
// If we have accumulated data for an event
|
|
571
|
-
const result = this._processSseEventData(eventDataBuffer, originalRequestId);
|
|
572
|
-
yield result;
|
|
573
|
-
eventDataBuffer = ''; // Reset buffer for the next event
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
else if (line.startsWith('data:')) {
|
|
577
|
-
eventDataBuffer += `${line.substring(5).trimStart()}\n`; // Append data (multi-line data is possible)
|
|
578
|
-
}
|
|
579
|
-
else if (line.startsWith(':')) {
|
|
580
|
-
// This is a comment line in SSE, ignore it.
|
|
581
|
-
}
|
|
582
|
-
else if (line.includes(':')) {
|
|
583
|
-
// Other SSE fields like 'event:', 'id:', 'retry:'.
|
|
584
|
-
// The A2A spec primarily focuses on the 'data' field for JSON-RPC payloads.
|
|
585
|
-
// For now, we don't specifically handle these other SSE fields unless required by spec.
|
|
586
|
-
}
|
|
587
|
-
lineEndIndex = buffer.indexOf('\n'); // Update for next iteration
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
catch (error) {
|
|
592
|
-
// Log and re-throw errors encountered during stream processing
|
|
593
|
-
console.error('Error reading or parsing SSE stream:', error.message);
|
|
594
|
-
throw error;
|
|
595
|
-
}
|
|
596
|
-
finally {
|
|
597
|
-
reader.releaseLock(); // Ensure the reader lock is released
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
/**
|
|
601
|
-
* Processes a single SSE event's data string, expecting it to be a JSON-RPC response.
|
|
602
|
-
* @param jsonData The string content from one or more 'data:' lines of an SSE event.
|
|
603
|
-
* @param originalRequestId The ID of the client's request that initiated the stream.
|
|
604
|
-
* @returns The `result` field of the parsed JSON-RPC success response.
|
|
605
|
-
* @throws Error if data is not valid JSON, not a valid JSON-RPC response, an error response, or ID mismatch.
|
|
606
|
-
*/
|
|
607
|
-
_processSseEventData(jsonData, originalRequestId) {
|
|
608
|
-
if (!jsonData.trim()) {
|
|
609
|
-
throw new Error('Attempted to process empty SSE event data.');
|
|
610
|
-
}
|
|
611
|
-
try {
|
|
612
|
-
// SSE data can be multi-line, ensure it's treated as a single JSON string.
|
|
613
|
-
const sseJsonRpcResponse = JSON.parse(jsonData.replace(/\n$/, '')); // Remove trailing newline if any
|
|
614
|
-
// Type assertion to SendStreamingMessageResponse, as this is the expected structure for A2A streams.
|
|
615
|
-
const a2aStreamResponse = sseJsonRpcResponse;
|
|
616
|
-
if (a2aStreamResponse.id !== originalRequestId) {
|
|
617
|
-
// According to JSON-RPC spec, notifications (which SSE events can be seen as) might not have an ID,
|
|
618
|
-
// or if they do, it should match. A2A spec implies streamed events are tied to the initial request.
|
|
619
|
-
console.warn(`SSE Event's JSON-RPC response ID mismatch. Client request ID: ${originalRequestId}, event response ID: ${a2aStreamResponse.id}.`);
|
|
620
|
-
// Depending on strictness, this could be an error. For now, it's a warning.
|
|
621
|
-
}
|
|
622
|
-
if (a2aStreamResponse.error) {
|
|
623
|
-
const err = a2aStreamResponse.error;
|
|
624
|
-
throw new Error(`SSE event contained an error: ${err.message} (Code: ${err.code}) Data: ${JSON.stringify(err.data)}`);
|
|
625
|
-
}
|
|
626
|
-
// Check if 'result' exists, as it's mandatory for successful JSON-RPC responses
|
|
627
|
-
if (!('result' in a2aStreamResponse) ||
|
|
628
|
-
typeof a2aStreamResponse.result === 'undefined') {
|
|
629
|
-
throw new Error(`SSE event JSON-RPC response is missing 'result' field. Data: ${jsonData}`);
|
|
630
|
-
}
|
|
631
|
-
const successResponse = a2aStreamResponse;
|
|
632
|
-
return successResponse.result;
|
|
633
|
-
}
|
|
634
|
-
catch (e) {
|
|
635
|
-
// Catch errors from JSON.parse or if it's an error response that was thrown by this function
|
|
636
|
-
if (e.message.startsWith('SSE event contained an error') ||
|
|
637
|
-
e.message.startsWith("SSE event JSON-RPC response is missing 'result' field")) {
|
|
638
|
-
throw e; // Re-throw errors already processed/identified by this function
|
|
639
|
-
}
|
|
640
|
-
// For other parsing errors or unexpected structures:
|
|
641
|
-
console.error('Failed to parse SSE event data string or unexpected JSON-RPC structure:', jsonData, e);
|
|
642
|
-
throw new Error(`Failed to parse SSE event data: "${jsonData.substring(0, 100)}...". Original error: ${e.message}`);
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
}
|
package/dist/a2a/handlers.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/a2a/handlers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAepC,OAAO,KAAK,EAA4C,eAAe,EAAE,MAAM,YAAY,CAAC;AAI5F,wBAAsB,UAAU,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CAiEtF"}
|