diff --git a/opencode/packages/opencode/src/session/compaction.ts b/opencode/packages/opencode/src/session/compaction.ts index 73a70af..81998b0 100644 --- a/opencode/packages/opencode/src/session/compaction.ts +++ b/opencode/packages/opencode/src/session/compaction.ts @@ -164,7 +164,7 @@ export namespace SessionCompaction { model, }) - if (result === "continue" && input.auto) { + if (result.status === "continue" && input.auto) { const continueMsg = await Session.updateMessage({ id: Identifier.ascending("message"), role: "user", diff --git a/opencode/packages/opencode/src/session/llm.ts b/opencode/packages/opencode/src/session/llm.ts index afa4984..befa5b9 100644 --- a/opencode/packages/opencode/src/session/llm.ts +++ b/opencode/packages/opencode/src/session/llm.ts @@ -45,7 +45,10 @@ export namespace LLM { allProviders?: Record } - export type StreamOutput = StreamTextResult + export type StreamOutput = { + result: StreamTextResult + singleStepTools: boolean + } export type RetryState = { attempt: number @@ -178,7 +181,7 @@ export namespace LLM { ProviderSwitch.recordSuccess(input.model.providerID, input.model.id) - return result + return { result, singleStepTools } } catch (error) { retryState.lastError = error as Error ProviderSwitch.recordFailure(input.model.providerID, input.model.id) @@ -454,10 +457,16 @@ export namespace LLM { ) } - export async function stream(input: StreamInput) { + export async function stream(input: StreamInput): Promise { return streamWithProvider(input) } + export function shouldLimitToolLoopForModel(model: Provider.Model): boolean { + if (model.providerID === "chutes") return true + const url = model.api.url?.toLowerCase() || "" + return url.includes("chutes.ai") || url.includes("/chutes/") + } + async function resolveTools(input: Pick) { const disabled = PermissionNext.disabled(Object.keys(input.tools), input.agent.permission) for (const tool of Object.keys(input.tools)) { diff --git a/opencode/packages/opencode/src/session/processor.ts b/opencode/packages/opencode/src/session/processor.ts index dc2528b..a5bf911 100644 --- a/opencode/packages/opencode/src/session/processor.ts +++ b/opencode/packages/opencode/src/session/processor.ts @@ -57,7 +57,7 @@ export namespace SessionProcessor { const log = Log.create({ service: "session.processor" }) export type Info = Awaited> - export type Result = Awaited> + export type Result = Awaited> & { singleStepTools: boolean } export function create(input: { assistantMessage: MessageV2.Assistant @@ -70,6 +70,7 @@ export namespace SessionProcessor { let blocked = false let attempt = 0 let needsCompaction = false + let singleStepTools = false const result = { get message() { @@ -78,7 +79,7 @@ export namespace SessionProcessor { partFromToolCall(toolCallID: string) { return toolcalls[toolCallID] }, - async process(streamInput: LLM.StreamInput & { allProviders?: Record }) { + async process(streamInput: LLM.StreamInput & { allProviders?: Record }): Promise<{ status: "compact" | "stop" | "continue"; singleStepTools: boolean }> { log.info("process") needsCompaction = false const shouldBreak = (await Config.get()).experimental?.continue_loop_on_deny !== true @@ -86,12 +87,13 @@ export namespace SessionProcessor { try { let currentText: MessageV2.TextPart | undefined let reasoningMap: Record = {} - const stream = await LLM.stream({ + const streamOutput = await LLM.stream({ ...streamInput, allProviders: streamInput.allProviders || await Provider.list(), }) + singleStepTools = streamOutput.singleStepTools - for await (const value of stream.fullStream) { + for await (const value of streamOutput.result.fullStream) { input.abort.throwIfAborted() switch (value.type) { case "start": @@ -438,11 +440,13 @@ export namespace SessionProcessor { } input.assistantMessage.time.completed = Date.now() await Session.updateMessage(input.assistantMessage) - if (needsCompaction) return "compact" - if (blocked) return "stop" - if (input.assistantMessage.error) return "stop" - return "continue" + if (needsCompaction) return { status: "compact" as const, singleStepTools } + if (blocked) return { status: "stop" as const, singleStepTools } + if (input.assistantMessage.error) return { status: "stop" as const, singleStepTools } + if (singleStepTools) return { status: "continue" as const, singleStepTools } + break } + return { status: "continue" as const, singleStepTools } }, } return result diff --git a/opencode/packages/opencode/src/session/prompt.ts b/opencode/packages/opencode/src/session/prompt.ts index e747579..4daf1e7 100644 --- a/opencode/packages/opencode/src/session/prompt.ts +++ b/opencode/packages/opencode/src/session/prompt.ts @@ -631,8 +631,8 @@ export namespace SessionPrompt { model, allProviders, }) - if (result === "stop") break - if (result === "compact") { + if (result.status === "stop") break + if (result.status === "compact") { await SessionCompaction.create({ sessionID, agent: lastUser.agent, @@ -640,6 +640,7 @@ export namespace SessionPrompt { auto: true, }) } + if (result.singleStepTools) break continue } SessionCompaction.prune({ sessionID })