diff --git a/apps/cli/src/backends/gemini/runGemini.ts b/apps/cli/src/backends/gemini/runGemini.ts index 0f9529ebba..86e0f4c664 100644 --- a/apps/cli/src/backends/gemini/runGemini.ts +++ b/apps/cli/src/backends/gemini/runGemini.ts @@ -74,10 +74,9 @@ import { import { maybeUpdateGeminiSessionIdMetadata } from '@/backends/gemini/utils/geminiSessionIdMetadata'; import { updateMetadataBestEffort } from '@/api/session/sessionWritesBestEffort'; import { - parseOptionsFromText, - hasIncompleteOptions, + segmentTrailingOptions, formatOptionsXml, -} from '@/backends/gemini/utils/optionsParser'; +} from '@/utils/optionsParser'; import { ConversationHistory } from '@/backends/gemini/utils/conversationHistory'; import { createGeminiBackendMessageHandler } from '@/backends/gemini/runtime/createGeminiBackendMessageHandler'; import { reportGeminiConnectedServiceRuntimeAuthFailureBestEffort } from '@/backends/gemini/connectedServices/surfaceGeminiConnectedServiceRuntimeAuthFailure'; @@ -983,7 +982,11 @@ export async function runGemini(opts: { // Send accumulated response to mobile app ONLY when turn is complete // This prevents message fragmentation from Gemini's chunked responses if (hasAssistantOutput) { - const { text: messageText, options } = parseOptionsFromText(turnMessageState.accumulatedResponse); + const { before, options, hasIncompleteTrailingOptions } = segmentTrailingOptions(turnMessageState.accumulatedResponse); + // Trim the app-facing message (Gemini display concern) to stay + // byte-identical to the previous parseOptionsFromText behavior; the + // terminal formatter path preserves surrounding whitespace verbatim. + const messageText = before.trim(); // Record assistant response in conversation history for context preservation conversationHistory.addAssistantMessage(messageText); @@ -994,7 +997,7 @@ export async function runGemini(opts: { const optionsXml = formatOptionsXml(options); finalMessageText = messageText + optionsXml; logger.debug(`[gemini] Found ${options.length} options in response`); - } else if (hasIncompleteOptions(turnMessageState.accumulatedResponse)) { + } else if (hasIncompleteTrailingOptions) { logger.debug(`[gemini] Warning: Incomplete options block detected`); } diff --git a/apps/cli/src/backends/gemini/utils/optionsParser.ts b/apps/cli/src/backends/gemini/utils/optionsParser.ts deleted file mode 100644 index bf105dd1f8..0000000000 --- a/apps/cli/src/backends/gemini/utils/optionsParser.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Options Parser Utilities - * - * Utilities for parsing and formatting XML options blocks from agent responses. - * Used for extracting and formatting blocks. - */ - -/** - * Check if text has an incomplete options block (opening tag but no closing tag) - * - * @param text - The text to check - * @returns true if there's an opening tag without a closing tag - */ -export function hasIncompleteOptions(text: string): boolean { - const hasOpeningTag = //i.test(text); - const hasClosingTag = /<\/options>/i.test(text); - return hasOpeningTag && !hasClosingTag; -} - -/** - * Parse XML options from text - * Extracts blocks and returns - * the text without options and the parsed options array - * - * @param text - The text containing options XML - * @returns Object with text (without options) and options array - */ -export function parseOptionsFromText(text: string): { text: string; options: string[] } { - // Match ... block (multiline, non-greedy) - const optionsRegex = /\s*([\s\S]*?)\s*<\/options>/i; - const match = text.match(optionsRegex); - - if (!match) { - return { text: text.trim(), options: [] }; - } - - // Extract options block content - const optionsBlock = match[1]; - - // Parse individual