workflows/default/systems/runtime/ProviderCLI/parsers/Parse-GeminiStream.ps1

<#
.SYNOPSIS
Gemini (Google) stream parser for ProviderCLI.

.DESCRIPTION
Processes Gemini CLI --output-format stream-json output. Gemini CLI is built on
the same MCP SDK foundation as Claude CLI, so the stream format may share structure
with Claude's stream-json events. This parser handles both the Claude-like format
and Gemini-specific variations.

Provides Process-StreamLine function for the ProviderCLI dispatcher.
#>


# Import helpers
if (-not (Get-Command Write-ActivityLog -ErrorAction SilentlyContinue)) {
    Import-Module "$PSScriptRoot\..\..\ClaudeCLI\ClaudeCLI.psm1" -Force
}

function Process-StreamLine {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Line,

        [Parameter(Mandatory)]
        [hashtable]$State,

        [switch]$ShowDebugJson,
        [switch]$ShowVerbose
    )

    $t = $State.theme

    # Skip non-JSON lines (Gemini emits plain text for YOLO mode banner, errors, etc.)
    if (-not $Line -or $Line[0] -ne '{') {
        # Check for rate limit in plain text
        if ($Line -match "rate.?limit|quota|429|too many requests") {
            $State.rateLimitMessage = $Line
            [Console]::Error.WriteLine("$($t.Amber)Rate limit: $Line$($t.Reset)")
            [Console]::Error.Flush()
            Write-ActivityLog -Type "rate_limit" -Message $Line
            return 'rate_limit'
        }
        if ($ShowDebugJson) {
            [Console]::Error.WriteLine("$($t.Bezel)[SKIP] $Line$($t.Reset)")
            [Console]::Error.Flush()
        }
        return 'skip'
    }

    if ($ShowDebugJson) {
        [Console]::Error.WriteLine("$($t.Bezel)[JSON] $Line$($t.Reset)")
        [Console]::Error.Flush()
    }

    $evt = $null
    try { $evt = $Line | ConvertFrom-Json -ErrorAction Stop } catch { return 'skip' }
    if (-not $evt) { return 'skip' }

    # --- Claude-like event handling (Gemini stream-json may share this format) ---

    # Assistant text streaming
    $text = $null
    if ($evt.message?.delta?.text) {
        $text = $evt.message.delta.text
    }
    elseif ($evt.message?.content -is [System.Array]) {
        foreach ($b in $evt.message.content) {
            if ($b.type -eq "text" -and $b.text) { $text += $b.text }
            elseif ($b.delta?.text) { $text += $b.delta.text }
        }
    }
    elseif ($evt.message?.content -is [string]) {
        $text = $evt.message.content
    }

    # Track token usage
    if ($evt.message?.usage -or $evt.usage) {
        $usage = if ($evt.message?.usage) { $evt.message.usage } else { $evt.usage }
        if ($usage.input_tokens) { $State.totalInputTokens += $usage.input_tokens }
        if ($usage.output_tokens) { $State.totalOutputTokens += $usage.output_tokens }
    }

    if ($text) {
        [void]$State.assistantText.Append($text)
        return 'text'
    }

    # Init/config event (Claude-like)
    if ($evt.type -and $evt.model -and $evt.cwd) {
        Write-ClaudeLog "init" "Gemini: $($evt.model)" "*"
        Write-ActivityLog -Type "init" -Message "Gemini model: $($evt.model)"
        return 'init'
    }

    # Tool use (Claude-like)
    if ($evt.type -eq "assistant" -and $evt.message?.content -is [System.Array]) {
        $toolUses = @($evt.message.content | Where-Object { $_.type -eq "tool_use" })
        if ($toolUses.Count -gt 0) {
            # Flush assistant text
            if ($State.assistantText.Length -gt 0) {
                [Console]::WriteLine("")
                [Console]::WriteLine($State.assistantText.ToString())
                Write-ActivityLog -Type "text" -Message (Get-PreviewText $State.assistantText.ToString() 200)
                [Console]::Out.Flush()
                $State.assistantText.Length = 0
            }
            foreach ($tu in $toolUses) {
                $detail = ""
                if ($tu.input) {
                    if ($tu.input.command) { $detail = Get-PreviewText $tu.input.command 140 }
                    elseif ($tu.input.file_path) { $detail = $tu.input.file_path }
                    elseif ($tu.input.description) { $detail = Get-PreviewText $tu.input.description 140 }
                }
                if (-not $detail) { $detail = "" }
                Write-ClaudeLog $tu.name $detail ">"
                Write-ActivityLog -Type $tu.name -Message $detail
            }
            return 'tool_use'
        }
    }

    # Tool result (Claude-like)
    if ($evt.type -eq "user" -and $evt.message?.content -is [System.Array]) {
        $toolResults = @($evt.message.content | Where-Object { $_.type -eq "tool_result" })
        if ($toolResults.Count -gt 0) {
            if ($State.assistantText.Length -gt 0) {
                [Console]::WriteLine("")
                [Console]::WriteLine($State.assistantText.ToString())
                Write-ActivityLog -Type "text" -Message (Get-PreviewText $State.assistantText.ToString() 200)
                [Console]::Out.Flush()
                $State.assistantText.Length = 0
            }
            foreach ($tr in $toolResults) {
                $isErr = [bool]$tr.is_error
                $icon = if ($isErr) { "x" } else { "+" }
                Write-ClaudeLog "done" "" $icon
            }
            return 'tool_result'
        }
    }

    # Result summary (Claude-like)
    if ($evt.type -eq "result") {
        if ($State.assistantText.Length -gt 0) {
            [Console]::WriteLine("")
            [Console]::WriteLine($State.assistantText.ToString())
            Write-ActivityLog -Type "text" -Message (Get-PreviewText $State.assistantText.ToString() 200)
            [Console]::Out.Flush()
            $State.assistantText.Length = 0
        }
        Format-ResultSummary $evt
        return 'result'
    }

    # Error event
    if ($evt.type -eq "error" -or $evt.error) {
        $errorMsg = if ($evt.message) { $evt.message } elseif ($evt.error?.message) { $evt.error.message } else { "Unknown error" }

        if ($errorMsg -match "rate.?limit|quota|429") {
            $State.rateLimitMessage = $errorMsg
            [Console]::Error.WriteLine("$($t.Amber)Rate limit: $errorMsg$($t.Reset)")
            [Console]::Error.Flush()
            Write-ActivityLog -Type "rate_limit" -Message $errorMsg
            return 'rate_limit'
        }

        [Console]::Error.WriteLine("")
        [Console]::Error.WriteLine("$($t.Amber)Error: $errorMsg$($t.Reset)")
        [Console]::Error.Flush()
        Write-ActivityLog -Type "error" -Message $errorMsg
        return 'error'
    }

    if ($ShowDebugJson) {
        [Console]::Error.WriteLine("$($t.Bezel)[UNKNOWN] type=$($evt.type)$($t.Reset)")
        [Console]::Error.Flush()
    }
    return 'unknown'
}