private/Invoke-StreamingExecution.ps1
|
function Invoke-StreamingExecution { <# .SYNOPSIS Executes an AI tool with streaming output to console. .DESCRIPTION Handles real-time streaming of AI tool output. For tools with JSON streaming (Claude, Gemini), parses the stream-json format and extracts text content. For other tools, pipes output directly to console. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$ToolName, [Parameter(Mandatory)] [string]$ToolCommand, [Parameter()] [string[]]$Arguments, [Parameter()] [string]$FullPrompt, [Parameter()] [string]$Context = "Streaming operation" ) $startTime = Get-Date $streamedContent = [System.Text.StringBuilder]::new() $originalOutputEncoding = [Console]::OutputEncoding [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 try { # Determine execution style based on tool $usesPipedInput = $ToolName -notin @('Aider', 'Codex', 'Cursor') if ($usesPipedInput -and $FullPrompt) { $process = $FullPrompt | & $ToolCommand @Arguments 2>&1 } else { $process = & $ToolCommand @Arguments 2>&1 } $process | ForEach-Object { $line = $_ if ($line -is [System.Management.Automation.ErrorRecord]) { Write-PSFMessage -Level Debug -Message $line.Exception.Message } elseif ($ToolName -in @('Claude', 'Gemini') -and $line -match '^\s*\{') { # Try to parse as JSON streaming event try { $jsonEvent = $line | ConvertFrom-Json -ErrorAction Stop # Handle Claude CLI stream-json format (complete JSON objects per line) switch ($jsonEvent.type) { 'system' { # Init event - show model info if ($jsonEvent.subtype -eq 'init' -and $jsonEvent.model) { Write-Host "[" -NoNewline Write-Host $jsonEvent.model -ForegroundColor DarkCyan -NoNewline Write-Host "]" -NoNewline Write-Host "" } } 'assistant' { # Message with content - can be text or tool_use if ($jsonEvent.message.content) { foreach ($block in $jsonEvent.message.content) { if ($block.type -eq 'text') { Write-Host $block.text -NoNewline $null = $streamedContent.Append($block.text) } elseif ($block.type -eq 'tool_use') { # Show tool usage with summary Write-Host "`n[" -NoNewline Write-Host $block.name -ForegroundColor Cyan -NoNewline Write-Host "] " -NoNewline # Show relevant info based on tool type $summary = switch -Wildcard ($block.name) { 'Read' { if ($block.input.file_path) { $block.input.file_path } else { "file" } } 'Edit' { if ($block.input.file_path) { $block.input.file_path } else { "file" } } 'Write' { if ($block.input.file_path) { $block.input.file_path } else { "file" } } 'Bash' { if ($block.input.command) { $cmd = $block.input.command $cmd.Substring(0, [Math]::Min(60, $cmd.Length)) + $(if ($cmd.Length -gt 60) { "..." } else { "" }) } else { "command" } } 'Glob' { if ($block.input.pattern) { $block.input.pattern } else { "pattern" } } 'Grep' { if ($block.input.pattern) { $block.input.pattern } else { "pattern" } } 'Task' { if ($block.input.description) { $block.input.description } else { "task" } } 'TodoWrite' { "updating todos" } default { "" } } if ($summary) { Write-Host $summary -ForegroundColor DarkGray } else { Write-Host "" } } } } } 'user' { # Tool result - show brief indicator if ($jsonEvent.tool_use_result) { Write-Host " [" -NoNewline Write-Host "done" -ForegroundColor Green -NoNewline Write-Host "]" -NoNewline } } 'result' { # Final result Write-Host "" if ($jsonEvent.subtype -eq 'success') { Write-Host "[" -NoNewline Write-Host "completed" -ForegroundColor Green -NoNewline Write-Host "] " -NoNewline if ($jsonEvent.duration_ms) { $secs = [Math]::Round($jsonEvent.duration_ms / 1000, 1) Write-Host "${secs}s" -ForegroundColor DarkGray } } elseif ($jsonEvent.subtype -eq 'error') { Write-Host "[" -NoNewline Write-Host "error" -ForegroundColor Red -NoNewline Write-Host "] " -NoNewline if ($jsonEvent.result) { Write-Host $jsonEvent.result -ForegroundColor Red } } } default { # Check for Gemini format or other content if ($jsonEvent.content) { Write-Host $jsonEvent.content -NoNewline $null = $streamedContent.Append($jsonEvent.content) } } } } catch { # Not valid JSON, output as-is Write-Host $line $null = $streamedContent.AppendLine($line) } } else { # Non-JSON output, display directly Write-Host $line $null = $streamedContent.AppendLine($line) } } $exitCode = $LASTEXITCODE } finally { [Console]::OutputEncoding = $originalOutputEncoding } $endTime = Get-Date @{ Output = $streamedContent.ToString() ExitCode = $exitCode Success = ($exitCode -eq 0) Duration = [timespan]::FromSeconds([Math]::Floor(($endTime - $startTime).TotalSeconds)) } } |