From 43c4491560e14c70cade9036e4064e2d671a703f Mon Sep 17 00:00:00 2001 From: chrarnoldus <12196001+chrarnoldus@users.noreply.github.com> Date: Fri, 21 Aug 2026 07:30:33 +0000 Subject: [PATCH] fix(cli): send max-step instruction as user message Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> --- .changeset/fix-max-steps-prefill.md | 5 +++ packages/core/src/session/runner/llm.ts | 2 +- packages/core/test/session-runner.test.ts | 2 +- packages/opencode/src/session/prompt.ts | 2 +- packages/opencode/test/session/prompt.test.ts | 36 +++++++++++++++++++ 5 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 .changeset/fix-max-steps-prefill.md diff --git a/.changeset/fix-max-steps-prefill.md b/.changeset/fix-max-steps-prefill.md new file mode 100644 index 0000000000..af5f6c67a6 --- /dev/null +++ b/.changeset/fix-max-steps-prefill.md @@ -0,0 +1,5 @@ +--- +"@kilocode/cli": patch +--- + +Prevent provider errors when agents reach their step limit by sending the final summary instruction as user input instead of an assistant prefill. diff --git a/packages/core/src/session/runner/llm.ts b/packages/core/src/session/runner/llm.ts index a71ec3e423..d7cd855772 100644 --- a/packages/core/src/session/runner/llm.ts +++ b/packages/core/src/session/runner/llm.ts @@ -209,7 +209,7 @@ const layer = Layer.effect( system: [agent.info?.system, system.baseline] .filter((part): part is string => part !== undefined && part.length > 0) .map(SystemPart.make), - messages: [...toLLMMessages(context, model), ...(isLastStep ? [Message.assistant(MAX_STEPS_PROMPT)] : [])], + messages: [...toLLMMessages(context, model), ...(isLastStep ? [Message.user(MAX_STEPS_PROMPT)] : [])], // kilocode_change - avoid provider-incompatible assistant prefill tools: toolMaterialization?.definitions ?? [], toolChoice: isLastStep ? "none" : undefined, }) diff --git a/packages/core/test/session-runner.test.ts b/packages/core/test/session-runner.test.ts index 294cddd747..289648a84c 100644 --- a/packages/core/test/session-runner.test.ts +++ b/packages/core/test/session-runner.test.ts @@ -3022,7 +3022,7 @@ describe("SessionRunnerLLM", () => { expect(requests[1]?.toolChoice).toMatchObject({ type: "none" }) expect(requests[1]?.tools).toEqual([]) expect(requests[1]?.messages.at(-1)).toMatchObject({ - role: "assistant", + role: "user", // kilocode_change - max-step instructions must not become assistant prefill content: [{ type: "text", text: expect.stringContaining("MAXIMUM STEPS REACHED") }], }) expect(executions).toEqual(["done"]) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index d325e3937d..092666a3ef 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1772,7 +1772,7 @@ export const layer = Layer.effect( system, messages: [ ...modelMsgs, - ...(isLastStep ? [{ role: "assistant" as const, content: MAX_STEPS_PROMPT }] : []), + ...(isLastStep ? [{ role: "user" as const, content: MAX_STEPS_PROMPT }] : []), // kilocode_change - avoid provider-incompatible assistant prefill ], tools, model, diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index 1205bb897d..353ee6504f 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -550,6 +550,42 @@ it.instance("loop calls LLM and returns assistant message", () => }), ) +// kilocode_change start - guard provider-compatible max-step request shape +it.instance( + "loop sends max steps instruction as a user message", + () => + Effect.gen(function* () { + const { llm } = yield* useServerConfig((url) => ({ + ...providerCfg(url), + agent: { build: { steps: 1 } }, + })) + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const chat = yield* sessions.create({ title: "Pinned" }) + yield* prompt.prompt({ + sessionID: chat.id, + agent: "build", + noReply: true, + parts: [{ type: "text", text: "finish at the limit" }], + }) + yield* llm.text("summary") + + yield* prompt.loop({ sessionID: chat.id }) + + const inputs = yield* llm.inputs + const messages = inputs.at(-1)?.messages + if (!Array.isArray(messages)) throw new Error("expected LLM messages") + expect(messages.at(-1)).toMatchObject({ + role: "user", + content: expect.arrayContaining([ + { type: "text", text: expect.stringContaining("MAXIMUM STEPS REACHED") }, + ]), + }) + }), + 30_000, +) +// kilocode_change end + noLLMServer.instance( "new prompt dismisses a pending question", () =>