@bowenqt/qiniu-ai-sdk 0.28.0 → 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 +53 -50
- package/dist/ai/a2a/expert.js.map +1 -1
- package/dist/ai/a2a/expert.mjs +54 -51
- package/dist/ai/a2a/rate-limiter.d.ts +9 -12
- package/dist/ai/a2a/rate-limiter.d.ts.map +1 -1
- package/dist/ai/a2a/rate-limiter.js +45 -45
- package/dist/ai/a2a/rate-limiter.js.map +1 -1
- package/dist/ai/a2a/rate-limiter.mjs +45 -45
- package/dist/ai/a2a/types.d.ts +2 -0
- package/dist/ai/a2a/types.d.ts.map +1 -1
- package/dist/ai/a2a/types.js.map +1 -1
- 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,19 +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}_`;
|
|
36
|
+
const expertId = agent.id;
|
|
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 = {};
|
|
41
43
|
for (const toolName of config.expose) {
|
|
42
44
|
const tool = originalTools[toolName];
|
|
43
45
|
if (!tool) {
|
|
@@ -45,45 +47,48 @@ class AgentExpert {
|
|
|
45
47
|
continue;
|
|
46
48
|
}
|
|
47
49
|
const prefixedName = `${prefix}${toolName}`;
|
|
48
|
-
exposedTools[prefixedName] = createWrappedTool(tool, config.validateArgs ?? true);
|
|
50
|
+
exposedTools[prefixedName] = createWrappedTool(tool, toolName, expertId, rateLimiter, config.validateArgs ?? true, config.rateLimit?.onLimit ?? 'reject');
|
|
49
51
|
}
|
|
50
|
-
return new AgentExpert(agent, config, exposedTools, originalTools);
|
|
52
|
+
return new AgentExpert(agent, config, exposedTools, originalTools, rateLimiter);
|
|
51
53
|
}
|
|
52
54
|
/**
|
|
53
55
|
* Direct tool call with full request.
|
|
56
|
+
* Note: `from` field is ignored for rate limiting - expert.id is always used.
|
|
54
57
|
*/
|
|
55
58
|
async callTool(request) {
|
|
56
59
|
const requestId = request.requestId ?? (0, types_1.generateRequestId)();
|
|
57
|
-
const { tool, args, signal } = request;
|
|
60
|
+
const { from = '', tool, args, signal } = request;
|
|
61
|
+
// Create base request for responses
|
|
62
|
+
const baseRequest = {
|
|
63
|
+
requestId,
|
|
64
|
+
type: 'request',
|
|
65
|
+
from,
|
|
66
|
+
to: this.id,
|
|
67
|
+
timestamp: Date.now(),
|
|
68
|
+
tool,
|
|
69
|
+
args,
|
|
70
|
+
};
|
|
58
71
|
// Check if tool is exposed
|
|
59
72
|
if (!this.config.expose.includes(tool)) {
|
|
60
|
-
return (0, types_1.createA2AError)(
|
|
73
|
+
return (0, types_1.createA2AError)(baseRequest, 'TOOL_NOT_EXPOSED', `Tool "${tool}" is not exposed by this expert`);
|
|
61
74
|
}
|
|
62
75
|
// Get original tool
|
|
63
76
|
const originalTool = this.originalTools[tool];
|
|
64
77
|
if (!originalTool) {
|
|
65
|
-
return (0, types_1.createA2AError)(
|
|
78
|
+
return (0, types_1.createA2AError)(baseRequest, 'TOOL_NOT_FOUND', `Tool "${tool}" not found`);
|
|
66
79
|
}
|
|
67
80
|
// Check abort signal
|
|
68
81
|
if (signal?.aborted) {
|
|
69
|
-
return (0, types_1.createA2AError)(
|
|
82
|
+
return (0, types_1.createA2AError)(baseRequest, 'CANCELLED', 'Request was cancelled');
|
|
70
83
|
}
|
|
71
|
-
// Rate limiting
|
|
84
|
+
// Rate limiting (use expert.id, not caller-provided from)
|
|
72
85
|
if (this.rateLimiter) {
|
|
73
86
|
try {
|
|
74
|
-
|
|
75
|
-
if (!allowed) {
|
|
76
|
-
if (this.config.rateLimit?.onLimit === 'reject') {
|
|
77
|
-
throw new rate_limiter_1.RateLimitError(this.id, tool, this.rateLimiter.getTimeUntilSlot(this.id, tool));
|
|
78
|
-
}
|
|
79
|
-
// Queue mode - wait for slot
|
|
80
|
-
await this.waitForRateLimitSlot(tool);
|
|
81
|
-
}
|
|
82
|
-
this.rateLimiter.track(this.id, tool);
|
|
87
|
+
await this.rateLimiter.execute(this.id, tool, async () => { });
|
|
83
88
|
}
|
|
84
89
|
catch (error) {
|
|
85
90
|
if (error instanceof rate_limiter_1.RateLimitError) {
|
|
86
|
-
return (0, types_1.createA2AError)(
|
|
91
|
+
return (0, types_1.createA2AError)(baseRequest, 'RATE_LIMITED', error.message);
|
|
87
92
|
}
|
|
88
93
|
throw error;
|
|
89
94
|
}
|
|
@@ -92,7 +97,7 @@ class AgentExpert {
|
|
|
92
97
|
if (this.config.validateArgs && originalTool.parameters) {
|
|
93
98
|
const validationResult = (0, validation_1.validateSchema)(args, originalTool.parameters);
|
|
94
99
|
if (!validationResult.valid) {
|
|
95
|
-
return (0, types_1.createA2AError)(
|
|
100
|
+
return (0, types_1.createA2AError)(baseRequest, 'VALIDATION_ERROR', validationResult.error?.message ?? 'Validation failed');
|
|
96
101
|
}
|
|
97
102
|
}
|
|
98
103
|
// Execute tool
|
|
@@ -105,12 +110,10 @@ class AgentExpert {
|
|
|
105
110
|
messages: [],
|
|
106
111
|
abortSignal: signal,
|
|
107
112
|
});
|
|
108
|
-
|
|
109
|
-
requestMsg.requestId = requestId;
|
|
110
|
-
return (0, types_1.createA2AResponse)(requestMsg, result);
|
|
113
|
+
return (0, types_1.createA2AResponse)(baseRequest, result);
|
|
111
114
|
}
|
|
112
115
|
catch (error) {
|
|
113
|
-
return (0, types_1.createA2AError)(
|
|
116
|
+
return (0, types_1.createA2AError)(baseRequest, 'EXECUTION_ERROR', error instanceof Error ? error.message : String(error), error instanceof Error ? error.stack : undefined);
|
|
114
117
|
}
|
|
115
118
|
}
|
|
116
119
|
/**
|
|
@@ -119,11 +122,9 @@ class AgentExpert {
|
|
|
119
122
|
async runTask(request) {
|
|
120
123
|
const requestId = request.requestId ?? (0, types_1.generateRequestId)();
|
|
121
124
|
const { prompt, signal } = request;
|
|
122
|
-
// Check abort
|
|
123
125
|
if (signal?.aborted) {
|
|
124
126
|
throw new Error('Request was cancelled');
|
|
125
127
|
}
|
|
126
|
-
// Run the agent
|
|
127
128
|
const result = await this.agent.run({ prompt });
|
|
128
129
|
return {
|
|
129
130
|
output: result.text ?? '',
|
|
@@ -136,36 +137,38 @@ class AgentExpert {
|
|
|
136
137
|
getExposedToolNames() {
|
|
137
138
|
return [...this.config.expose];
|
|
138
139
|
}
|
|
139
|
-
// ========================================================================
|
|
140
|
-
// Private Methods
|
|
141
|
-
// ========================================================================
|
|
142
|
-
async waitForRateLimitSlot(tool) {
|
|
143
|
-
const maxWait = this.config.rateLimit?.windowMs ?? 60000;
|
|
144
|
-
const checkInterval = 100;
|
|
145
|
-
let waited = 0;
|
|
146
|
-
while (waited < maxWait) {
|
|
147
|
-
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
148
|
-
waited += checkInterval;
|
|
149
|
-
if (this.rateLimiter?.isAllowed(this.id, tool)) {
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
throw new rate_limiter_1.RateLimitError(this.id, tool, 0);
|
|
154
|
-
}
|
|
155
140
|
}
|
|
156
141
|
exports.AgentExpert = AgentExpert;
|
|
157
142
|
// ============================================================================
|
|
158
143
|
// Helper Functions
|
|
159
144
|
// ============================================================================
|
|
160
145
|
/**
|
|
161
|
-
* Create a wrapped tool
|
|
146
|
+
* Create a wrapped tool preserving all metadata.
|
|
162
147
|
*/
|
|
163
|
-
function createWrappedTool(original, validateArgs) {
|
|
148
|
+
function createWrappedTool(original, toolName, expertId, rateLimiter, validateArgs, onLimit) {
|
|
164
149
|
return {
|
|
165
150
|
description: original.description,
|
|
166
151
|
parameters: original.parameters,
|
|
152
|
+
requiresApproval: original.requiresApproval,
|
|
153
|
+
approvalHandler: original.approvalHandler,
|
|
154
|
+
source: original.source,
|
|
167
155
|
execute: async (args, context) => {
|
|
168
|
-
//
|
|
156
|
+
// Rate limiting with proper queue support
|
|
157
|
+
if (rateLimiter) {
|
|
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);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Validate
|
|
169
172
|
if (validateArgs && original.parameters) {
|
|
170
173
|
const result = (0, validation_1.validateSchema)(args, original.parameters);
|
|
171
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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AgentExpert - Expose agent tools for A2A collaboration.
|
|
3
3
|
*/
|
|
4
|
-
import { generateRequestId,
|
|
4
|
+
import { generateRequestId, createA2AResponse, createA2AError, } from './types.mjs';
|
|
5
5
|
import { validateSchema } from './validation.mjs';
|
|
6
6
|
import { A2ARateLimiter, RateLimitError } from './rate-limiter.mjs';
|
|
7
7
|
// ============================================================================
|
|
@@ -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,19 +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}_`;
|
|
33
|
+
const expertId = agent.id;
|
|
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 = {};
|
|
38
40
|
for (const toolName of config.expose) {
|
|
39
41
|
const tool = originalTools[toolName];
|
|
40
42
|
if (!tool) {
|
|
@@ -42,45 +44,48 @@ export class AgentExpert {
|
|
|
42
44
|
continue;
|
|
43
45
|
}
|
|
44
46
|
const prefixedName = `${prefix}${toolName}`;
|
|
45
|
-
exposedTools[prefixedName] = createWrappedTool(tool, config.validateArgs ?? true);
|
|
47
|
+
exposedTools[prefixedName] = createWrappedTool(tool, toolName, expertId, rateLimiter, config.validateArgs ?? true, config.rateLimit?.onLimit ?? 'reject');
|
|
46
48
|
}
|
|
47
|
-
return new AgentExpert(agent, config, exposedTools, originalTools);
|
|
49
|
+
return new AgentExpert(agent, config, exposedTools, originalTools, rateLimiter);
|
|
48
50
|
}
|
|
49
51
|
/**
|
|
50
52
|
* Direct tool call with full request.
|
|
53
|
+
* Note: `from` field is ignored for rate limiting - expert.id is always used.
|
|
51
54
|
*/
|
|
52
55
|
async callTool(request) {
|
|
53
56
|
const requestId = request.requestId ?? generateRequestId();
|
|
54
|
-
const { tool, args, signal } = request;
|
|
57
|
+
const { from = '', tool, args, signal } = request;
|
|
58
|
+
// Create base request for responses
|
|
59
|
+
const baseRequest = {
|
|
60
|
+
requestId,
|
|
61
|
+
type: 'request',
|
|
62
|
+
from,
|
|
63
|
+
to: this.id,
|
|
64
|
+
timestamp: Date.now(),
|
|
65
|
+
tool,
|
|
66
|
+
args,
|
|
67
|
+
};
|
|
55
68
|
// Check if tool is exposed
|
|
56
69
|
if (!this.config.expose.includes(tool)) {
|
|
57
|
-
return createA2AError(
|
|
70
|
+
return createA2AError(baseRequest, 'TOOL_NOT_EXPOSED', `Tool "${tool}" is not exposed by this expert`);
|
|
58
71
|
}
|
|
59
72
|
// Get original tool
|
|
60
73
|
const originalTool = this.originalTools[tool];
|
|
61
74
|
if (!originalTool) {
|
|
62
|
-
return createA2AError(
|
|
75
|
+
return createA2AError(baseRequest, 'TOOL_NOT_FOUND', `Tool "${tool}" not found`);
|
|
63
76
|
}
|
|
64
77
|
// Check abort signal
|
|
65
78
|
if (signal?.aborted) {
|
|
66
|
-
return createA2AError(
|
|
79
|
+
return createA2AError(baseRequest, 'CANCELLED', 'Request was cancelled');
|
|
67
80
|
}
|
|
68
|
-
// Rate limiting
|
|
81
|
+
// Rate limiting (use expert.id, not caller-provided from)
|
|
69
82
|
if (this.rateLimiter) {
|
|
70
83
|
try {
|
|
71
|
-
|
|
72
|
-
if (!allowed) {
|
|
73
|
-
if (this.config.rateLimit?.onLimit === 'reject') {
|
|
74
|
-
throw new RateLimitError(this.id, tool, this.rateLimiter.getTimeUntilSlot(this.id, tool));
|
|
75
|
-
}
|
|
76
|
-
// Queue mode - wait for slot
|
|
77
|
-
await this.waitForRateLimitSlot(tool);
|
|
78
|
-
}
|
|
79
|
-
this.rateLimiter.track(this.id, tool);
|
|
84
|
+
await this.rateLimiter.execute(this.id, tool, async () => { });
|
|
80
85
|
}
|
|
81
86
|
catch (error) {
|
|
82
87
|
if (error instanceof RateLimitError) {
|
|
83
|
-
return createA2AError(
|
|
88
|
+
return createA2AError(baseRequest, 'RATE_LIMITED', error.message);
|
|
84
89
|
}
|
|
85
90
|
throw error;
|
|
86
91
|
}
|
|
@@ -89,7 +94,7 @@ export class AgentExpert {
|
|
|
89
94
|
if (this.config.validateArgs && originalTool.parameters) {
|
|
90
95
|
const validationResult = validateSchema(args, originalTool.parameters);
|
|
91
96
|
if (!validationResult.valid) {
|
|
92
|
-
return createA2AError(
|
|
97
|
+
return createA2AError(baseRequest, 'VALIDATION_ERROR', validationResult.error?.message ?? 'Validation failed');
|
|
93
98
|
}
|
|
94
99
|
}
|
|
95
100
|
// Execute tool
|
|
@@ -102,12 +107,10 @@ export class AgentExpert {
|
|
|
102
107
|
messages: [],
|
|
103
108
|
abortSignal: signal,
|
|
104
109
|
});
|
|
105
|
-
|
|
106
|
-
requestMsg.requestId = requestId;
|
|
107
|
-
return createA2AResponse(requestMsg, result);
|
|
110
|
+
return createA2AResponse(baseRequest, result);
|
|
108
111
|
}
|
|
109
112
|
catch (error) {
|
|
110
|
-
return createA2AError(
|
|
113
|
+
return createA2AError(baseRequest, 'EXECUTION_ERROR', error instanceof Error ? error.message : String(error), error instanceof Error ? error.stack : undefined);
|
|
111
114
|
}
|
|
112
115
|
}
|
|
113
116
|
/**
|
|
@@ -116,11 +119,9 @@ export class AgentExpert {
|
|
|
116
119
|
async runTask(request) {
|
|
117
120
|
const requestId = request.requestId ?? generateRequestId();
|
|
118
121
|
const { prompt, signal } = request;
|
|
119
|
-
// Check abort
|
|
120
122
|
if (signal?.aborted) {
|
|
121
123
|
throw new Error('Request was cancelled');
|
|
122
124
|
}
|
|
123
|
-
// Run the agent
|
|
124
125
|
const result = await this.agent.run({ prompt });
|
|
125
126
|
return {
|
|
126
127
|
output: result.text ?? '',
|
|
@@ -133,35 +134,37 @@ export class AgentExpert {
|
|
|
133
134
|
getExposedToolNames() {
|
|
134
135
|
return [...this.config.expose];
|
|
135
136
|
}
|
|
136
|
-
// ========================================================================
|
|
137
|
-
// Private Methods
|
|
138
|
-
// ========================================================================
|
|
139
|
-
async waitForRateLimitSlot(tool) {
|
|
140
|
-
const maxWait = this.config.rateLimit?.windowMs ?? 60000;
|
|
141
|
-
const checkInterval = 100;
|
|
142
|
-
let waited = 0;
|
|
143
|
-
while (waited < maxWait) {
|
|
144
|
-
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
145
|
-
waited += checkInterval;
|
|
146
|
-
if (this.rateLimiter?.isAllowed(this.id, tool)) {
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
throw new RateLimitError(this.id, tool, 0);
|
|
151
|
-
}
|
|
152
137
|
}
|
|
153
138
|
// ============================================================================
|
|
154
139
|
// Helper Functions
|
|
155
140
|
// ============================================================================
|
|
156
141
|
/**
|
|
157
|
-
* Create a wrapped tool
|
|
142
|
+
* Create a wrapped tool preserving all metadata.
|
|
158
143
|
*/
|
|
159
|
-
function createWrappedTool(original, validateArgs) {
|
|
144
|
+
function createWrappedTool(original, toolName, expertId, rateLimiter, validateArgs, onLimit) {
|
|
160
145
|
return {
|
|
161
146
|
description: original.description,
|
|
162
147
|
parameters: original.parameters,
|
|
148
|
+
requiresApproval: original.requiresApproval,
|
|
149
|
+
approvalHandler: original.approvalHandler,
|
|
150
|
+
source: original.source,
|
|
163
151
|
execute: async (args, context) => {
|
|
164
|
-
//
|
|
152
|
+
// Rate limiting with proper queue support
|
|
153
|
+
if (rateLimiter) {
|
|
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);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Validate
|
|
165
168
|
if (validateArgs && original.parameters) {
|
|
166
169
|
const result = validateSchema(args, original.parameters);
|
|
167
170
|
if (!result.valid) {
|
|
@@ -12,27 +12,25 @@ export declare class A2ARateLimiter {
|
|
|
12
12
|
private processing;
|
|
13
13
|
constructor(config: RateLimitConfig);
|
|
14
14
|
/**
|
|
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
|
|
15
|
+
* Check if a 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;
|
|
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"}
|
|
@@ -23,22 +23,17 @@ class A2ARateLimiter {
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
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
|
|
26
|
+
* Check if a 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,8 +54,25 @@ class A2ARateLimiter {
|
|
|
62
54
|
if (this.config.onLimit === 'reject') {
|
|
63
55
|
throw new RateLimitError(agentId, toolName, this.getTimeUntilSlot(agentId, toolName));
|
|
64
56
|
}
|
|
65
|
-
// Queue
|
|
66
|
-
return this.enqueue(execute);
|
|
57
|
+
// Queue mode (strict): wait for slot or throw
|
|
58
|
+
return this.enqueue(agentId, toolName, execute);
|
|
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));
|
|
67
76
|
}
|
|
68
77
|
/**
|
|
69
78
|
* Get time until next available slot (ms).
|
|
@@ -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
|
}
|
|
@@ -87,17 +95,22 @@ class A2ARateLimiter {
|
|
|
87
95
|
* Reset rate limit for an agent/tool.
|
|
88
96
|
*/
|
|
89
97
|
reset(agentId, toolName) {
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
if (this.config.scope === 'tool') {
|
|
99
|
+
if (toolName) {
|
|
100
|
+
this.entries.delete(this.getKey(agentId, toolName));
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
const prefix = `${agentId}:`;
|
|
104
|
+
for (const key of [...this.entries.keys()]) {
|
|
105
|
+
if (key.startsWith(prefix)) {
|
|
106
|
+
this.entries.delete(key);
|
|
107
|
+
}
|
|
98
108
|
}
|
|
99
109
|
}
|
|
100
110
|
}
|
|
111
|
+
else {
|
|
112
|
+
this.entries.delete(agentId);
|
|
113
|
+
}
|
|
101
114
|
}
|
|
102
115
|
/**
|
|
103
116
|
* Clear all rate limit data.
|
|
@@ -107,7 +120,7 @@ class A2ARateLimiter {
|
|
|
107
120
|
this.queue = [];
|
|
108
121
|
}
|
|
109
122
|
// ========================================================================
|
|
110
|
-
// Private
|
|
123
|
+
// Private
|
|
111
124
|
// ========================================================================
|
|
112
125
|
getKey(agentId, toolName) {
|
|
113
126
|
return this.config.scope === 'tool'
|
|
@@ -126,9 +139,11 @@ class A2ARateLimiter {
|
|
|
126
139
|
const cutoff = now - this.config.windowMs;
|
|
127
140
|
entry.timestamps = entry.timestamps.filter(t => t > cutoff);
|
|
128
141
|
}
|
|
129
|
-
async enqueue(execute) {
|
|
142
|
+
async enqueue(agentId, toolName, execute) {
|
|
130
143
|
return new Promise((resolve, reject) => {
|
|
131
144
|
this.queue.push({
|
|
145
|
+
agentId,
|
|
146
|
+
toolName,
|
|
132
147
|
execute: execute,
|
|
133
148
|
resolve: resolve,
|
|
134
149
|
reject,
|
|
@@ -143,36 +158,21 @@ class A2ARateLimiter {
|
|
|
143
158
|
this.processing = true;
|
|
144
159
|
while (this.queue.length > 0) {
|
|
145
160
|
const call = this.queue[0];
|
|
146
|
-
// Wait until we have a slot
|
|
147
|
-
// We use a simple "any agent" check since we don't have agent info in queue
|
|
148
|
-
await this.waitForSlot();
|
|
149
|
-
this.queue.shift();
|
|
150
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();
|
|
151
166
|
const result = await call.execute();
|
|
152
167
|
call.resolve(result);
|
|
153
168
|
}
|
|
154
169
|
catch (error) {
|
|
170
|
+
this.queue.shift();
|
|
155
171
|
call.reject(error);
|
|
156
172
|
}
|
|
157
173
|
}
|
|
158
174
|
this.processing = false;
|
|
159
175
|
}
|
|
160
|
-
async waitForSlot() {
|
|
161
|
-
// Simple exponential backoff check
|
|
162
|
-
const checkInterval = 100; // 100ms
|
|
163
|
-
const maxWait = this.config.windowMs;
|
|
164
|
-
let waited = 0;
|
|
165
|
-
while (waited < maxWait) {
|
|
166
|
-
// Check if any slot is available
|
|
167
|
-
// Since we don't have context, wait for window to pass
|
|
168
|
-
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
169
|
-
waited += checkInterval;
|
|
170
|
-
// If we've waited long enough for at least one slot to free up
|
|
171
|
-
if (waited >= this.config.windowMs) {
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
176
|
}
|
|
177
177
|
exports.A2ARateLimiter = A2ARateLimiter;
|
|
178
178
|
// ============================================================================
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../../src/ai/a2a/rate-limiter.ts"],"names":[],"mappings":";AAAA;;GAEG;;;
|
|
1
|
+
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../../src/ai/a2a/rate-limiter.ts"],"names":[],"mappings":"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"}
|
|
@@ -20,22 +20,17 @@ export class A2ARateLimiter {
|
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
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
|
|
23
|
+
* Check if a 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,8 +51,25 @@ 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
|
|
63
|
-
return this.enqueue(execute);
|
|
54
|
+
// Queue mode (strict): wait for slot or throw
|
|
55
|
+
return this.enqueue(agentId, toolName, execute);
|
|
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));
|
|
64
73
|
}
|
|
65
74
|
/**
|
|
66
75
|
* Get time until next available slot (ms).
|
|
@@ -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
|
}
|
|
@@ -84,17 +92,22 @@ export class A2ARateLimiter {
|
|
|
84
92
|
* Reset rate limit for an agent/tool.
|
|
85
93
|
*/
|
|
86
94
|
reset(agentId, toolName) {
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
if (this.config.scope === 'tool') {
|
|
96
|
+
if (toolName) {
|
|
97
|
+
this.entries.delete(this.getKey(agentId, toolName));
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const prefix = `${agentId}:`;
|
|
101
|
+
for (const key of [...this.entries.keys()]) {
|
|
102
|
+
if (key.startsWith(prefix)) {
|
|
103
|
+
this.entries.delete(key);
|
|
104
|
+
}
|
|
95
105
|
}
|
|
96
106
|
}
|
|
97
107
|
}
|
|
108
|
+
else {
|
|
109
|
+
this.entries.delete(agentId);
|
|
110
|
+
}
|
|
98
111
|
}
|
|
99
112
|
/**
|
|
100
113
|
* Clear all rate limit data.
|
|
@@ -104,7 +117,7 @@ export class A2ARateLimiter {
|
|
|
104
117
|
this.queue = [];
|
|
105
118
|
}
|
|
106
119
|
// ========================================================================
|
|
107
|
-
// Private
|
|
120
|
+
// Private
|
|
108
121
|
// ========================================================================
|
|
109
122
|
getKey(agentId, toolName) {
|
|
110
123
|
return this.config.scope === 'tool'
|
|
@@ -123,9 +136,11 @@ export class A2ARateLimiter {
|
|
|
123
136
|
const cutoff = now - this.config.windowMs;
|
|
124
137
|
entry.timestamps = entry.timestamps.filter(t => t > cutoff);
|
|
125
138
|
}
|
|
126
|
-
async enqueue(execute) {
|
|
139
|
+
async enqueue(agentId, toolName, execute) {
|
|
127
140
|
return new Promise((resolve, reject) => {
|
|
128
141
|
this.queue.push({
|
|
142
|
+
agentId,
|
|
143
|
+
toolName,
|
|
129
144
|
execute: execute,
|
|
130
145
|
resolve: resolve,
|
|
131
146
|
reject,
|
|
@@ -140,36 +155,21 @@ export class A2ARateLimiter {
|
|
|
140
155
|
this.processing = true;
|
|
141
156
|
while (this.queue.length > 0) {
|
|
142
157
|
const call = this.queue[0];
|
|
143
|
-
// Wait until we have a slot
|
|
144
|
-
// We use a simple "any agent" check since we don't have agent info in queue
|
|
145
|
-
await this.waitForSlot();
|
|
146
|
-
this.queue.shift();
|
|
147
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();
|
|
148
163
|
const result = await call.execute();
|
|
149
164
|
call.resolve(result);
|
|
150
165
|
}
|
|
151
166
|
catch (error) {
|
|
167
|
+
this.queue.shift();
|
|
152
168
|
call.reject(error);
|
|
153
169
|
}
|
|
154
170
|
}
|
|
155
171
|
this.processing = false;
|
|
156
172
|
}
|
|
157
|
-
async waitForSlot() {
|
|
158
|
-
// Simple exponential backoff check
|
|
159
|
-
const checkInterval = 100; // 100ms
|
|
160
|
-
const maxWait = this.config.windowMs;
|
|
161
|
-
let waited = 0;
|
|
162
|
-
while (waited < maxWait) {
|
|
163
|
-
// Check if any slot is available
|
|
164
|
-
// Since we don't have context, wait for window to pass
|
|
165
|
-
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
166
|
-
waited += checkInterval;
|
|
167
|
-
// If we've waited long enough for at least one slot to free up
|
|
168
|
-
if (waited >= this.config.windowMs) {
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
173
|
}
|
|
174
174
|
// ============================================================================
|
|
175
175
|
// Errors
|
package/dist/ai/a2a/types.d.ts
CHANGED
|
@@ -73,6 +73,8 @@ export interface AgentExpertConfig {
|
|
|
73
73
|
* Request to call a specific tool.
|
|
74
74
|
*/
|
|
75
75
|
export interface CallToolRequest {
|
|
76
|
+
/** Caller agent ID (for response routing) */
|
|
77
|
+
from?: string;
|
|
76
78
|
/** Tool name (without prefix) */
|
|
77
79
|
tool: string;
|
|
78
80
|
/** Tool arguments */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/ai/a2a/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/ai/a2a/types.ts"],"names":[],"mappings":"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"}
|
package/dist/ai/a2a/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/ai/a2a/types.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/ai/a2a/types.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;AA+IH,8CAEC;AAKD,4CAiBC;AAKD,8CAYC;AAKD,wCAcC;AAnED,+EAA+E;AAC/E,YAAY;AACZ,+EAA+E;AAE/E;;GAEG;AACH,SAAgB,iBAAiB;IAC7B,OAAO,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;AACzE,CAAC;AAED;;GAEG;AACH,SAAgB,gBAAgB,CAC5B,IAAY,EACZ,EAAU,EACV,IAAY,EACZ,IAA6B,EAC7B,QAAiB;IAEjB,OAAO;QACH,SAAS,EAAE,iBAAiB,EAAE;QAC9B,IAAI,EAAE,SAAS;QACf,IAAI;QACJ,EAAE;QACF,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;QACrB,QAAQ;QACR,IAAI;QACJ,IAAI;KACP,CAAC;AACN,CAAC;AAED;;GAEG;AACH,SAAgB,iBAAiB,CAC7B,OAAmB,EACnB,MAAe;IAEf,OAAO;QACH,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,IAAI,EAAE,UAAU;QAChB,IAAI,EAAE,OAAO,CAAC,EAAE;QAChB,EAAE,EAAE,OAAO,CAAC,IAAI;QAChB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;QACrB,MAAM;KACT,CAAC;AACN,CAAC;AAED;;GAEG;AACH,SAAgB,cAAc,CAC1B,OAAmB,EACnB,IAAkB,EAClB,OAAe,EACf,KAAc;IAEd,OAAO;QACH,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,OAAO,CAAC,EAAE;QAChB,EAAE,EAAE,OAAO,CAAC,IAAI;QAChB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;QACrB,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE;KAClC,CAAC;AACN,CAAC"}
|
package/package.json
CHANGED