fix: enforced loop detection

This commit is contained in:
Gab
2026-03-29 19:24:02 +11:00
parent cecaf216c2
commit 493b0f924f
3 changed files with 62 additions and 54 deletions

View File

@@ -1,5 +1,5 @@
import type { LanguageModelV2 } from "@ai-sdk/provider"
import { type FetchFunction, withoutTrailingSlash } from "@ai-sdk/provider-utils"
import { type FetchFunction, withoutTrailingSlash, safeParseJSON, EventSourceParserStream } from "@ai-sdk/provider-utils"
import { OpenAICompatibleChatLanguageModel } from "../copilot/chat/openai-compatible-chat-language-model"
import { Log } from "@/util/log"
@@ -117,57 +117,59 @@ export function createToothFairyAI(options: ToothFairyAIProviderSettings = {}):
}
if (res.body && res.headers.get("content-type")?.includes("text/event-stream")) {
const decoder = new TextDecoder()
const encoder = new TextEncoder()
let buffer = ""
const filteredStream = res.body.pipeThrough(
new TransformStream({
transform(chunk, controller) {
buffer += decoder.decode(chunk, { stream: true })
const lines = buffer.split("\n")
buffer = lines.pop() || ""
const filtered: string[] = []
for (const line of lines) {
if (line.startsWith("data: ")) {
const json = line.slice(6).trim()
if (json) {
try {
const parsed = JSON.parse(json)
if (parsed.status === "initialising" || parsed.status === "connected") {
log.debug("filtered connection status", { status: parsed.status })
continue
}
if (parsed.choices?.[0]?.finish_reason) {
log.info("stream finish_reason", {
finish_reason: parsed.choices[0].finish_reason,
})
}
if (parsed.usage) {
log.info("stream usage", {
prompt_tokens: parsed.usage.prompt_tokens,
completion_tokens: parsed.usage.completion_tokens,
total_tokens: parsed.usage.total_tokens,
})
}
} catch {}
}
const filteredStream = res.body
.pipeThrough(new TextDecoderStream())
.pipeThrough(new EventSourceParserStream())
.pipeThrough(
new TransformStream({
async transform({ data }, controller) {
if (data === "[DONE]") {
return
}
filtered.push(line)
}
if (filtered.length > 0) {
controller.enqueue(encoder.encode(filtered.join("\n") + "\n"))
}
},
flush(controller) {
if (buffer) {
controller.enqueue(encoder.encode(buffer))
}
},
}),
)
const parsed = await safeParseJSON({ text: data, schema: null })
if (!parsed.success) {
log.error("Failed to parse SSE chunk", {
chunk: data.slice(0, 100),
error: parsed.error,
})
controller.enqueue({ data })
return
}
const value = parsed.value
if (value.status === "initialising" || value.status === "connected") {
log.debug("filtered connection status", { status: value.status })
return
}
if (value.choices?.[0]?.finish_reason) {
log.info("stream finish_reason", {
finish_reason: value.choices[0].finish_reason,
})
}
if (value.usage) {
log.info("stream usage", {
prompt_tokens: value.usage.prompt_tokens,
completion_tokens: value.usage.completion_tokens,
total_tokens: value.usage.total_tokens,
})
}
controller.enqueue({ data })
},
}),
)
.pipeThrough(
new TransformStream({
transform({ data }, controller) {
controller.enqueue(new TextEncoder().encode(`data: ${data}\n\n`))
},
}),
)
return new Response(filteredStream, {
headers: res.headers,

View File

@@ -198,6 +198,11 @@ export namespace SessionProcessor {
always: [value.toolName],
ruleset: agent.permission,
})
// STOP the loop - do not execute the tool again
blocked = true
throw new Error(
`Verification loop detected: Tool "${value.toolName}" called 3+ times with ${isSimilarLoop ? "similar" : "identical"} parameters. Move to next step or ask for guidance.`,
)
}
}
break

View File

@@ -86,10 +86,11 @@ The user will primarily request you perform software engineering tasks. This inc
NEVER commit changes unless the user explicitly asks you to. It is VERY IMPORTANT to only commit when explicitly asked, otherwise the user will feel that you are being too proactive.
# Avoiding loops
- CRITICAL: Avoid verification loops. After making edits, do NOT re-read the same file to verify your changes. Trust that your edits were applied correctly.
- If you need to verify something, read the file ONCE before editing, not repeatedly after each edit.
- If you believe an edit was not applied correctly, check the file ONCE. If it looks correct, move on. Do not re-read the same file multiple times.
- Reading the same file 3 or more times is a sign of a loop - STOP and ask the user for guidance.
- **ONE VERIFICATION MAXIMUM**: After making edits, you may verify ONCE by reading the file. After this single verification, NEVER read the same file again for verification purposes.
- **TRUST TOOL EXECUTION**: When a tool returns success, the action is complete. The system executes tools correctly.
- **VERIFICATION IS NOT RE-EXECUTION**: Do not re-run tools to "verify". If you need to verify an edit, read the file ONCE, then move on.
- **READING LIMIT**: Reading the same file 3+ times indicates a malfunction. STOP immediately and ask the user for guidance.
- **NO REPEATED CALLS**: Never call the same tool with the same parameters more than once. Plan ahead and gather all needed information in a single pass.
- Tool results and user messages may include <system-reminder> tags. <system-reminder> tags contain useful information and reminders. They are NOT part of the user's provided input or the tool result.