@@ -81,6 +81,12 @@ const STRUCTURED_OUTPUT_SYSTEM_PROMPT = `IMPORTANT: The user has requested struc
8181const log = Log . create ( { service : "session.prompt" } )
8282const elog = EffectLogger . create ( { service : "session.prompt" } )
8383
84+ function isOrphanedInterruptedTool ( part : MessageV2 . ToolPart ) {
85+ // cleanup() marks abandoned tool_use blocks this way after retries/aborts.
86+ // They are not pending work and must not trigger an assistant-prefill request.
87+ return part . state . status === "error" && part . state . metadata ?. interrupted === true
88+ }
89+
8490export interface Interface {
8591 readonly cancel : ( sessionID : SessionID ) => Effect . Effect < void >
8692 readonly prompt : ( input : PromptInput ) => Effect . Effect < MessageV2 . WithParts , Image . Error >
@@ -1257,19 +1263,30 @@ export const layer = Layer.effect(
12571263 const lastAssistantMsg = msgs . findLast (
12581264 ( msg ) => msg . info . role === "assistant" && msg . info . id === lastAssistant ?. id ,
12591265 )
1260- // Some providers return "stop" even when the assistant message contains tool calls.
1261- // Keep the loop running so tool results can be sent back to the model.
1262- // Skip provider-executed tool parts — those were fully handled within the
1263- // provider's stream (e.g. DWS Agent Platform) and don't need a re-loop.
1266+ // Some providers return "stop" even when the assistant message contains
1267+ // tool calls. Keep the loop running so tool results can be sent back to
1268+ // the model, but ignore cleanup-marked interrupted orphans.
12641269 const hasToolCalls =
1265- lastAssistantMsg ?. parts . some ( ( part ) => part . type === "tool" && ! part . metadata ?. providerExecuted ) ?? false
1270+ lastAssistantMsg ?. parts . some (
1271+ ( part ) => part . type === "tool" && ! part . metadata ?. providerExecuted && ! isOrphanedInterruptedTool ( part ) ,
1272+ ) ?? false
12661273
12671274 if (
12681275 lastAssistant ?. finish &&
12691276 ! [ "tool-calls" ] . includes ( lastAssistant . finish ) &&
12701277 ! hasToolCalls &&
12711278 lastUser . id < lastAssistant . id
12721279 ) {
1280+ const orphan = lastAssistantMsg ?. parts . find (
1281+ ( part ) : part is MessageV2 . ToolPart => part . type === "tool" && isOrphanedInterruptedTool ( part ) ,
1282+ )
1283+ if ( orphan ) {
1284+ yield * slog . warn ( "loop exit with orphaned interrupted tool" , {
1285+ messageID : lastAssistant . id ,
1286+ tool : orphan . tool ,
1287+ callID : orphan . callID ,
1288+ } )
1289+ }
12731290 yield * slog . info ( "exiting loop" )
12741291 break
12751292 }
0 commit comments