fix(agent): support async onLoopDetected callbacks and prevent orphaned tool_use events
- Await onLoopDetected callback result so async functions work correctly instead of silently falling through to 'continue' - Move loop detection before yielding tool_use events so terminate mode never emits tool_use without a matching tool_result
This commit is contained in:
parent
cc957b3148
commit
56b8cef158
|
|
@ -310,14 +310,13 @@ export class AgentRunner {
|
||||||
yield { type: 'text', data: turnText } satisfies StreamEvent
|
yield { type: 'text', data: turnText } satisfies StreamEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
// Announce each tool-use block the model requested.
|
// Extract tool-use blocks for detection and execution.
|
||||||
const toolUseBlocks = extractToolUseBlocks(response.content)
|
const toolUseBlocks = extractToolUseBlocks(response.content)
|
||||||
for (const block of toolUseBlocks) {
|
|
||||||
yield { type: 'tool_use', data: block } satisfies StreamEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
// Step 2.5: Loop detection — check before executing tools.
|
// Step 2.5: Loop detection — check before yielding tool_use events
|
||||||
|
// so that terminate mode doesn't emit orphaned tool_use without
|
||||||
|
// matching tool_result.
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
let injectWarning = false
|
let injectWarning = false
|
||||||
let injectWarningKind: 'tool_repetition' | 'text_repetition' = 'tool_repetition'
|
let injectWarningKind: 'tool_repetition' | 'text_repetition' = 'tool_repetition'
|
||||||
|
|
@ -331,7 +330,7 @@ export class AgentRunner {
|
||||||
options.onWarning?.(info.detail)
|
options.onWarning?.(info.detail)
|
||||||
|
|
||||||
const action = typeof loopAction === 'function'
|
const action = typeof loopAction === 'function'
|
||||||
? loopAction(info)
|
? await loopAction(info)
|
||||||
: loopAction
|
: loopAction
|
||||||
|
|
||||||
if (action === 'terminate') {
|
if (action === 'terminate') {
|
||||||
|
|
@ -363,6 +362,12 @@ export class AgentRunner {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Announce each tool-use block the model requested (after loop
|
||||||
|
// detection, so terminate mode never emits unpaired events).
|
||||||
|
for (const block of toolUseBlocks) {
|
||||||
|
yield { type: 'tool_use', data: block } satisfies StreamEvent
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
// Step 4: Execute all tool calls in PARALLEL.
|
// Step 4: Execute all tool calls in PARALLEL.
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -254,10 +254,10 @@ export interface LoopDetectionConfig {
|
||||||
* - `'warn'` — inject a "you appear stuck" message, give the LLM one
|
* - `'warn'` — inject a "you appear stuck" message, give the LLM one
|
||||||
* more chance; terminate if the loop persists (default)
|
* more chance; terminate if the loop persists (default)
|
||||||
* - `'terminate'` — stop the run immediately
|
* - `'terminate'` — stop the run immediately
|
||||||
* - `function` — custom callback; return `'continue'`, `'inject'`, or
|
* - `function` — custom callback (sync or async); return `'continue'`,
|
||||||
* `'terminate'` to control the outcome
|
* `'inject'`, or `'terminate'` to control the outcome
|
||||||
*/
|
*/
|
||||||
readonly onLoopDetected?: 'warn' | 'terminate' | ((info: LoopDetectionInfo) => 'continue' | 'inject' | 'terminate')
|
readonly onLoopDetected?: 'warn' | 'terminate' | ((info: LoopDetectionInfo) => 'continue' | 'inject' | 'terminate' | Promise<'continue' | 'inject' | 'terminate'>)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Diagnostic payload emitted when a loop is detected. */
|
/** Diagnostic payload emitted when a loop is detected. */
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue