From 1e4182b0ebe3fce52ca98b2e482988011afde10d Mon Sep 17 00:00:00 2001 From: nrslib <38722970+nrslib@users.noreply.github.com> Date: Wed, 11 Feb 2026 08:47:46 +0900 Subject: [PATCH] =?UTF-8?q?opencode=20=E3=81=A7=E3=83=97=E3=83=AD=E3=83=B3?= =?UTF-8?q?=E3=83=97=E3=83=88=E3=81=8Cecho=E3=81=95=E3=82=8C=E3=82=8B?= =?UTF-8?q?=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/infra/opencode/client.ts | 44 +++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/infra/opencode/client.ts b/src/infra/opencode/client.ts index d87c06d..fa2690e 100644 --- a/src/infra/opencode/client.ts +++ b/src/infra/opencode/client.ts @@ -17,6 +17,7 @@ import { type OpenCodeTextPart, createStreamTrackingState, emitInit, + emitText, emitResult, handlePartUpdated, } from './OpenCodeStreamHandler.js'; @@ -40,6 +41,31 @@ const OPENCODE_RETRYABLE_ERROR_PATTERNS = [ 'failed to start server on port', ]; +function getCommonPrefixLength(a: string, b: string): number { + const max = Math.min(a.length, b.length); + let i = 0; + while (i < max && a[i] === b[i]) { + i += 1; + } + return i; +} + +function stripPromptEcho( + chunk: string, + echoState: { remainingPrompt: string }, +): string { + if (!chunk) return ''; + if (!echoState.remainingPrompt) return chunk; + + const consumeLength = getCommonPrefixLength(chunk, echoState.remainingPrompt); + if (consumeLength > 0) { + echoState.remainingPrompt = echoState.remainingPrompt.slice(consumeLength); + return chunk.slice(consumeLength); + } + + return chunk; +} + async function getFreePort(): Promise { return new Promise((resolve, reject) => { const server = createServer(); @@ -197,6 +223,8 @@ export class OpenCodeClient { let success = true; let failureMessage = ''; const state = createStreamTrackingState(); + const echoState = { remainingPrompt: fullPrompt }; + const textOffsets = new Map(); const textContentParts = new Map(); for await (const event of stream) { @@ -212,7 +240,21 @@ export class OpenCodeClient { if (part.type === 'text') { const textPart = part as OpenCodeTextPart; - textContentParts.set(textPart.id, textPart.text); + const prev = textOffsets.get(textPart.id) ?? 0; + const rawDelta = delta + ?? (textPart.text.length > prev ? textPart.text.slice(prev) : ''); + + textOffsets.set(textPart.id, textPart.text.length); + + if (rawDelta) { + const visibleDelta = stripPromptEcho(rawDelta, echoState); + if (visibleDelta) { + emitText(options.onStream, visibleDelta); + const previous = textContentParts.get(textPart.id) ?? ''; + textContentParts.set(textPart.id, `${previous}${visibleDelta}`); + } + } + continue; } handlePartUpdated(part, delta, options.onStream, state);