@compilr-dev/agents 0.3.26 → 0.3.27
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/agent.d.ts +22 -0
- package/dist/agent.js +49 -12
- package/package.json +1 -1
package/dist/agent.d.ts
CHANGED
|
@@ -256,6 +256,27 @@ export interface AgentConfig {
|
|
|
256
256
|
maxIterations: number;
|
|
257
257
|
toolCallCount: number;
|
|
258
258
|
}) => Promise<number | false>;
|
|
259
|
+
/**
|
|
260
|
+
* Callback when tool loop is detected (same tool called N times with identical input).
|
|
261
|
+
*
|
|
262
|
+
* When provided, the agent asks the user instead of throwing ToolLoopError.
|
|
263
|
+
* Return `true` to continue (reset the counter), `false` to stop.
|
|
264
|
+
*
|
|
265
|
+
* When not provided, ToolLoopError is thrown (backwards compatible).
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```typescript
|
|
269
|
+
* onToolLoopDetected: async ({ toolName, consecutiveCalls }) => {
|
|
270
|
+
* const answer = await askUser(`${toolName} called ${consecutiveCalls} times. Continue?`);
|
|
271
|
+
* return answer === 'yes';
|
|
272
|
+
* }
|
|
273
|
+
* ```
|
|
274
|
+
*/
|
|
275
|
+
onToolLoopDetected?: (context: {
|
|
276
|
+
toolName: string;
|
|
277
|
+
consecutiveCalls: number;
|
|
278
|
+
input: Record<string, unknown>;
|
|
279
|
+
}) => Promise<boolean>;
|
|
259
280
|
/**
|
|
260
281
|
* Chat options (model, temperature, etc.)
|
|
261
282
|
*/
|
|
@@ -850,6 +871,7 @@ export declare class Agent {
|
|
|
850
871
|
private readonly autoContextManagement;
|
|
851
872
|
private readonly onEvent?;
|
|
852
873
|
private readonly onIterationLimitReached?;
|
|
874
|
+
private readonly onToolLoopDetected?;
|
|
853
875
|
private readonly retryConfig;
|
|
854
876
|
private readonly checkpointer?;
|
|
855
877
|
private readonly _sessionId;
|
package/dist/agent.js
CHANGED
|
@@ -50,6 +50,7 @@ export class Agent {
|
|
|
50
50
|
autoContextManagement;
|
|
51
51
|
onEvent;
|
|
52
52
|
onIterationLimitReached;
|
|
53
|
+
onToolLoopDetected;
|
|
53
54
|
// Retry configuration
|
|
54
55
|
retryConfig;
|
|
55
56
|
// State management
|
|
@@ -139,6 +140,7 @@ export class Agent {
|
|
|
139
140
|
}
|
|
140
141
|
this.onEvent = config.onEvent;
|
|
141
142
|
this.onIterationLimitReached = config.onIterationLimitReached;
|
|
143
|
+
this.onToolLoopDetected = config.onToolLoopDetected;
|
|
142
144
|
// State management
|
|
143
145
|
this.checkpointer = config.checkpointer;
|
|
144
146
|
this._sessionId = config.sessionId ?? generateSessionId();
|
|
@@ -2091,13 +2093,36 @@ export class Agent {
|
|
|
2091
2093
|
aborted = true;
|
|
2092
2094
|
break;
|
|
2093
2095
|
}
|
|
2096
|
+
const toolCallEntry = { name: toolUse.name, input: toolUse.input, result };
|
|
2097
|
+
toolCalls.push(toolCallEntry);
|
|
2098
|
+
iterationToolCalls.push(toolCallEntry);
|
|
2099
|
+
// Always push the tool_result BEFORE loop detection
|
|
2100
|
+
// so the conversation history stays valid if we throw
|
|
2101
|
+
messages.push(toolResultMsg);
|
|
2102
|
+
newMessages.push(toolResultMsg);
|
|
2094
2103
|
// Tool loop detection (still applies per-tool)
|
|
2095
2104
|
if (this.maxConsecutiveToolCalls > 0) {
|
|
2096
2105
|
const currentHash = hashToolCall(toolUse.name, toolUse.input);
|
|
2097
2106
|
if (currentHash === lastToolCallHash) {
|
|
2098
2107
|
consecutiveIdenticalCalls++;
|
|
2099
2108
|
if (consecutiveIdenticalCalls >= this.maxConsecutiveToolCalls) {
|
|
2100
|
-
|
|
2109
|
+
if (this.onToolLoopDetected) {
|
|
2110
|
+
// Ask user: continue or stop?
|
|
2111
|
+
const shouldContinue = await this.onToolLoopDetected({
|
|
2112
|
+
toolName: toolUse.name,
|
|
2113
|
+
consecutiveCalls: consecutiveIdenticalCalls,
|
|
2114
|
+
input: toolUse.input,
|
|
2115
|
+
});
|
|
2116
|
+
if (shouldContinue) {
|
|
2117
|
+
consecutiveIdenticalCalls = 0; // Reset counter
|
|
2118
|
+
}
|
|
2119
|
+
else {
|
|
2120
|
+
throw new ToolLoopError(toolUse.name, consecutiveIdenticalCalls, toolUse.input);
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
else {
|
|
2124
|
+
throw new ToolLoopError(toolUse.name, consecutiveIdenticalCalls, toolUse.input);
|
|
2125
|
+
}
|
|
2101
2126
|
}
|
|
2102
2127
|
emit({
|
|
2103
2128
|
type: 'tool_loop_warning',
|
|
@@ -2110,11 +2135,6 @@ export class Agent {
|
|
|
2110
2135
|
consecutiveIdenticalCalls = 1;
|
|
2111
2136
|
}
|
|
2112
2137
|
}
|
|
2113
|
-
const toolCallEntry = { name: toolUse.name, input: toolUse.input, result };
|
|
2114
|
-
toolCalls.push(toolCallEntry);
|
|
2115
|
-
iterationToolCalls.push(toolCallEntry);
|
|
2116
|
-
messages.push(toolResultMsg);
|
|
2117
|
-
newMessages.push(toolResultMsg);
|
|
2118
2138
|
// Stamp for observation masking
|
|
2119
2139
|
if (this.observationMasker) {
|
|
2120
2140
|
const block = toolResultMsg.content[0];
|
|
@@ -2134,13 +2154,35 @@ export class Agent {
|
|
|
2134
2154
|
aborted = true;
|
|
2135
2155
|
break;
|
|
2136
2156
|
}
|
|
2157
|
+
const toolCallEntry = { name: toolUse.name, input: toolUse.input, result };
|
|
2158
|
+
toolCalls.push(toolCallEntry);
|
|
2159
|
+
iterationToolCalls.push(toolCallEntry);
|
|
2160
|
+
// Always push the tool_result BEFORE loop detection
|
|
2161
|
+
// so the conversation history stays valid if we throw
|
|
2162
|
+
messages.push(toolResultMsg);
|
|
2163
|
+
newMessages.push(toolResultMsg);
|
|
2137
2164
|
// Tool loop detection
|
|
2138
2165
|
if (this.maxConsecutiveToolCalls > 0) {
|
|
2139
2166
|
const currentHash = hashToolCall(toolUse.name, toolUse.input);
|
|
2140
2167
|
if (currentHash === lastToolCallHash) {
|
|
2141
2168
|
consecutiveIdenticalCalls++;
|
|
2142
2169
|
if (consecutiveIdenticalCalls >= this.maxConsecutiveToolCalls) {
|
|
2143
|
-
|
|
2170
|
+
if (this.onToolLoopDetected) {
|
|
2171
|
+
const shouldContinue = await this.onToolLoopDetected({
|
|
2172
|
+
toolName: toolUse.name,
|
|
2173
|
+
consecutiveCalls: consecutiveIdenticalCalls,
|
|
2174
|
+
input: toolUse.input,
|
|
2175
|
+
});
|
|
2176
|
+
if (shouldContinue) {
|
|
2177
|
+
consecutiveIdenticalCalls = 0;
|
|
2178
|
+
}
|
|
2179
|
+
else {
|
|
2180
|
+
throw new ToolLoopError(toolUse.name, consecutiveIdenticalCalls, toolUse.input);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
else {
|
|
2184
|
+
throw new ToolLoopError(toolUse.name, consecutiveIdenticalCalls, toolUse.input);
|
|
2185
|
+
}
|
|
2144
2186
|
}
|
|
2145
2187
|
emit({
|
|
2146
2188
|
type: 'tool_loop_warning',
|
|
@@ -2153,11 +2195,6 @@ export class Agent {
|
|
|
2153
2195
|
consecutiveIdenticalCalls = 1;
|
|
2154
2196
|
}
|
|
2155
2197
|
}
|
|
2156
|
-
const toolCallEntry = { name: toolUse.name, input: toolUse.input, result };
|
|
2157
|
-
toolCalls.push(toolCallEntry);
|
|
2158
|
-
iterationToolCalls.push(toolCallEntry);
|
|
2159
|
-
messages.push(toolResultMsg);
|
|
2160
|
-
newMessages.push(toolResultMsg);
|
|
2161
2198
|
// Stamp for observation masking
|
|
2162
2199
|
if (this.observationMasker) {
|
|
2163
2200
|
const block = toolResultMsg.content[0];
|