@rama_nigg/open-cursor 2.3.4 → 2.3.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/README.md +35 -10
- package/dist/index.js +31 -9
- package/dist/plugin-entry.js +31 -9
- package/package.json +1 -1
- package/src/provider/tool-loop-guard.ts +48 -4
package/README.md
CHANGED
|
@@ -7,11 +7,6 @@
|
|
|
7
7
|
|
|
8
8
|
No prompt limits. No broken streams. Full thinking + tool support in OpenCode. Your Cursor subscription, properly integrated.
|
|
9
9
|
|
|
10
|
-
## Prerequisites
|
|
11
|
-
|
|
12
|
-
Required: [OpenCode](https://opencode.ai/) + [cursor-agent](https://cursor.com/) (`curl -fsSL https://cursor.com/install | bash && cursor-agent login`)
|
|
13
|
-
Optional: [Bun](https://bun.sh/) (Options B-F), [Go 1.21+](https://go.dev/) (Option D)
|
|
14
|
-
|
|
15
10
|
## Installation
|
|
16
11
|
|
|
17
12
|
### Option A — One-line installer
|
|
@@ -20,7 +15,8 @@ Optional: [Bun](https://bun.sh/) (Options B-F), [Go 1.21+](https://go.dev/) (Opt
|
|
|
20
15
|
curl -fsSL https://raw.githubusercontent.com/Nomadcxx/opencode-cursor/main/install.sh | bash
|
|
21
16
|
```
|
|
22
17
|
|
|
23
|
-
|
|
18
|
+
<details>
|
|
19
|
+
<summary><b>Option B</b> — Add to opencode.json</summary>
|
|
24
20
|
|
|
25
21
|
Add to `~/.config/opencode/opencode.json`:
|
|
26
22
|
|
|
@@ -31,6 +27,9 @@ Add to `~/.config/opencode/opencode.json`:
|
|
|
31
27
|
"cursor-acp": {
|
|
32
28
|
"name": "Cursor ACP",
|
|
33
29
|
"npm": "@ai-sdk/openai-compatible",
|
|
30
|
+
"options": {
|
|
31
|
+
"baseURL": "http://127.0.0.1:32124/v1"
|
|
32
|
+
},
|
|
34
33
|
"models": {
|
|
35
34
|
"cursor-acp/auto": { "name": "Auto" },
|
|
36
35
|
"cursor-acp/composer-1.5": { "name": "Composer 1.5" },
|
|
@@ -72,8 +71,10 @@ Add to `~/.config/opencode/opencode.json`:
|
|
|
72
71
|
```
|
|
73
72
|
|
|
74
73
|
> Update models anytime: `cursor-agent models`
|
|
74
|
+
</details>
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
<details>
|
|
77
|
+
<summary><b>Option C</b> — npm global + CLI</summary>
|
|
77
78
|
|
|
78
79
|
```bash
|
|
79
80
|
npm install -g @rama_nigg/open-cursor
|
|
@@ -81,22 +82,28 @@ open-cursor install
|
|
|
81
82
|
```
|
|
82
83
|
|
|
83
84
|
Upgrade: `npm update -g @rama_nigg/open-cursor`
|
|
85
|
+
</details>
|
|
84
86
|
|
|
85
|
-
|
|
87
|
+
<details>
|
|
88
|
+
<summary><b>Option D</b> — Go TUI installer</summary>
|
|
86
89
|
|
|
87
90
|
```bash
|
|
88
91
|
git clone https://github.com/Nomadcxx/opencode-cursor.git
|
|
89
92
|
cd opencode-cursor
|
|
90
93
|
go build -o ./installer ./cmd/installer && ./installer
|
|
91
94
|
```
|
|
95
|
+
</details>
|
|
92
96
|
|
|
93
|
-
|
|
97
|
+
<details>
|
|
98
|
+
<summary><b>Option E</b> — LLM paste</summary>
|
|
94
99
|
|
|
95
100
|
```
|
|
96
101
|
Install open-cursor for OpenCode: edit ~/.config/opencode/opencode.json, add "@rama_nigg/open-cursor@latest" to "plugin", add a "cursor-acp" provider with npm "@ai-sdk/openai-compatible" and models from `cursor-agent models` prefixed with "cursor-acp/". Auth: `cursor-agent login`. Verify: `opencode models | grep cursor-acp`.
|
|
97
102
|
```
|
|
103
|
+
</details>
|
|
98
104
|
|
|
99
|
-
|
|
105
|
+
<details>
|
|
106
|
+
<summary><b>Option F</b> — Manual (from source)</summary>
|
|
100
107
|
|
|
101
108
|
```bash
|
|
102
109
|
git clone https://github.com/Nomadcxx/opencode-cursor.git && cd opencode-cursor
|
|
@@ -106,6 +113,7 @@ ln -sf $(pwd)/dist/plugin-entry.js ~/.config/opencode/plugin/cursor-acp.js
|
|
|
106
113
|
```
|
|
107
114
|
|
|
108
115
|
Add `"cursor-acp"` to the `plugin` array and reuse the provider block from Option B.
|
|
116
|
+
</details>
|
|
109
117
|
|
|
110
118
|
## Authentication
|
|
111
119
|
|
|
@@ -170,6 +178,23 @@ Default mode: `CURSOR_ACP_TOOL_LOOP_MODE=opencode`. Legacy `proxy-exec` still av
|
|
|
170
178
|
|
|
171
179
|
Debug logging: `CURSOR_ACP_LOG_LEVEL=debug opencode run "your prompt" --model cursor-acp/auto`
|
|
172
180
|
|
|
181
|
+
## Roadmap
|
|
182
|
+
|
|
183
|
+
```mermaid
|
|
184
|
+
flowchart LR
|
|
185
|
+
P1[/Stabilise/] --> P2[/MCP Server/] --> P3[/Simplify/] --> P4[/ACP + MCP/]
|
|
186
|
+
|
|
187
|
+
style P1 fill:#264653,stroke:#1d3557,color:#fff
|
|
188
|
+
style P2 fill:#264653,stroke:#1d3557,color:#fff
|
|
189
|
+
style P3 fill:#495057,stroke:#343a40,color:#adb5bd
|
|
190
|
+
style P4 fill:#495057,stroke:#343a40,color:#adb5bd
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
[X] **Stabilise** — Clean up dead code, fix test isolation
|
|
194
|
+
[ ] **MCP Server** — Expose OpenCode tools via stdio transport
|
|
195
|
+
[ ] **Simplify** — Rip out serialisation layers
|
|
196
|
+
[ ] **ACP + MCP** — Structured protocols end-to-end
|
|
197
|
+
|
|
173
198
|
## License
|
|
174
199
|
|
|
175
200
|
BSD-3-Clause
|
package/dist/index.js
CHANGED
|
@@ -17408,6 +17408,7 @@ function parseToolLoopMaxRepeat(value) {
|
|
|
17408
17408
|
return { value: Math.floor(parsed), valid: true };
|
|
17409
17409
|
}
|
|
17410
17410
|
function createToolLoopGuard(messages, maxRepeat) {
|
|
17411
|
+
const coarseMaxRepeat = maxRepeat * COARSE_LIMIT_MULTIPLIER;
|
|
17411
17412
|
const {
|
|
17412
17413
|
byCallId,
|
|
17413
17414
|
latest,
|
|
@@ -17447,13 +17448,13 @@ function createToolLoopGuard(messages, maxRepeat) {
|
|
|
17447
17448
|
}
|
|
17448
17449
|
const strictFingerprint = `${toolCall.function.name}|${argShape}|${errorClass}`;
|
|
17449
17450
|
const coarseFingerprint = `${toolCall.function.name}|${errorClass}`;
|
|
17450
|
-
return evaluateWithFingerprints(errorClass, strictFingerprint, coarseFingerprint, counts, coarseCounts, maxRepeat);
|
|
17451
|
+
return evaluateWithFingerprints(toolCall.function.name, errorClass, strictFingerprint, coarseFingerprint, counts, coarseCounts, maxRepeat, coarseMaxRepeat);
|
|
17451
17452
|
},
|
|
17452
17453
|
evaluateValidation(toolCall, validationSignature) {
|
|
17453
17454
|
const normalizedSignature = normalizeValidationSignature(validationSignature);
|
|
17454
17455
|
const strictFingerprint = `${toolCall.function.name}|schema:${normalizedSignature}|validation`;
|
|
17455
17456
|
const coarseFingerprint = `${toolCall.function.name}|validation`;
|
|
17456
|
-
return evaluateWithFingerprints("validation", strictFingerprint, coarseFingerprint, validationCounts, validationCoarseCounts, maxRepeat);
|
|
17457
|
+
return evaluateWithFingerprints(toolCall.function.name, "validation", strictFingerprint, coarseFingerprint, validationCounts, validationCoarseCounts, maxRepeat, coarseMaxRepeat);
|
|
17457
17458
|
},
|
|
17458
17459
|
resetFingerprint(fingerprint) {
|
|
17459
17460
|
counts.delete(fingerprint);
|
|
@@ -17684,7 +17685,7 @@ function normalizeValidationSignature(signature) {
|
|
|
17684
17685
|
const normalized = signature.trim().toLowerCase();
|
|
17685
17686
|
return normalized.length > 0 ? normalized : "invalid";
|
|
17686
17687
|
}
|
|
17687
|
-
function evaluateWithFingerprints(errorClass, strictFingerprint, coarseFingerprint, strictCounts, coarseCounts, maxRepeat) {
|
|
17688
|
+
function evaluateWithFingerprints(toolName, errorClass, strictFingerprint, coarseFingerprint, strictCounts, coarseCounts, maxRepeat, coarseMaxRepeat) {
|
|
17688
17689
|
if (errorClass === "success") {
|
|
17689
17690
|
return {
|
|
17690
17691
|
fingerprint: strictFingerprint,
|
|
@@ -17697,15 +17698,26 @@ function evaluateWithFingerprints(errorClass, strictFingerprint, coarseFingerpri
|
|
|
17697
17698
|
}
|
|
17698
17699
|
const strictRepeatCount = (strictCounts.get(strictFingerprint) ?? 0) + 1;
|
|
17699
17700
|
strictCounts.set(strictFingerprint, strictRepeatCount);
|
|
17701
|
+
const strictTriggered = strictRepeatCount > maxRepeat;
|
|
17702
|
+
const isExplorationTool = EXPLORATION_TOOLS.has(toolName.toLowerCase());
|
|
17703
|
+
if (isExplorationTool) {
|
|
17704
|
+
return {
|
|
17705
|
+
fingerprint: strictFingerprint,
|
|
17706
|
+
repeatCount: strictRepeatCount,
|
|
17707
|
+
maxRepeat,
|
|
17708
|
+
errorClass,
|
|
17709
|
+
triggered: strictTriggered,
|
|
17710
|
+
tracked: true
|
|
17711
|
+
};
|
|
17712
|
+
}
|
|
17700
17713
|
const coarseRepeatCount = (coarseCounts.get(coarseFingerprint) ?? 0) + 1;
|
|
17701
17714
|
coarseCounts.set(coarseFingerprint, coarseRepeatCount);
|
|
17702
|
-
const
|
|
17703
|
-
const coarseTriggered = coarseRepeatCount > maxRepeat;
|
|
17715
|
+
const coarseTriggered = coarseRepeatCount > coarseMaxRepeat;
|
|
17704
17716
|
const preferCoarseFingerprint = coarseTriggered && !strictTriggered;
|
|
17705
17717
|
return {
|
|
17706
17718
|
fingerprint: preferCoarseFingerprint ? coarseFingerprint : strictFingerprint,
|
|
17707
17719
|
repeatCount: preferCoarseFingerprint ? coarseRepeatCount : strictRepeatCount,
|
|
17708
|
-
maxRepeat,
|
|
17720
|
+
maxRepeat: preferCoarseFingerprint ? coarseMaxRepeat : maxRepeat,
|
|
17709
17721
|
errorClass,
|
|
17710
17722
|
triggered: strictTriggered || coarseTriggered,
|
|
17711
17723
|
tracked: true
|
|
@@ -17790,7 +17802,7 @@ function containsAny(text, patterns) {
|
|
|
17790
17802
|
function isRecord4(value) {
|
|
17791
17803
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17792
17804
|
}
|
|
17793
|
-
var UNKNOWN_AS_SUCCESS_TOOLS;
|
|
17805
|
+
var UNKNOWN_AS_SUCCESS_TOOLS, EXPLORATION_TOOLS, COARSE_LIMIT_MULTIPLIER = 3;
|
|
17794
17806
|
var init_tool_loop_guard = __esm(() => {
|
|
17795
17807
|
UNKNOWN_AS_SUCCESS_TOOLS = new Set([
|
|
17796
17808
|
"bash",
|
|
@@ -17802,9 +17814,19 @@ var init_tool_loop_guard = __esm(() => {
|
|
|
17802
17814
|
"ls",
|
|
17803
17815
|
"glob",
|
|
17804
17816
|
"stat",
|
|
17805
|
-
"webfetch",
|
|
17806
17817
|
"mkdir",
|
|
17807
|
-
"rm"
|
|
17818
|
+
"rm",
|
|
17819
|
+
"webfetch",
|
|
17820
|
+
"semsearch",
|
|
17821
|
+
"readlints"
|
|
17822
|
+
]);
|
|
17823
|
+
EXPLORATION_TOOLS = new Set([
|
|
17824
|
+
"read",
|
|
17825
|
+
"grep",
|
|
17826
|
+
"glob",
|
|
17827
|
+
"ls",
|
|
17828
|
+
"stat",
|
|
17829
|
+
"semsearch"
|
|
17808
17830
|
]);
|
|
17809
17831
|
});
|
|
17810
17832
|
|
package/dist/plugin-entry.js
CHANGED
|
@@ -17408,6 +17408,7 @@ function parseToolLoopMaxRepeat(value) {
|
|
|
17408
17408
|
return { value: Math.floor(parsed), valid: true };
|
|
17409
17409
|
}
|
|
17410
17410
|
function createToolLoopGuard(messages, maxRepeat) {
|
|
17411
|
+
const coarseMaxRepeat = maxRepeat * COARSE_LIMIT_MULTIPLIER;
|
|
17411
17412
|
const {
|
|
17412
17413
|
byCallId,
|
|
17413
17414
|
latest,
|
|
@@ -17447,13 +17448,13 @@ function createToolLoopGuard(messages, maxRepeat) {
|
|
|
17447
17448
|
}
|
|
17448
17449
|
const strictFingerprint = `${toolCall.function.name}|${argShape}|${errorClass}`;
|
|
17449
17450
|
const coarseFingerprint = `${toolCall.function.name}|${errorClass}`;
|
|
17450
|
-
return evaluateWithFingerprints(errorClass, strictFingerprint, coarseFingerprint, counts, coarseCounts, maxRepeat);
|
|
17451
|
+
return evaluateWithFingerprints(toolCall.function.name, errorClass, strictFingerprint, coarseFingerprint, counts, coarseCounts, maxRepeat, coarseMaxRepeat);
|
|
17451
17452
|
},
|
|
17452
17453
|
evaluateValidation(toolCall, validationSignature) {
|
|
17453
17454
|
const normalizedSignature = normalizeValidationSignature(validationSignature);
|
|
17454
17455
|
const strictFingerprint = `${toolCall.function.name}|schema:${normalizedSignature}|validation`;
|
|
17455
17456
|
const coarseFingerprint = `${toolCall.function.name}|validation`;
|
|
17456
|
-
return evaluateWithFingerprints("validation", strictFingerprint, coarseFingerprint, validationCounts, validationCoarseCounts, maxRepeat);
|
|
17457
|
+
return evaluateWithFingerprints(toolCall.function.name, "validation", strictFingerprint, coarseFingerprint, validationCounts, validationCoarseCounts, maxRepeat, coarseMaxRepeat);
|
|
17457
17458
|
},
|
|
17458
17459
|
resetFingerprint(fingerprint) {
|
|
17459
17460
|
counts.delete(fingerprint);
|
|
@@ -17684,7 +17685,7 @@ function normalizeValidationSignature(signature) {
|
|
|
17684
17685
|
const normalized = signature.trim().toLowerCase();
|
|
17685
17686
|
return normalized.length > 0 ? normalized : "invalid";
|
|
17686
17687
|
}
|
|
17687
|
-
function evaluateWithFingerprints(errorClass, strictFingerprint, coarseFingerprint, strictCounts, coarseCounts, maxRepeat) {
|
|
17688
|
+
function evaluateWithFingerprints(toolName, errorClass, strictFingerprint, coarseFingerprint, strictCounts, coarseCounts, maxRepeat, coarseMaxRepeat) {
|
|
17688
17689
|
if (errorClass === "success") {
|
|
17689
17690
|
return {
|
|
17690
17691
|
fingerprint: strictFingerprint,
|
|
@@ -17697,15 +17698,26 @@ function evaluateWithFingerprints(errorClass, strictFingerprint, coarseFingerpri
|
|
|
17697
17698
|
}
|
|
17698
17699
|
const strictRepeatCount = (strictCounts.get(strictFingerprint) ?? 0) + 1;
|
|
17699
17700
|
strictCounts.set(strictFingerprint, strictRepeatCount);
|
|
17701
|
+
const strictTriggered = strictRepeatCount > maxRepeat;
|
|
17702
|
+
const isExplorationTool = EXPLORATION_TOOLS.has(toolName.toLowerCase());
|
|
17703
|
+
if (isExplorationTool) {
|
|
17704
|
+
return {
|
|
17705
|
+
fingerprint: strictFingerprint,
|
|
17706
|
+
repeatCount: strictRepeatCount,
|
|
17707
|
+
maxRepeat,
|
|
17708
|
+
errorClass,
|
|
17709
|
+
triggered: strictTriggered,
|
|
17710
|
+
tracked: true
|
|
17711
|
+
};
|
|
17712
|
+
}
|
|
17700
17713
|
const coarseRepeatCount = (coarseCounts.get(coarseFingerprint) ?? 0) + 1;
|
|
17701
17714
|
coarseCounts.set(coarseFingerprint, coarseRepeatCount);
|
|
17702
|
-
const
|
|
17703
|
-
const coarseTriggered = coarseRepeatCount > maxRepeat;
|
|
17715
|
+
const coarseTriggered = coarseRepeatCount > coarseMaxRepeat;
|
|
17704
17716
|
const preferCoarseFingerprint = coarseTriggered && !strictTriggered;
|
|
17705
17717
|
return {
|
|
17706
17718
|
fingerprint: preferCoarseFingerprint ? coarseFingerprint : strictFingerprint,
|
|
17707
17719
|
repeatCount: preferCoarseFingerprint ? coarseRepeatCount : strictRepeatCount,
|
|
17708
|
-
maxRepeat,
|
|
17720
|
+
maxRepeat: preferCoarseFingerprint ? coarseMaxRepeat : maxRepeat,
|
|
17709
17721
|
errorClass,
|
|
17710
17722
|
triggered: strictTriggered || coarseTriggered,
|
|
17711
17723
|
tracked: true
|
|
@@ -17790,7 +17802,7 @@ function containsAny(text, patterns) {
|
|
|
17790
17802
|
function isRecord4(value) {
|
|
17791
17803
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17792
17804
|
}
|
|
17793
|
-
var UNKNOWN_AS_SUCCESS_TOOLS;
|
|
17805
|
+
var UNKNOWN_AS_SUCCESS_TOOLS, EXPLORATION_TOOLS, COARSE_LIMIT_MULTIPLIER = 3;
|
|
17794
17806
|
var init_tool_loop_guard = __esm(() => {
|
|
17795
17807
|
UNKNOWN_AS_SUCCESS_TOOLS = new Set([
|
|
17796
17808
|
"bash",
|
|
@@ -17802,9 +17814,19 @@ var init_tool_loop_guard = __esm(() => {
|
|
|
17802
17814
|
"ls",
|
|
17803
17815
|
"glob",
|
|
17804
17816
|
"stat",
|
|
17805
|
-
"webfetch",
|
|
17806
17817
|
"mkdir",
|
|
17807
|
-
"rm"
|
|
17818
|
+
"rm",
|
|
17819
|
+
"webfetch",
|
|
17820
|
+
"semsearch",
|
|
17821
|
+
"readlints"
|
|
17822
|
+
]);
|
|
17823
|
+
EXPLORATION_TOOLS = new Set([
|
|
17824
|
+
"read",
|
|
17825
|
+
"grep",
|
|
17826
|
+
"glob",
|
|
17827
|
+
"ls",
|
|
17828
|
+
"stat",
|
|
17829
|
+
"semsearch"
|
|
17808
17830
|
]);
|
|
17809
17831
|
});
|
|
17810
17832
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rama_nigg/open-cursor",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.6",
|
|
4
4
|
"description": "No prompt limits. No broken streams. Full thinking + tool support. Your Cursor subscription, properly integrated.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/plugin-entry.js",
|
|
@@ -10,6 +10,7 @@ type ToolLoopErrorClass =
|
|
|
10
10
|
| "unknown";
|
|
11
11
|
|
|
12
12
|
const UNKNOWN_AS_SUCCESS_TOOLS = new Set([
|
|
13
|
+
// Core filesystem tools
|
|
13
14
|
"bash",
|
|
14
15
|
"shell",
|
|
15
16
|
"read",
|
|
@@ -19,9 +20,27 @@ const UNKNOWN_AS_SUCCESS_TOOLS = new Set([
|
|
|
19
20
|
"ls",
|
|
20
21
|
"glob",
|
|
21
22
|
"stat",
|
|
22
|
-
"webfetch",
|
|
23
23
|
"mkdir",
|
|
24
24
|
"rm",
|
|
25
|
+
// Web/network tools
|
|
26
|
+
"webfetch",
|
|
27
|
+
// cursor-agent specific tools (passthrough, but should not trigger loop guard)
|
|
28
|
+
// Discovered via tests/experiments/ harness - see docs/cursor-agent-tools.md
|
|
29
|
+
"semsearch", // semantic code search
|
|
30
|
+
"readlints", // lint/diagnostic reader
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
// Exploratory tools that commonly iterate over many files/patterns.
|
|
34
|
+
// These are exempt from COARSE fingerprint tracking (tool|errorClass) to allow
|
|
35
|
+
// legitimate multi-file exploration. Strict fingerprints (tool|args|errorClass)
|
|
36
|
+
// still apply to catch identical repeated failures.
|
|
37
|
+
const EXPLORATION_TOOLS = new Set([
|
|
38
|
+
"read",
|
|
39
|
+
"grep",
|
|
40
|
+
"glob",
|
|
41
|
+
"ls",
|
|
42
|
+
"stat",
|
|
43
|
+
"semsearch",
|
|
25
44
|
]);
|
|
26
45
|
|
|
27
46
|
export interface ToolLoopGuardDecision {
|
|
@@ -52,10 +71,16 @@ export function parseToolLoopMaxRepeat(
|
|
|
52
71
|
return { value: Math.floor(parsed), valid: true };
|
|
53
72
|
}
|
|
54
73
|
|
|
74
|
+
// Coarse fingerprint (tool|errorClass without args) uses a higher multiplier
|
|
75
|
+
// to allow legitimate exploration across different files/targets while still
|
|
76
|
+
// catching spray-and-pray patterns.
|
|
77
|
+
const COARSE_LIMIT_MULTIPLIER = 3;
|
|
78
|
+
|
|
55
79
|
export function createToolLoopGuard(
|
|
56
80
|
messages: Array<unknown>,
|
|
57
81
|
maxRepeat: number,
|
|
58
82
|
): ToolLoopGuard {
|
|
83
|
+
const coarseMaxRepeat = maxRepeat * COARSE_LIMIT_MULTIPLIER;
|
|
59
84
|
const {
|
|
60
85
|
byCallId,
|
|
61
86
|
latest,
|
|
@@ -119,12 +144,14 @@ export function createToolLoopGuard(
|
|
|
119
144
|
const coarseFingerprint = `${toolCall.function.name}|${errorClass}`;
|
|
120
145
|
|
|
121
146
|
return evaluateWithFingerprints(
|
|
147
|
+
toolCall.function.name,
|
|
122
148
|
errorClass,
|
|
123
149
|
strictFingerprint,
|
|
124
150
|
coarseFingerprint,
|
|
125
151
|
counts,
|
|
126
152
|
coarseCounts,
|
|
127
153
|
maxRepeat,
|
|
154
|
+
coarseMaxRepeat,
|
|
128
155
|
);
|
|
129
156
|
},
|
|
130
157
|
|
|
@@ -133,12 +160,14 @@ export function createToolLoopGuard(
|
|
|
133
160
|
const strictFingerprint = `${toolCall.function.name}|schema:${normalizedSignature}|validation`;
|
|
134
161
|
const coarseFingerprint = `${toolCall.function.name}|validation`;
|
|
135
162
|
return evaluateWithFingerprints(
|
|
163
|
+
toolCall.function.name,
|
|
136
164
|
"validation",
|
|
137
165
|
strictFingerprint,
|
|
138
166
|
coarseFingerprint,
|
|
139
167
|
validationCounts,
|
|
140
168
|
validationCoarseCounts,
|
|
141
169
|
maxRepeat,
|
|
170
|
+
coarseMaxRepeat,
|
|
142
171
|
);
|
|
143
172
|
},
|
|
144
173
|
|
|
@@ -448,12 +477,14 @@ function normalizeValidationSignature(signature: string): string {
|
|
|
448
477
|
}
|
|
449
478
|
|
|
450
479
|
function evaluateWithFingerprints(
|
|
480
|
+
toolName: string,
|
|
451
481
|
errorClass: ToolLoopErrorClass,
|
|
452
482
|
strictFingerprint: string,
|
|
453
483
|
coarseFingerprint: string,
|
|
454
484
|
strictCounts: Map<string, number>,
|
|
455
485
|
coarseCounts: Map<string, number>,
|
|
456
486
|
maxRepeat: number,
|
|
487
|
+
coarseMaxRepeat: number,
|
|
457
488
|
): ToolLoopGuardDecision {
|
|
458
489
|
if (errorClass === "success") {
|
|
459
490
|
return {
|
|
@@ -468,15 +499,28 @@ function evaluateWithFingerprints(
|
|
|
468
499
|
|
|
469
500
|
const strictRepeatCount = (strictCounts.get(strictFingerprint) ?? 0) + 1;
|
|
470
501
|
strictCounts.set(strictFingerprint, strictRepeatCount);
|
|
502
|
+
const strictTriggered = strictRepeatCount > maxRepeat;
|
|
503
|
+
|
|
504
|
+
const isExplorationTool = EXPLORATION_TOOLS.has(toolName.toLowerCase());
|
|
505
|
+
if (isExplorationTool) {
|
|
506
|
+
return {
|
|
507
|
+
fingerprint: strictFingerprint,
|
|
508
|
+
repeatCount: strictRepeatCount,
|
|
509
|
+
maxRepeat,
|
|
510
|
+
errorClass,
|
|
511
|
+
triggered: strictTriggered,
|
|
512
|
+
tracked: true,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
471
516
|
const coarseRepeatCount = (coarseCounts.get(coarseFingerprint) ?? 0) + 1;
|
|
472
517
|
coarseCounts.set(coarseFingerprint, coarseRepeatCount);
|
|
473
|
-
const
|
|
474
|
-
const coarseTriggered = coarseRepeatCount > maxRepeat;
|
|
518
|
+
const coarseTriggered = coarseRepeatCount > coarseMaxRepeat;
|
|
475
519
|
const preferCoarseFingerprint = coarseTriggered && !strictTriggered;
|
|
476
520
|
return {
|
|
477
521
|
fingerprint: preferCoarseFingerprint ? coarseFingerprint : strictFingerprint,
|
|
478
522
|
repeatCount: preferCoarseFingerprint ? coarseRepeatCount : strictRepeatCount,
|
|
479
|
-
maxRepeat,
|
|
523
|
+
maxRepeat: preferCoarseFingerprint ? coarseMaxRepeat : maxRepeat,
|
|
480
524
|
errorClass,
|
|
481
525
|
triggered: strictTriggered || coarseTriggered,
|
|
482
526
|
tracked: true,
|