From 6fbef5b5a9c46a76bfe82ca084ba6cc3bbef196a Mon Sep 17 00:00:00 2001 From: amelvil2 Date: Thu, 3 Sep 2026 10:25:36 -0400 Subject: [PATCH] Added fix for streams ending without a finish reason Added plugin hook finish.chunk so that plugins that e.g. add providers can add custom handling if needed. --- packages/opencode/src/session/processor.ts | 30 ++++++++++++++++++++++ packages/plugin/src/index.ts | 11 ++++++++ 2 files changed, 41 insertions(+) diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index 9f8530929c15..ccab023175bd 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -457,6 +457,36 @@ const layer = Layer.effect( ctx.assistantMessage.finish = value.reason ctx.assistantMessage.cost += usage.cost ctx.assistantMessage.tokens = usage.tokens + + const isKnownFinishReason = + value.reason == 'stop' + || value.reason == 'tool-calls' + || value.reason == 'length' + || value.reason == 'error' + || value.reason == 'content-filter' + + const finishReasonMessage = isKnownFinishReason ? + `Stream ended with a known finish reason "${value.reason}"` + : `Stream ended with an unrecognized finish reason "${value.reason}".` + + const output = { + ok: isKnownFinishReason, + message: finishReasonMessage + } + yield* plugin.trigger( + "finish.chunk", + { + finishReason: value.reason, + cost: usage.cost, + tokens: usage.tokens + }, + output + ) + yield* Effect.logDebug(`Got output from finish.chunk: ${JSON.stringify(output, null, 4)}`) + if (output.ok === false) { + throw new Error(`Error: ${output.message}`) + } + yield* session.updatePart({ id: PartID.ascending(), reason: value.reason, diff --git a/packages/plugin/src/index.ts b/packages/plugin/src/index.ts index edfa0139dfca..7519c56062e0 100644 --- a/packages/plugin/src/index.ts +++ b/packages/plugin/src/index.ts @@ -332,4 +332,15 @@ export interface Hooks { * Modify tool definitions (description and parameters) sent to LLM */ "tool.definition"?: (input: { toolID: string }, output: { description: string; parameters: any }) => Promise + "finish.chunk"?: ( + input: { + finishReason: string, + cost: number, + tokens: number + }, + output: { + ok: boolean, + message: string + } + ) => Promise }