diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 5d79e000645..ca5549d13ff 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1600,21 +1600,6 @@ NOTE: At any point in time through this workflow you should feel free to ask the toolChoice: format.type === "json_schema" ? "required" : undefined, }) - // kilocode_change start - guard against providers that end the stream - // without a terminal stop_reason (e.g. an Anthropic-style message_delta - // with stop_reason: null followed immediately by message_stop). Without - // a finishReason, the loop-exit check at the top of the next iteration - // sees a falsy `finish` (loaded from storage via filterCompactedEffect) - // and keeps stepping forever. Default to "unknown" and persist so the - // regular break condition fires when there are no tool calls — the - // tool-call path is unaffected because `!hasToolCalls` keeps the break - // gated when the assistant did emit tool calls. - if (!handle.message.finish) { - handle.message.finish = "unknown" - yield* sessions.updateMessage(handle.message) - } - // kilocode_change end - if (structured !== undefined) { handle.message.structured = structured handle.message.finish = handle.message.finish ?? "stop" @@ -1672,6 +1657,21 @@ NOTE: At any point in time through this workflow you should feel free to ask the return "break" as const } // kilocode_change end + // kilocode_change start - guard against providers that end the stream + // without a terminal stop_reason (e.g. an Anthropic-style message_delta + // with stop_reason: null followed immediately by message_stop). Without + // a finishReason, the loop-exit check at the top of the next iteration + // sees a falsy `finish` (loaded from storage via filterCompactedEffect) + // and keeps stepping forever. Default to "unknown" and persist so the + // regular break condition fires when there are no tool calls. Skipped + // for the compact path so guardCompactionAttempt can still fill in + // "error" on exhaustion. Tool-call turns already get "tool-calls" from + // the AI SDK; even without it, !hasToolCalls keeps the break gated. + if (result !== "compact" && !handle.message.finish) { + handle.message.finish = "unknown" + yield* sessions.updateMessage(handle.message) + } + // kilocode_change end return "continue" as const }).pipe(Effect.ensuring(instruction.clear(handle.message.id))) if (outcome === "break") break