From 0b3bbfec061f55a65975b6dce77c04dba1925f9c Mon Sep 17 00:00:00 2001 From: Viz Date: Tue, 3 Mar 2026 00:40:54 -0500 Subject: [PATCH] fix(gateway+acp): thread stopReason through final event to ACP bridge (#24867) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete the stop reason propagation chain so ACP clients can distinguish end_turn from max_tokens: - server-chat.ts: emitChatFinal accepts optional stopReason param, includes it in the final payload, reads it from lifecycle event data - translator.ts: read stopReason from the final payload instead of hardcoding end_turn Chain: LLM API → run.ts (meta.stopReason) → agent.ts (lifecycle event) → server-chat.ts (final payload) → ACP translator (PromptResponse) --- src/acp/translator.ts | 4 +++- src/gateway/server-chat.ts | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/acp/translator.ts b/src/acp/translator.ts index 5039cb15504..c7cf3739a9a 100644 --- a/src/acp/translator.ts +++ b/src/acp/translator.ts @@ -423,7 +423,9 @@ export class AcpGatewayAgent implements Agent { } if (state === "final") { - this.finishPrompt(pending.sessionId, pending, "end_turn"); + const rawStopReason = payload.stopReason as string | undefined; + const stopReason: StopReason = rawStopReason === "max_tokens" ? "max_tokens" : "end_turn"; + this.finishPrompt(pending.sessionId, pending, stopReason); return; } if (state === "aborted") { diff --git a/src/gateway/server-chat.ts b/src/gateway/server-chat.ts index 00dc2a9d359..67da7cd8d1d 100644 --- a/src/gateway/server-chat.ts +++ b/src/gateway/server-chat.ts @@ -346,6 +346,7 @@ export function createAgentEventHandler({ seq: number, jobState: "done" | "error", error?: unknown, + stopReason?: string, ) => { const bufferedText = stripInlineDirectiveTagsForDisplay( chatRunState.buffers.get(clientRunId) ?? "", @@ -399,6 +400,7 @@ export function createAgentEventHandler({ sessionKey, seq, state: "final" as const, + ...(stopReason && { stopReason }), message: text && !shouldSuppressSilent ? { @@ -512,6 +514,8 @@ export function createAgentEventHandler({ if (!isAborted && evt.stream === "assistant" && typeof evt.data?.text === "string") { emitChatDelta(sessionKey, clientRunId, evt.runId, evt.seq, evt.data.text); } else if (!isAborted && (lifecyclePhase === "end" || lifecyclePhase === "error")) { + const evtStopReason = + typeof evt.data?.stopReason === "string" ? evt.data.stopReason : undefined; if (chatLink) { const finished = chatRunState.registry.shift(evt.runId); if (!finished) { @@ -525,6 +529,7 @@ export function createAgentEventHandler({ evt.seq, lifecyclePhase === "error" ? "error" : "done", evt.data?.error, + evtStopReason, ); } else { emitChatFinal( @@ -534,6 +539,7 @@ export function createAgentEventHandler({ evt.seq, lifecyclePhase === "error" ? "error" : "done", evt.data?.error, + evtStopReason, ); } } else if (isAborted && (lifecyclePhase === "end" || lifecyclePhase === "error")) {