@bowenqt/qiniu-ai-sdk 0.28.1 → 0.28.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/a2a/expert.d.ts +1 -1
- package/dist/ai/a2a/expert.d.ts.map +1 -1
- package/dist/ai/a2a/expert.js +28 -51
- package/dist/ai/a2a/expert.js.map +1 -1
- package/dist/ai/a2a/expert.mjs +28 -51
- package/dist/ai/a2a/rate-limiter.d.ts +8 -11
- package/dist/ai/a2a/rate-limiter.d.ts.map +1 -1
- package/dist/ai/a2a/rate-limiter.js +27 -36
- package/dist/ai/a2a/rate-limiter.js.map +1 -1
- package/dist/ai/a2a/rate-limiter.mjs +27 -36
- package/package.json +1 -1
package/dist/ai/a2a/expert.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export declare class AgentExpert {
|
|
|
23
23
|
static from(agent: Agent, config: AgentExpertConfig): AgentExpert;
|
|
24
24
|
/**
|
|
25
25
|
* Direct tool call with full request.
|
|
26
|
+
* Note: `from` field is ignored for rate limiting - expert.id is always used.
|
|
26
27
|
*/
|
|
27
28
|
callTool(request: CallToolRequest): Promise<A2AMessage>;
|
|
28
29
|
/**
|
|
@@ -33,6 +34,5 @@ export declare class AgentExpert {
|
|
|
33
34
|
* Get list of exposed tool names (without prefix).
|
|
34
35
|
*/
|
|
35
36
|
getExposedToolNames(): string[];
|
|
36
|
-
private waitForRateLimitSlot;
|
|
37
37
|
}
|
|
38
38
|
//# sourceMappingURL=expert.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expert.d.ts","sourceRoot":"","sources":["../../../src/ai/a2a/expert.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,IAAI,EAA+B,MAAM,kBAAkB,CAAC;AAC1E,OAAO,KAAK,EACR,iBAAiB,EACjB,UAAU,EACV,eAAe,EACf,cAAc,EACd,aAAa,EAChB,MAAM,SAAS,CAAC;AAcjB;;GAEG;AACH,qBAAa,WAAW;IACpB,uBAAuB;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,kCAAkC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAErC,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,MAAM,CAAkG;IAChH,OAAO,CAAC,WAAW,CAAC,CAAiB;IACrC,OAAO,CAAC,aAAa,CAAuB;IAE5C,OAAO;
|
|
1
|
+
{"version":3,"file":"expert.d.ts","sourceRoot":"","sources":["../../../src/ai/a2a/expert.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,IAAI,EAA+B,MAAM,kBAAkB,CAAC;AAC1E,OAAO,KAAK,EACR,iBAAiB,EACjB,UAAU,EACV,eAAe,EACf,cAAc,EACd,aAAa,EAChB,MAAM,SAAS,CAAC;AAcjB;;GAEG;AACH,qBAAa,WAAW;IACpB,uBAAuB;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,kCAAkC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAErC,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,MAAM,CAAkG;IAChH,OAAO,CAAC,WAAW,CAAC,CAAiB;IACrC,OAAO,CAAC,aAAa,CAAuB;IAE5C,OAAO;IAoBP;;OAEG;IACH,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,GAAG,WAAW;IAkCjE;;;OAGG;IACG,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC;IA0E7D;;OAEG;IACG,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAgB9D;;OAEG;IACH,mBAAmB,IAAI,MAAM,EAAE;CAGlC"}
|
package/dist/ai/a2a/expert.js
CHANGED
|
@@ -14,7 +14,7 @@ const rate_limiter_1 = require("./rate-limiter");
|
|
|
14
14
|
* Wraps an Agent to expose selected tools for A2A collaboration.
|
|
15
15
|
*/
|
|
16
16
|
class AgentExpert {
|
|
17
|
-
constructor(agent, config, tools, originalTools) {
|
|
17
|
+
constructor(agent, config, tools, originalTools, rateLimiter) {
|
|
18
18
|
this.agent = agent;
|
|
19
19
|
this.id = agent.id;
|
|
20
20
|
this.config = {
|
|
@@ -25,25 +25,21 @@ class AgentExpert {
|
|
|
25
25
|
};
|
|
26
26
|
this.tools = tools;
|
|
27
27
|
this.originalTools = originalTools;
|
|
28
|
-
|
|
29
|
-
this.rateLimiter = new rate_limiter_1.A2ARateLimiter(config.rateLimit);
|
|
30
|
-
}
|
|
28
|
+
this.rateLimiter = rateLimiter;
|
|
31
29
|
}
|
|
32
30
|
/**
|
|
33
31
|
* Create an AgentExpert from an Agent.
|
|
34
32
|
*/
|
|
35
33
|
static from(agent, config) {
|
|
36
|
-
// Get original tools
|
|
37
34
|
const originalTools = agent._tools;
|
|
38
|
-
// Build exposed tools with prefix
|
|
39
|
-
const exposedTools = {};
|
|
40
35
|
const prefix = config.prefix ?? `${agent.id}_`;
|
|
41
36
|
const expertId = agent.id;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
37
|
+
// Single shared rate limiter instance
|
|
38
|
+
const rateLimiter = config.rateLimit
|
|
39
|
+
? new rate_limiter_1.A2ARateLimiter(config.rateLimit)
|
|
40
|
+
: undefined;
|
|
41
|
+
// Build exposed tools
|
|
42
|
+
const exposedTools = {};
|
|
47
43
|
for (const toolName of config.expose) {
|
|
48
44
|
const tool = originalTools[toolName];
|
|
49
45
|
if (!tool) {
|
|
@@ -51,17 +47,18 @@ class AgentExpert {
|
|
|
51
47
|
continue;
|
|
52
48
|
}
|
|
53
49
|
const prefixedName = `${prefix}${toolName}`;
|
|
54
|
-
exposedTools[prefixedName] = createWrappedTool(tool, toolName, expertId, rateLimiter, config.validateArgs ?? true);
|
|
50
|
+
exposedTools[prefixedName] = createWrappedTool(tool, toolName, expertId, rateLimiter, config.validateArgs ?? true, config.rateLimit?.onLimit ?? 'reject');
|
|
55
51
|
}
|
|
56
|
-
return new AgentExpert(agent, config, exposedTools, originalTools);
|
|
52
|
+
return new AgentExpert(agent, config, exposedTools, originalTools, rateLimiter);
|
|
57
53
|
}
|
|
58
54
|
/**
|
|
59
55
|
* Direct tool call with full request.
|
|
56
|
+
* Note: `from` field is ignored for rate limiting - expert.id is always used.
|
|
60
57
|
*/
|
|
61
58
|
async callTool(request) {
|
|
62
59
|
const requestId = request.requestId ?? (0, types_1.generateRequestId)();
|
|
63
60
|
const { from = '', tool, args, signal } = request;
|
|
64
|
-
// Create base request for
|
|
61
|
+
// Create base request for responses
|
|
65
62
|
const baseRequest = {
|
|
66
63
|
requestId,
|
|
67
64
|
type: 'request',
|
|
@@ -84,18 +81,10 @@ class AgentExpert {
|
|
|
84
81
|
if (signal?.aborted) {
|
|
85
82
|
return (0, types_1.createA2AError)(baseRequest, 'CANCELLED', 'Request was cancelled');
|
|
86
83
|
}
|
|
87
|
-
// Rate limiting
|
|
84
|
+
// Rate limiting (use expert.id, not caller-provided from)
|
|
88
85
|
if (this.rateLimiter) {
|
|
89
86
|
try {
|
|
90
|
-
|
|
91
|
-
if (!allowed) {
|
|
92
|
-
if (this.config.rateLimit?.onLimit === 'reject') {
|
|
93
|
-
throw new rate_limiter_1.RateLimitError(from || this.id, tool, this.rateLimiter.getTimeUntilSlot(from || this.id, tool));
|
|
94
|
-
}
|
|
95
|
-
// Queue mode - wait for slot
|
|
96
|
-
await this.waitForRateLimitSlot(from || this.id, tool);
|
|
97
|
-
}
|
|
98
|
-
this.rateLimiter.track(from || this.id, tool);
|
|
87
|
+
await this.rateLimiter.execute(this.id, tool, async () => { });
|
|
99
88
|
}
|
|
100
89
|
catch (error) {
|
|
101
90
|
if (error instanceof rate_limiter_1.RateLimitError) {
|
|
@@ -133,11 +122,9 @@ class AgentExpert {
|
|
|
133
122
|
async runTask(request) {
|
|
134
123
|
const requestId = request.requestId ?? (0, types_1.generateRequestId)();
|
|
135
124
|
const { prompt, signal } = request;
|
|
136
|
-
// Check abort
|
|
137
125
|
if (signal?.aborted) {
|
|
138
126
|
throw new Error('Request was cancelled');
|
|
139
127
|
}
|
|
140
|
-
// Run the agent
|
|
141
128
|
const result = await this.agent.run({ prompt });
|
|
142
129
|
return {
|
|
143
130
|
output: result.text ?? '',
|
|
@@ -150,22 +137,6 @@ class AgentExpert {
|
|
|
150
137
|
getExposedToolNames() {
|
|
151
138
|
return [...this.config.expose];
|
|
152
139
|
}
|
|
153
|
-
// ========================================================================
|
|
154
|
-
// Private Methods
|
|
155
|
-
// ========================================================================
|
|
156
|
-
async waitForRateLimitSlot(callerId, tool) {
|
|
157
|
-
const maxWait = this.config.rateLimit?.windowMs ?? 60000;
|
|
158
|
-
const checkInterval = 50;
|
|
159
|
-
let waited = 0;
|
|
160
|
-
while (waited < maxWait) {
|
|
161
|
-
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
162
|
-
waited += checkInterval;
|
|
163
|
-
if (this.rateLimiter?.isAllowed(callerId, tool)) {
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
throw new rate_limiter_1.RateLimitError(callerId, tool, 0);
|
|
168
|
-
}
|
|
169
140
|
}
|
|
170
141
|
exports.AgentExpert = AgentExpert;
|
|
171
142
|
// ============================================================================
|
|
@@ -174,24 +145,30 @@ exports.AgentExpert = AgentExpert;
|
|
|
174
145
|
/**
|
|
175
146
|
* Create a wrapped tool preserving all metadata.
|
|
176
147
|
*/
|
|
177
|
-
function createWrappedTool(original, toolName, expertId, rateLimiter, validateArgs) {
|
|
148
|
+
function createWrappedTool(original, toolName, expertId, rateLimiter, validateArgs, onLimit) {
|
|
178
149
|
return {
|
|
179
|
-
// Preserve all metadata
|
|
180
150
|
description: original.description,
|
|
181
151
|
parameters: original.parameters,
|
|
182
152
|
requiresApproval: original.requiresApproval,
|
|
183
153
|
approvalHandler: original.approvalHandler,
|
|
184
154
|
source: original.source,
|
|
185
155
|
execute: async (args, context) => {
|
|
186
|
-
// Rate limiting
|
|
156
|
+
// Rate limiting with proper queue support
|
|
187
157
|
if (rateLimiter) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
158
|
+
if (onLimit === 'queue') {
|
|
159
|
+
// Wait for slot (throws on timeout)
|
|
160
|
+
await rateLimiter.waitForSlot(expertId, toolName);
|
|
161
|
+
rateLimiter.track(expertId, toolName);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
// Reject immediately if not allowed
|
|
165
|
+
if (!rateLimiter.isAllowed(expertId, toolName)) {
|
|
166
|
+
throw new rate_limiter_1.RateLimitError(expertId, toolName, rateLimiter.getTimeUntilSlot(expertId, toolName));
|
|
167
|
+
}
|
|
168
|
+
rateLimiter.track(expertId, toolName);
|
|
191
169
|
}
|
|
192
|
-
rateLimiter.track(callerId, toolName);
|
|
193
170
|
}
|
|
194
|
-
// Validate
|
|
171
|
+
// Validate
|
|
195
172
|
if (validateArgs && original.parameters) {
|
|
196
173
|
const result = (0, validation_1.validateSchema)(args, original.parameters);
|
|
197
174
|
if (!result.valid) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expert.js","sourceRoot":"","sources":["../../../src/ai/a2a/expert.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AAWH,mCAKiB;AACjB,6CAA+D;AAC/D,iDAAgE;AAEhE,+EAA+E;AAC/E,cAAc;AACd,+EAA+E;AAE/E;;GAEG;AACH,MAAa,WAAW;IAWpB,YACI,KAAY,EACZ,MAAyB,EACzB,KAA2B,EAC3B,aAAmC;
|
|
1
|
+
{"version":3,"file":"expert.js","sourceRoot":"","sources":["../../../src/ai/a2a/expert.ts"],"names":[],"mappings":"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"}
|
package/dist/ai/a2a/expert.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { A2ARateLimiter, RateLimitError } from './rate-limiter.mjs';
|
|
|
11
11
|
* Wraps an Agent to expose selected tools for A2A collaboration.
|
|
12
12
|
*/
|
|
13
13
|
export class AgentExpert {
|
|
14
|
-
constructor(agent, config, tools, originalTools) {
|
|
14
|
+
constructor(agent, config, tools, originalTools, rateLimiter) {
|
|
15
15
|
this.agent = agent;
|
|
16
16
|
this.id = agent.id;
|
|
17
17
|
this.config = {
|
|
@@ -22,25 +22,21 @@ export class AgentExpert {
|
|
|
22
22
|
};
|
|
23
23
|
this.tools = tools;
|
|
24
24
|
this.originalTools = originalTools;
|
|
25
|
-
|
|
26
|
-
this.rateLimiter = new A2ARateLimiter(config.rateLimit);
|
|
27
|
-
}
|
|
25
|
+
this.rateLimiter = rateLimiter;
|
|
28
26
|
}
|
|
29
27
|
/**
|
|
30
28
|
* Create an AgentExpert from an Agent.
|
|
31
29
|
*/
|
|
32
30
|
static from(agent, config) {
|
|
33
|
-
// Get original tools
|
|
34
31
|
const originalTools = agent._tools;
|
|
35
|
-
// Build exposed tools with prefix
|
|
36
|
-
const exposedTools = {};
|
|
37
32
|
const prefix = config.prefix ?? `${agent.id}_`;
|
|
38
33
|
const expertId = agent.id;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
34
|
+
// Single shared rate limiter instance
|
|
35
|
+
const rateLimiter = config.rateLimit
|
|
36
|
+
? new A2ARateLimiter(config.rateLimit)
|
|
37
|
+
: undefined;
|
|
38
|
+
// Build exposed tools
|
|
39
|
+
const exposedTools = {};
|
|
44
40
|
for (const toolName of config.expose) {
|
|
45
41
|
const tool = originalTools[toolName];
|
|
46
42
|
if (!tool) {
|
|
@@ -48,17 +44,18 @@ export class AgentExpert {
|
|
|
48
44
|
continue;
|
|
49
45
|
}
|
|
50
46
|
const prefixedName = `${prefix}${toolName}`;
|
|
51
|
-
exposedTools[prefixedName] = createWrappedTool(tool, toolName, expertId, rateLimiter, config.validateArgs ?? true);
|
|
47
|
+
exposedTools[prefixedName] = createWrappedTool(tool, toolName, expertId, rateLimiter, config.validateArgs ?? true, config.rateLimit?.onLimit ?? 'reject');
|
|
52
48
|
}
|
|
53
|
-
return new AgentExpert(agent, config, exposedTools, originalTools);
|
|
49
|
+
return new AgentExpert(agent, config, exposedTools, originalTools, rateLimiter);
|
|
54
50
|
}
|
|
55
51
|
/**
|
|
56
52
|
* Direct tool call with full request.
|
|
53
|
+
* Note: `from` field is ignored for rate limiting - expert.id is always used.
|
|
57
54
|
*/
|
|
58
55
|
async callTool(request) {
|
|
59
56
|
const requestId = request.requestId ?? generateRequestId();
|
|
60
57
|
const { from = '', tool, args, signal } = request;
|
|
61
|
-
// Create base request for
|
|
58
|
+
// Create base request for responses
|
|
62
59
|
const baseRequest = {
|
|
63
60
|
requestId,
|
|
64
61
|
type: 'request',
|
|
@@ -81,18 +78,10 @@ export class AgentExpert {
|
|
|
81
78
|
if (signal?.aborted) {
|
|
82
79
|
return createA2AError(baseRequest, 'CANCELLED', 'Request was cancelled');
|
|
83
80
|
}
|
|
84
|
-
// Rate limiting
|
|
81
|
+
// Rate limiting (use expert.id, not caller-provided from)
|
|
85
82
|
if (this.rateLimiter) {
|
|
86
83
|
try {
|
|
87
|
-
|
|
88
|
-
if (!allowed) {
|
|
89
|
-
if (this.config.rateLimit?.onLimit === 'reject') {
|
|
90
|
-
throw new RateLimitError(from || this.id, tool, this.rateLimiter.getTimeUntilSlot(from || this.id, tool));
|
|
91
|
-
}
|
|
92
|
-
// Queue mode - wait for slot
|
|
93
|
-
await this.waitForRateLimitSlot(from || this.id, tool);
|
|
94
|
-
}
|
|
95
|
-
this.rateLimiter.track(from || this.id, tool);
|
|
84
|
+
await this.rateLimiter.execute(this.id, tool, async () => { });
|
|
96
85
|
}
|
|
97
86
|
catch (error) {
|
|
98
87
|
if (error instanceof RateLimitError) {
|
|
@@ -130,11 +119,9 @@ export class AgentExpert {
|
|
|
130
119
|
async runTask(request) {
|
|
131
120
|
const requestId = request.requestId ?? generateRequestId();
|
|
132
121
|
const { prompt, signal } = request;
|
|
133
|
-
// Check abort
|
|
134
122
|
if (signal?.aborted) {
|
|
135
123
|
throw new Error('Request was cancelled');
|
|
136
124
|
}
|
|
137
|
-
// Run the agent
|
|
138
125
|
const result = await this.agent.run({ prompt });
|
|
139
126
|
return {
|
|
140
127
|
output: result.text ?? '',
|
|
@@ -147,22 +134,6 @@ export class AgentExpert {
|
|
|
147
134
|
getExposedToolNames() {
|
|
148
135
|
return [...this.config.expose];
|
|
149
136
|
}
|
|
150
|
-
// ========================================================================
|
|
151
|
-
// Private Methods
|
|
152
|
-
// ========================================================================
|
|
153
|
-
async waitForRateLimitSlot(callerId, tool) {
|
|
154
|
-
const maxWait = this.config.rateLimit?.windowMs ?? 60000;
|
|
155
|
-
const checkInterval = 50;
|
|
156
|
-
let waited = 0;
|
|
157
|
-
while (waited < maxWait) {
|
|
158
|
-
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
159
|
-
waited += checkInterval;
|
|
160
|
-
if (this.rateLimiter?.isAllowed(callerId, tool)) {
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
throw new RateLimitError(callerId, tool, 0);
|
|
165
|
-
}
|
|
166
137
|
}
|
|
167
138
|
// ============================================================================
|
|
168
139
|
// Helper Functions
|
|
@@ -170,24 +141,30 @@ export class AgentExpert {
|
|
|
170
141
|
/**
|
|
171
142
|
* Create a wrapped tool preserving all metadata.
|
|
172
143
|
*/
|
|
173
|
-
function createWrappedTool(original, toolName, expertId, rateLimiter, validateArgs) {
|
|
144
|
+
function createWrappedTool(original, toolName, expertId, rateLimiter, validateArgs, onLimit) {
|
|
174
145
|
return {
|
|
175
|
-
// Preserve all metadata
|
|
176
146
|
description: original.description,
|
|
177
147
|
parameters: original.parameters,
|
|
178
148
|
requiresApproval: original.requiresApproval,
|
|
179
149
|
approvalHandler: original.approvalHandler,
|
|
180
150
|
source: original.source,
|
|
181
151
|
execute: async (args, context) => {
|
|
182
|
-
// Rate limiting
|
|
152
|
+
// Rate limiting with proper queue support
|
|
183
153
|
if (rateLimiter) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
154
|
+
if (onLimit === 'queue') {
|
|
155
|
+
// Wait for slot (throws on timeout)
|
|
156
|
+
await rateLimiter.waitForSlot(expertId, toolName);
|
|
157
|
+
rateLimiter.track(expertId, toolName);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
// Reject immediately if not allowed
|
|
161
|
+
if (!rateLimiter.isAllowed(expertId, toolName)) {
|
|
162
|
+
throw new RateLimitError(expertId, toolName, rateLimiter.getTimeUntilSlot(expertId, toolName));
|
|
163
|
+
}
|
|
164
|
+
rateLimiter.track(expertId, toolName);
|
|
187
165
|
}
|
|
188
|
-
rateLimiter.track(callerId, toolName);
|
|
189
166
|
}
|
|
190
|
-
// Validate
|
|
167
|
+
// Validate
|
|
191
168
|
if (validateArgs && original.parameters) {
|
|
192
169
|
const result = validateSchema(args, original.parameters);
|
|
193
170
|
if (!result.valid) {
|
|
@@ -13,26 +13,24 @@ export declare class A2ARateLimiter {
|
|
|
13
13
|
constructor(config: RateLimitConfig);
|
|
14
14
|
/**
|
|
15
15
|
* Check if a call is allowed.
|
|
16
|
-
*
|
|
17
|
-
* @param agentId - The agent making the call
|
|
18
|
-
* @param toolName - The tool being called (used if scope is 'tool')
|
|
19
|
-
* @returns Whether the call is allowed
|
|
20
16
|
*/
|
|
21
17
|
isAllowed(agentId: string, toolName: string): boolean;
|
|
22
18
|
/**
|
|
23
|
-
* Track a call
|
|
19
|
+
* Track a call.
|
|
24
20
|
*/
|
|
25
21
|
track(agentId: string, toolName: string): void;
|
|
26
22
|
/**
|
|
27
23
|
* Execute a call with rate limiting.
|
|
28
24
|
*
|
|
29
|
-
* @
|
|
30
|
-
* @
|
|
31
|
-
* @param execute - The function to execute
|
|
32
|
-
* @returns Result of execution
|
|
33
|
-
* @throws Error if rate limited and onLimit is 'reject'
|
|
25
|
+
* @throws RateLimitError if onLimit is 'reject' and limit exceeded
|
|
26
|
+
* @throws RateLimitError if onLimit is 'queue' and wait times out (strict mode)
|
|
34
27
|
*/
|
|
35
28
|
execute<T>(agentId: string, toolName: string, execute: () => Promise<T>): Promise<T>;
|
|
29
|
+
/**
|
|
30
|
+
* Wait for a slot to become available (for external use).
|
|
31
|
+
* Throws if timeout exceeded.
|
|
32
|
+
*/
|
|
33
|
+
waitForSlot(agentId: string, toolName: string, timeoutMs?: number): Promise<void>;
|
|
36
34
|
/**
|
|
37
35
|
* Get time until next available slot (ms).
|
|
38
36
|
*/
|
|
@@ -50,7 +48,6 @@ export declare class A2ARateLimiter {
|
|
|
50
48
|
private pruneExpired;
|
|
51
49
|
private enqueue;
|
|
52
50
|
private processQueue;
|
|
53
|
-
private waitForSlot;
|
|
54
51
|
}
|
|
55
52
|
export declare class RateLimitError extends Error {
|
|
56
53
|
readonly code: "RATE_LIMITED";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../../src/ai/a2a/rate-limiter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAsB/C;;GAEG;AACH,qBAAa,cAAc;IACvB,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,UAAU,CAAS;gBAEf,MAAM,EAAE,eAAe;IASnC
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../../src/ai/a2a/rate-limiter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAsB/C;;GAEG;AACH,qBAAa,cAAc;IACvB,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,UAAU,CAAS;gBAEf,MAAM,EAAE,eAAe;IASnC;;OAEG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;IAQrD;;OAEG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAM9C;;;;;OAKG;IACG,OAAO,CAAC,CAAC,EACX,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAC1B,OAAO,CAAC,CAAC,CAAC;IAkBb;;;OAGG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBvF;;OAEG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;IAkB3D;;OAEG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IAiB/C;;OAEG;IACH,KAAK,IAAI,IAAI;IASb,OAAO,CAAC,MAAM;IAMd,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,YAAY;YAKN,OAAO;YAiBP,YAAY;CAyB7B;AAMD,qBAAa,cAAe,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAG,cAAc,CAAU;IACxC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;gBAElB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;CAOtE"}
|
|
@@ -24,21 +24,16 @@ class A2ARateLimiter {
|
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* Check if a call is allowed.
|
|
27
|
-
*
|
|
28
|
-
* @param agentId - The agent making the call
|
|
29
|
-
* @param toolName - The tool being called (used if scope is 'tool')
|
|
30
|
-
* @returns Whether the call is allowed
|
|
31
27
|
*/
|
|
32
28
|
isAllowed(agentId, toolName) {
|
|
33
29
|
const key = this.getKey(agentId, toolName);
|
|
34
30
|
const now = Date.now();
|
|
35
31
|
const entry = this.getOrCreateEntry(key);
|
|
36
|
-
// Remove expired timestamps
|
|
37
32
|
this.pruneExpired(entry, now);
|
|
38
33
|
return entry.timestamps.length < this.config.maxCalls;
|
|
39
34
|
}
|
|
40
35
|
/**
|
|
41
|
-
* Track a call
|
|
36
|
+
* Track a call.
|
|
42
37
|
*/
|
|
43
38
|
track(agentId, toolName) {
|
|
44
39
|
const key = this.getKey(agentId, toolName);
|
|
@@ -48,11 +43,8 @@ class A2ARateLimiter {
|
|
|
48
43
|
/**
|
|
49
44
|
* Execute a call with rate limiting.
|
|
50
45
|
*
|
|
51
|
-
* @
|
|
52
|
-
* @
|
|
53
|
-
* @param execute - The function to execute
|
|
54
|
-
* @returns Result of execution
|
|
55
|
-
* @throws Error if rate limited and onLimit is 'reject'
|
|
46
|
+
* @throws RateLimitError if onLimit is 'reject' and limit exceeded
|
|
47
|
+
* @throws RateLimitError if onLimit is 'queue' and wait times out (strict mode)
|
|
56
48
|
*/
|
|
57
49
|
async execute(agentId, toolName, execute) {
|
|
58
50
|
if (this.isAllowed(agentId, toolName)) {
|
|
@@ -62,9 +54,26 @@ class A2ARateLimiter {
|
|
|
62
54
|
if (this.config.onLimit === 'reject') {
|
|
63
55
|
throw new RateLimitError(agentId, toolName, this.getTimeUntilSlot(agentId, toolName));
|
|
64
56
|
}
|
|
65
|
-
// Queue
|
|
57
|
+
// Queue mode (strict): wait for slot or throw
|
|
66
58
|
return this.enqueue(agentId, toolName, execute);
|
|
67
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Wait for a slot to become available (for external use).
|
|
62
|
+
* Throws if timeout exceeded.
|
|
63
|
+
*/
|
|
64
|
+
async waitForSlot(agentId, toolName, timeoutMs) {
|
|
65
|
+
const timeout = timeoutMs ?? this.config.windowMs * 2;
|
|
66
|
+
const checkInterval = 50;
|
|
67
|
+
let waited = 0;
|
|
68
|
+
while (waited < timeout) {
|
|
69
|
+
if (this.isAllowed(agentId, toolName)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
73
|
+
waited += checkInterval;
|
|
74
|
+
}
|
|
75
|
+
throw new RateLimitError(agentId, toolName, this.getTimeUntilSlot(agentId, toolName));
|
|
76
|
+
}
|
|
68
77
|
/**
|
|
69
78
|
* Get time until next available slot (ms).
|
|
70
79
|
*/
|
|
@@ -79,7 +88,6 @@ class A2ARateLimiter {
|
|
|
79
88
|
if (entry.timestamps.length < this.config.maxCalls) {
|
|
80
89
|
return 0;
|
|
81
90
|
}
|
|
82
|
-
// Time until oldest timestamp expires
|
|
83
91
|
const oldest = entry.timestamps[0];
|
|
84
92
|
return Math.max(0, oldest + this.config.windowMs - now);
|
|
85
93
|
}
|
|
@@ -89,11 +97,9 @@ class A2ARateLimiter {
|
|
|
89
97
|
reset(agentId, toolName) {
|
|
90
98
|
if (this.config.scope === 'tool') {
|
|
91
99
|
if (toolName) {
|
|
92
|
-
// Reset specific tool
|
|
93
100
|
this.entries.delete(this.getKey(agentId, toolName));
|
|
94
101
|
}
|
|
95
102
|
else {
|
|
96
|
-
// Reset all tools for this agent
|
|
97
103
|
const prefix = `${agentId}:`;
|
|
98
104
|
for (const key of [...this.entries.keys()]) {
|
|
99
105
|
if (key.startsWith(prefix)) {
|
|
@@ -103,7 +109,6 @@ class A2ARateLimiter {
|
|
|
103
109
|
}
|
|
104
110
|
}
|
|
105
111
|
else {
|
|
106
|
-
// scope === 'agent': key is just agentId
|
|
107
112
|
this.entries.delete(agentId);
|
|
108
113
|
}
|
|
109
114
|
}
|
|
@@ -115,7 +120,7 @@ class A2ARateLimiter {
|
|
|
115
120
|
this.queue = [];
|
|
116
121
|
}
|
|
117
122
|
// ========================================================================
|
|
118
|
-
// Private
|
|
123
|
+
// Private
|
|
119
124
|
// ========================================================================
|
|
120
125
|
getKey(agentId, toolName) {
|
|
121
126
|
return this.config.scope === 'tool'
|
|
@@ -153,35 +158,21 @@ class A2ARateLimiter {
|
|
|
153
158
|
this.processing = true;
|
|
154
159
|
while (this.queue.length > 0) {
|
|
155
160
|
const call = this.queue[0];
|
|
156
|
-
// Wait until slot is available for this specific call
|
|
157
|
-
await this.waitForSlot(call.agentId, call.toolName);
|
|
158
|
-
// Track before execution (count it)
|
|
159
|
-
this.track(call.agentId, call.toolName);
|
|
160
|
-
this.queue.shift();
|
|
161
161
|
try {
|
|
162
|
+
// Strict wait - throws on timeout
|
|
163
|
+
await this.waitForSlot(call.agentId, call.toolName);
|
|
164
|
+
this.track(call.agentId, call.toolName);
|
|
165
|
+
this.queue.shift();
|
|
162
166
|
const result = await call.execute();
|
|
163
167
|
call.resolve(result);
|
|
164
168
|
}
|
|
165
169
|
catch (error) {
|
|
170
|
+
this.queue.shift();
|
|
166
171
|
call.reject(error);
|
|
167
172
|
}
|
|
168
173
|
}
|
|
169
174
|
this.processing = false;
|
|
170
175
|
}
|
|
171
|
-
async waitForSlot(agentId, toolName) {
|
|
172
|
-
const checkInterval = 50;
|
|
173
|
-
const maxWait = this.config.windowMs * 2;
|
|
174
|
-
let waited = 0;
|
|
175
|
-
while (waited < maxWait) {
|
|
176
|
-
if (this.isAllowed(agentId, toolName)) {
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
180
|
-
waited += checkInterval;
|
|
181
|
-
}
|
|
182
|
-
// Timeout - still execute but may exceed limit
|
|
183
|
-
console.warn(`[A2ARateLimiter] Queue timeout for ${agentId}:${toolName}`);
|
|
184
|
-
}
|
|
185
176
|
}
|
|
186
177
|
exports.A2ARateLimiter = A2ARateLimiter;
|
|
187
178
|
// ============================================================================
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../../src/ai/a2a/rate-limiter.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AAoBH,+EAA+E;AAC/E,eAAe;AACf,+EAA+E;AAE/E;;GAEG;AACH,MAAa,cAAc;IAMvB,YAAY,MAAuB;QAJ3B,YAAO,GAAG,IAAI,GAAG,EAA0B,CAAC;QAC5C,UAAK,GAA0B,EAAE,CAAC;QAClC,eAAU,GAAG,KAAK,CAAC;QAGvB,IAAI,CAAC,MAAM,GAAG;YACV,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,OAAO,EAAE,MAAM,CAAC,OAAO;SAC1B,CAAC;IACN,CAAC;IAED
|
|
1
|
+
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../../src/ai/a2a/rate-limiter.ts"],"names":[],"mappings":"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"}
|
|
@@ -21,21 +21,16 @@ export class A2ARateLimiter {
|
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
23
|
* Check if a call is allowed.
|
|
24
|
-
*
|
|
25
|
-
* @param agentId - The agent making the call
|
|
26
|
-
* @param toolName - The tool being called (used if scope is 'tool')
|
|
27
|
-
* @returns Whether the call is allowed
|
|
28
24
|
*/
|
|
29
25
|
isAllowed(agentId, toolName) {
|
|
30
26
|
const key = this.getKey(agentId, toolName);
|
|
31
27
|
const now = Date.now();
|
|
32
28
|
const entry = this.getOrCreateEntry(key);
|
|
33
|
-
// Remove expired timestamps
|
|
34
29
|
this.pruneExpired(entry, now);
|
|
35
30
|
return entry.timestamps.length < this.config.maxCalls;
|
|
36
31
|
}
|
|
37
32
|
/**
|
|
38
|
-
* Track a call
|
|
33
|
+
* Track a call.
|
|
39
34
|
*/
|
|
40
35
|
track(agentId, toolName) {
|
|
41
36
|
const key = this.getKey(agentId, toolName);
|
|
@@ -45,11 +40,8 @@ export class A2ARateLimiter {
|
|
|
45
40
|
/**
|
|
46
41
|
* Execute a call with rate limiting.
|
|
47
42
|
*
|
|
48
|
-
* @
|
|
49
|
-
* @
|
|
50
|
-
* @param execute - The function to execute
|
|
51
|
-
* @returns Result of execution
|
|
52
|
-
* @throws Error if rate limited and onLimit is 'reject'
|
|
43
|
+
* @throws RateLimitError if onLimit is 'reject' and limit exceeded
|
|
44
|
+
* @throws RateLimitError if onLimit is 'queue' and wait times out (strict mode)
|
|
53
45
|
*/
|
|
54
46
|
async execute(agentId, toolName, execute) {
|
|
55
47
|
if (this.isAllowed(agentId, toolName)) {
|
|
@@ -59,9 +51,26 @@ export class A2ARateLimiter {
|
|
|
59
51
|
if (this.config.onLimit === 'reject') {
|
|
60
52
|
throw new RateLimitError(agentId, toolName, this.getTimeUntilSlot(agentId, toolName));
|
|
61
53
|
}
|
|
62
|
-
// Queue
|
|
54
|
+
// Queue mode (strict): wait for slot or throw
|
|
63
55
|
return this.enqueue(agentId, toolName, execute);
|
|
64
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Wait for a slot to become available (for external use).
|
|
59
|
+
* Throws if timeout exceeded.
|
|
60
|
+
*/
|
|
61
|
+
async waitForSlot(agentId, toolName, timeoutMs) {
|
|
62
|
+
const timeout = timeoutMs ?? this.config.windowMs * 2;
|
|
63
|
+
const checkInterval = 50;
|
|
64
|
+
let waited = 0;
|
|
65
|
+
while (waited < timeout) {
|
|
66
|
+
if (this.isAllowed(agentId, toolName)) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
70
|
+
waited += checkInterval;
|
|
71
|
+
}
|
|
72
|
+
throw new RateLimitError(agentId, toolName, this.getTimeUntilSlot(agentId, toolName));
|
|
73
|
+
}
|
|
65
74
|
/**
|
|
66
75
|
* Get time until next available slot (ms).
|
|
67
76
|
*/
|
|
@@ -76,7 +85,6 @@ export class A2ARateLimiter {
|
|
|
76
85
|
if (entry.timestamps.length < this.config.maxCalls) {
|
|
77
86
|
return 0;
|
|
78
87
|
}
|
|
79
|
-
// Time until oldest timestamp expires
|
|
80
88
|
const oldest = entry.timestamps[0];
|
|
81
89
|
return Math.max(0, oldest + this.config.windowMs - now);
|
|
82
90
|
}
|
|
@@ -86,11 +94,9 @@ export class A2ARateLimiter {
|
|
|
86
94
|
reset(agentId, toolName) {
|
|
87
95
|
if (this.config.scope === 'tool') {
|
|
88
96
|
if (toolName) {
|
|
89
|
-
// Reset specific tool
|
|
90
97
|
this.entries.delete(this.getKey(agentId, toolName));
|
|
91
98
|
}
|
|
92
99
|
else {
|
|
93
|
-
// Reset all tools for this agent
|
|
94
100
|
const prefix = `${agentId}:`;
|
|
95
101
|
for (const key of [...this.entries.keys()]) {
|
|
96
102
|
if (key.startsWith(prefix)) {
|
|
@@ -100,7 +106,6 @@ export class A2ARateLimiter {
|
|
|
100
106
|
}
|
|
101
107
|
}
|
|
102
108
|
else {
|
|
103
|
-
// scope === 'agent': key is just agentId
|
|
104
109
|
this.entries.delete(agentId);
|
|
105
110
|
}
|
|
106
111
|
}
|
|
@@ -112,7 +117,7 @@ export class A2ARateLimiter {
|
|
|
112
117
|
this.queue = [];
|
|
113
118
|
}
|
|
114
119
|
// ========================================================================
|
|
115
|
-
// Private
|
|
120
|
+
// Private
|
|
116
121
|
// ========================================================================
|
|
117
122
|
getKey(agentId, toolName) {
|
|
118
123
|
return this.config.scope === 'tool'
|
|
@@ -150,35 +155,21 @@ export class A2ARateLimiter {
|
|
|
150
155
|
this.processing = true;
|
|
151
156
|
while (this.queue.length > 0) {
|
|
152
157
|
const call = this.queue[0];
|
|
153
|
-
// Wait until slot is available for this specific call
|
|
154
|
-
await this.waitForSlot(call.agentId, call.toolName);
|
|
155
|
-
// Track before execution (count it)
|
|
156
|
-
this.track(call.agentId, call.toolName);
|
|
157
|
-
this.queue.shift();
|
|
158
158
|
try {
|
|
159
|
+
// Strict wait - throws on timeout
|
|
160
|
+
await this.waitForSlot(call.agentId, call.toolName);
|
|
161
|
+
this.track(call.agentId, call.toolName);
|
|
162
|
+
this.queue.shift();
|
|
159
163
|
const result = await call.execute();
|
|
160
164
|
call.resolve(result);
|
|
161
165
|
}
|
|
162
166
|
catch (error) {
|
|
167
|
+
this.queue.shift();
|
|
163
168
|
call.reject(error);
|
|
164
169
|
}
|
|
165
170
|
}
|
|
166
171
|
this.processing = false;
|
|
167
172
|
}
|
|
168
|
-
async waitForSlot(agentId, toolName) {
|
|
169
|
-
const checkInterval = 50;
|
|
170
|
-
const maxWait = this.config.windowMs * 2;
|
|
171
|
-
let waited = 0;
|
|
172
|
-
while (waited < maxWait) {
|
|
173
|
-
if (this.isAllowed(agentId, toolName)) {
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
177
|
-
waited += checkInterval;
|
|
178
|
-
}
|
|
179
|
-
// Timeout - still execute but may exceed limit
|
|
180
|
-
console.warn(`[A2ARateLimiter] Queue timeout for ${agentId}:${toolName}`);
|
|
181
|
-
}
|
|
182
173
|
}
|
|
183
174
|
// ============================================================================
|
|
184
175
|
// Errors
|
package/package.json
CHANGED