From b80234aa3ecf75d71dbf1e136f5c1aafe43b3545 Mon Sep 17 00:00:00 2001 From: Sri Pasumarthi <111310667+sripasg@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:16:36 -0700 Subject: [PATCH] fix(acp): handle all InvalidStreamError types gracefully in prompt (#24540) --- packages/cli/src/acp/acpClient.test.ts | 26 ++++++++++++++++++++++++++ packages/cli/src/acp/acpClient.ts | 5 ++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/acp/acpClient.test.ts b/packages/cli/src/acp/acpClient.test.ts index f077b0ef4b..470ff38351 100644 --- a/packages/cli/src/acp/acpClient.test.ts +++ b/packages/cli/src/acp/acpClient.test.ts @@ -875,6 +875,32 @@ describe('Session', () => { expect(result).toMatchObject({ stopReason: 'end_turn' }); }); + it('should handle prompt with no finish reason (InvalidStreamError)', async () => { + mockChat.sendMessageStream.mockRejectedValue( + new InvalidStreamError('No finish reason', 'NO_FINISH_REASON'), + ); + + const result = await session.prompt({ + sessionId: 'session-1', + prompt: [{ type: 'text', text: 'Hi' }], + }); + + expect(mockChat.sendMessageStream).toHaveBeenCalled(); + expect(result).toMatchObject({ stopReason: 'end_turn' }); + }); + + it('should handle prompt with no finish reason (NO_FINISH_REASON anomaly)', async () => { + mockChat.sendMessageStream.mockRejectedValue({ type: 'NO_FINISH_REASON' }); + + const result = await session.prompt({ + sessionId: 'session-1', + prompt: [{ type: 'text', text: 'Hi' }], + }); + + expect(mockChat.sendMessageStream).toHaveBeenCalled(); + expect(result).toMatchObject({ stopReason: 'end_turn' }); + }); + it('should handle /memory command', async () => { const handleCommandSpy = vi .spyOn( diff --git a/packages/cli/src/acp/acpClient.ts b/packages/cli/src/acp/acpClient.ts index 14761d7162..e0a352e0d1 100644 --- a/packages/cli/src/acp/acpClient.ts +++ b/packages/cli/src/acp/acpClient.ts @@ -863,7 +863,10 @@ export class Session { (error && typeof error === 'object' && 'type' in error && - error.type === 'NO_RESPONSE_TEXT') + (error.type === 'NO_RESPONSE_TEXT' || + error.type === 'NO_FINISH_REASON' || + error.type === 'MALFORMED_FUNCTION_CALL' || + error.type === 'UNEXPECTED_TOOL_CALL')) ) { // The stream ended with an empty response or malformed tool call. // Treat this as a graceful end to the model's turn rather than a crash.