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