fix(engine): treat finish_reason=repeat as soft stop

Moonshot/Kimi returns a non-standard finish_reason of "repeat" when its
server-side repetition detector trips. pi-ai surfaces this as a fatal
session state error, killing the agent heartbeat. Filter that case in
promptSessionAndCheck so the truncated turn is logged and the heartbeat
continues on the next tick.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-05-04 06:47:29 -07:00
parent d761ea8dd2
commit 66f85da75f
2 changed files with 15 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
---
"@runfusion/fusion": patch
---
Treat OpenAI-compatible `finish_reason: repeat` (raised by Moonshot/Kimi when its server-side repetition detector trips) as a soft stop in the engine heartbeat instead of a fatal error, so agent runs survive the truncation and can continue on the next tick.

View File

@@ -167,6 +167,16 @@ async function promptSessionAndCheck(session: AgentSession, prompt: string, opti
piLog.warn(`pi state error — failed to inspect transcript: ${inspectErr instanceof Error ? inspectErr.message : String(inspectErr)}`); piLog.warn(`pi state error — failed to inspect transcript: ${inspectErr instanceof Error ? inspectErr.message : String(inspectErr)}`);
} }
} }
// Some OpenAI-compatible providers (notably Moonshot/Kimi) end generation
// with a non-standard `finish_reason: repeat` when their server-side
// repetition detector trips. pi-ai surfaces this as a fatal state error,
// but for our purposes the assistant turn is already complete — treat it
// as a soft stop so the heartbeat keeps running.
if (/Provider finish_reason:\s*repeat\b/i.test(stateError)) {
piLog.warn(`pi state error — treating provider finish_reason=repeat as soft stop: ${stateError}`);
clearSessionStateError(session);
return;
}
throw new Error(stateError); throw new Error(stateError);
} }
} }