outputhandlers.ps1
|
#region Output Handler Functions for psfoundrylocal Crescendo Module # These functions parse the output from Foundry Local CLI commands # and convert them to PowerShell objects for pipeline use. <# .SYNOPSIS Converts the output from 'foundry model list' to PowerShell objects. .DESCRIPTION Parses the tabular output from the foundry model list command and returns structured FoundryLocalModel objects with Alias, Device, Task, FileSize, License, and ModelId properties. .PARAMETER Output The raw output from the foundry model list command. #> function Convert-FoundryLocalModelListOutput { [CmdletBinding()] param( [Parameter(Mandatory = $false)] [object[]]$Output ) if (-not $Output) { return } # Convert output to string array if needed $lines = if ($Output -is [array]) { $Output } else { $Output -split "`n" } # Track if we're in the data section (after header line with dashes) $inDataSection = $false $headerFound = $false foreach ($line in $lines) { $trimmedLine = $line.Trim() # Skip empty lines and informational messages if ([string]::IsNullOrWhiteSpace($trimmedLine)) { continue } # Skip service status messages and download messages if ($trimmedLine -match '🟢|🔴|🕗|Successfully downloaded|Valid EPs:') { continue } # Detect header line (contains column names) if ($trimmedLine -match '^Alias\s+Device\s+Task\s+File Size\s+License\s+Model ID') { $headerFound = $true continue } # Detect separator line (all dashes) if ($trimmedLine -match '^-{20,}') { $inDataSection = $true continue } # Skip if we haven't found the header yet if (-not $headerFound) { continue } # Parse data lines - they have specific column positions based on the header # The output format is: Alias (30 chars), Device (10 chars), Task (14 chars), FileSize (12 chars), License (12 chars), ModelId (rest) if ($inDataSection -and $trimmedLine.Length -ge 40) { # Use regex to parse the columns more reliably # Match pattern: Alias, Device, Task, FileSize, License, ModelId $pattern = '^(?<alias>\S+)\s+(?<device>CPU|GPU|NPU)\s+(?<task>[\w,\s-]+?)\s+(?<filesize>[\d.]+\s*[KMGT]?B)\s+(?<license>\S+)\s+(?<modelid>.+)$' if ($trimmedLine -match $pattern) { $modelObject = [PSCustomObject]@{ PSTypeName = 'psfoundrylocal.Model' Alias = $Matches['alias'].Trim() Device = $Matches['device'].Trim() Task = $Matches['task'].Trim() FileSize = $Matches['filesize'].Trim() License = $Matches['license'].Trim() ModelId = $Matches['modelid'].Trim() } $modelObject } elseif ($trimmedLine -match '^\s+(?<device>CPU|GPU|NPU)\s+(?<task>[\w,\s-]+?)\s+(?<filesize>[\d.]+\s*[KMGT]?B)\s+(?<license>\S+)\s+(?<modelid>.+)$') { # Continuation line (no alias, starts with whitespace + device) $modelObject = [PSCustomObject]@{ PSTypeName = 'psfoundrylocal.Model' Alias = '' # Continuation of previous alias group Device = $Matches['device'].Trim() Task = $Matches['task'].Trim() FileSize = $Matches['filesize'].Trim() License = $Matches['license'].Trim() ModelId = $Matches['modelid'].Trim() } $modelObject } } } } <# .SYNOPSIS Converts the output from 'foundry model info' to a PowerShell object. .DESCRIPTION Parses the output from the foundry model info command and returns a structured object with model details. .PARAMETER Output The raw output from the foundry model info command. #> function Convert-FoundryLocalModelInfoOutput { [CmdletBinding()] param( [Parameter(Mandatory = $false)] [object[]]$Output ) if (-not $Output) { return } # Convert output to string array if needed $lines = if ($Output -is [array]) { $Output } else { $Output -split "`n" } $inDataSection = $false foreach ($line in $lines) { $trimmedLine = $line.Trim() # Skip empty lines if ([string]::IsNullOrWhiteSpace($trimmedLine)) { continue } # Detect header line if ($trimmedLine -match '^Alias\s+Device\s+Task\s+File Size\s+License\s+Model ID') { $inDataSection = $true continue } # Parse data line if ($inDataSection) { $pattern = '^(?<alias>\S+)\s+(?<device>CPU|GPU|NPU)\s+(?<task>[\w,\s-]+?)\s+(?<filesize>[\d.]+\s*[KMGT]?B)\s+(?<license>\S+)\s+(?<modelid>.+)$' if ($trimmedLine -match $pattern) { $modelObject = [PSCustomObject]@{ PSTypeName = 'psfoundrylocal.ModelInfo' Alias = $Matches['alias'].Trim() Device = $Matches['device'].Trim() Task = $Matches['task'].Trim() FileSize = $Matches['filesize'].Trim() License = $Matches['license'].Trim() ModelId = $Matches['modelid'].Trim() } return $modelObject } } } # If no structured output, return raw output return $Output -join "`n" } <# .SYNOPSIS Converts the output from 'foundry model download' to a PowerShell object. .DESCRIPTION Parses the output from the foundry model download command and returns status information. .PARAMETER Output The raw output from the foundry model download command. #> function Convert-FoundryLocalDownloadOutput { [CmdletBinding()] param( [Parameter(Mandatory = $false)] [object[]]$Output ) if (-not $Output) { return } $outputText = ($Output -join "`n").Trim() # Check for success or error conditions $success = $outputText -match 'downloaded|complete|success' -or $outputText -notmatch 'error|failed|not found' [PSCustomObject]@{ PSTypeName = 'psfoundrylocal.DownloadResult' Success = $success Message = $outputText } } <# .SYNOPSIS Converts the output from 'foundry model load' to a PowerShell object. .DESCRIPTION Parses the output from the foundry model load command and returns status information. .PARAMETER Output The raw output from the foundry model load command. #> function Convert-FoundryLocalLoadOutput { [CmdletBinding()] param( [Parameter(Mandatory = $false)] [object[]]$Output ) if (-not $Output) { return } $outputText = ($Output -join "`n").Trim() # Check for success indicators $success = $outputText -match 'loaded|ready|success' -or $outputText -notmatch 'error|failed|not found' [PSCustomObject]@{ PSTypeName = 'psfoundrylocal.LoadResult' Success = $success Message = $outputText } } <# .SYNOPSIS Converts the output from 'foundry model unload' to a PowerShell object. .DESCRIPTION Parses the output from the foundry model unload command and returns status information. .PARAMETER Output The raw output from the foundry model unload command. #> function Convert-FoundryLocalUnloadOutput { [CmdletBinding()] param( [Parameter(Mandatory = $false)] [object[]]$Output ) if (-not $Output) { return } $outputText = ($Output -join "`n").Trim() # Check for success indicators $success = $outputText -match 'unloaded|success' -or $outputText -notmatch 'error|failed|not found' [PSCustomObject]@{ PSTypeName = 'psfoundrylocal.UnloadResult' Success = $success Message = $outputText } } <# .SYNOPSIS Converts the output from 'foundry service status' to a PowerShell object. .DESCRIPTION Parses the service status output and returns a structured object with Status, Endpoint, Port, and ProcessId properties. .PARAMETER Output The raw output from the foundry service status command. #> function Convert-FoundryLocalServiceStatusOutput { [CmdletBinding()] param( [Parameter(Mandatory = $false)] [object[]]$Output ) if (-not $Output) { return } $outputText = ($Output -join "`n").Trim() $serviceStatus = [PSCustomObject]@{ PSTypeName = 'psfoundrylocal.ServiceStatus' IsRunning = $false Endpoint = $null Port = $null ProcessId = $null Message = $outputText } # Parse running status - check for running indicators (handles emoji encoding issues) # Patterns: "🟢 Service is Started on http://127.0.0.1:44549/, PID 19804!" # "service is running on http://..." # Check for "not running" first to avoid false positive from "running" substring if ($outputText -match 'not running|stopped') { $serviceStatus.IsRunning = $false } elseif ($outputText -match 'Started|is running') { $serviceStatus.IsRunning = $true # Try to parse endpoint if present if ($outputText -match 'http://([^/\s]+):(\d+)') { $serviceStatus.Endpoint = "http://$($Matches[1]):$($Matches[2])/" $serviceStatus.Port = [int]$Matches[2] } if ($outputText -match 'PID\s*(\d+)') { $serviceStatus.ProcessId = [int]$Matches[1] } } return $serviceStatus } <# .SYNOPSIS Converts the output from 'foundry service ps' to PowerShell objects. .DESCRIPTION Parses the list of loaded models and returns structured objects. .PARAMETER Output The raw output from the foundry service ps command. #> function Convert-FoundryLocalServicePsOutput { [CmdletBinding()] param( [Parameter(Mandatory = $false)] [object[]]$Output ) if (-not $Output) { return } $outputText = ($Output -join "`n").Trim() # Check if no models are loaded if ($outputText -match 'No models.*loaded|empty') { return [PSCustomObject]@{ PSTypeName = 'psfoundrylocal.LoadedModels' ModelsLoaded = 0 Models = @() Message = $outputText } } # Parse loaded models - format may vary $models = @() $lines = $Output | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } foreach ($line in $lines) { $trimmed = $line.Trim() # Skip header and informational lines if ($trimmed -match '^(Alias|Model|Name|-)' -or $trimmed -match '🟢|🔴') { continue } if ($trimmed.Length -gt 0) { $models += $trimmed } } return [PSCustomObject]@{ PSTypeName = 'psfoundrylocal.LoadedModels' ModelsLoaded = $models.Count Models = $models Message = $outputText } } <# .SYNOPSIS Converts the output from 'foundry service start' to a PowerShell object. .DESCRIPTION Parses the service start output and returns status information. .PARAMETER Output The raw output from the foundry service start command. #> function Convert-FoundryLocalServiceStartOutput { [CmdletBinding()] param( [Parameter(Mandatory = $false)] [object[]]$Output ) if (-not $Output) { return } $outputText = ($Output -join "`n").Trim() $result = [PSCustomObject]@{ PSTypeName = 'psfoundrylocal.ServiceStartResult' Success = $false Endpoint = $null Port = $null ProcessId = $null Message = $outputText } if ($outputText -match 'started|running|Started') { $result.Success = $true if ($outputText -match 'http://([^/\s]+):(\d+)') { $result.Endpoint = "http://$($Matches[1]):$($Matches[2])/" $result.Port = [int]$Matches[2] } if ($outputText -match 'PID\s*(\d+)') { $result.ProcessId = [int]$Matches[1] } } return $result } <# .SYNOPSIS Converts the output from 'foundry service stop' to a PowerShell object. .DESCRIPTION Parses the service stop output and returns status information. .PARAMETER Output The raw output from the foundry service stop command. #> function Convert-FoundryLocalServiceStopOutput { [CmdletBinding()] param( [Parameter(Mandatory = $false)] [object[]]$Output ) if (-not $Output) { return } $outputText = ($Output -join "`n").Trim() $success = $outputText -match 'stopped|Stopped' -or $outputText -notmatch 'error|failed' return [PSCustomObject]@{ PSTypeName = 'psfoundrylocal.ServiceStopResult' Success = $success Message = $outputText } } <# .SYNOPSIS Converts the output from 'foundry service restart' to a PowerShell object. .DESCRIPTION Parses the service restart output and returns status information. .PARAMETER Output The raw output from the foundry service restart command. #> function Convert-FoundryLocalServiceRestartOutput { [CmdletBinding()] param( [Parameter(Mandatory = $false)] [object[]]$Output ) if (-not $Output) { return } $outputText = ($Output -join "`n").Trim() $result = [PSCustomObject]@{ PSTypeName = 'psfoundrylocal.ServiceRestartResult' Success = $false Endpoint = $null Port = $null ProcessId = $null Message = $outputText } if ($outputText -match 'started|running|restarted|Started') { $result.Success = $true if ($outputText -match 'http://([^/\s]+):(\d+)') { $result.Endpoint = "http://$($Matches[1]):$($Matches[2])/" $result.Port = [int]$Matches[2] } if ($outputText -match 'PID\s*(\d+)') { $result.ProcessId = [int]$Matches[1] } } return $result } <# .SYNOPSIS Converts the output from 'foundry cache list' to PowerShell objects. .DESCRIPTION Parses the cache list output and returns structured objects for each cached model. .PARAMETER Output The raw output from the foundry cache list command. #> function Convert-FoundryLocalCacheListOutput { [CmdletBinding()] param( [Parameter(Mandatory = $false)] [object[]]$Output ) if (-not $Output) { return } # Convert output to string array if needed $lines = if ($Output -is [array]) { $Output } else { $Output -split "`n" } $inDataSection = $false $currentRow = $null foreach ($line in $lines) { $trimmedLine = $line.Trim() # Skip empty lines if ([string]::IsNullOrWhiteSpace($trimmedLine)) { continue } # Skip informational line if ($trimmedLine -match 'Models cached') { continue } # Detect header line (Alias / Model ID on one line) if ($trimmedLine -match '^\s*Alias\s+Model ID\s*$') { $inDataSection = $true $currentRow = $null continue } if (-not $inDataSection) { continue } # Once in data section, assemble logical rows to undo console wrapping. # A new row starts when we see an alias (optionally prefixed with an icon). # Use an explicit ASCII character class so that garbled emoji bytes are not # treated as part of the alias. if ($trimmedLine -match '^[^A-Za-z0-9_.-]*(?<alias>[A-Za-z0-9_.-]+)\b' -and $trimmedLine -notmatch 'Alias|Model ID') { # Flush previous row if present if ($currentRow) { if ($currentRow -match '^[^A-Za-z0-9_.-]*(?<ra>[A-Za-z0-9_.-]+)\s+(?<rm>.+)$' -and $currentRow -notmatch 'Cache directory') { [PSCustomObject]@{ PSTypeName = 'psfoundrylocal.CachedModel' Alias = $Matches['ra'].Trim() ModelId = $Matches['rm'].Trim() } } } $currentRow = $trimmedLine continue } # Continuation of the current row (wrapped model id line) if ($currentRow) { $currentRow = "$currentRow $trimmedLine" } } # Flush the final row if ($currentRow -and $currentRow -match '^[^A-Za-z0-9_.-]*(?<fa>[A-Za-z0-9_.-]+)\s+(?<fm>.+)$' -and $currentRow -notmatch 'Cache directory') { [PSCustomObject]@{ PSTypeName = 'psfoundrylocal.CachedModel' Alias = $Matches['fa'].Trim() ModelId = $Matches['fm'].Trim() } } } <# .SYNOPSIS Converts the output from 'foundry cache location' to a PowerShell object. .DESCRIPTION Parses the cache location output and returns the directory path. .PARAMETER Output The raw output from the foundry cache location command. #> function Convert-FoundryLocalCacheLocationOutput { [CmdletBinding()] param( [Parameter(Mandatory = $false)] [object[]]$Output ) if (-not $Output) { return } $outputText = ($Output -join "`n").Trim() $cachePath = $null # Parse: "💾 Cache directory path: C:\Users\...\cache\models" if ($outputText -match 'Cache directory path:\s*(?<path>.+)$') { $cachePath = $Matches['path'].Trim() } # Fallback: just a path elseif ($outputText -match '^[A-Za-z]:\\' -or $outputText -match '^/') { $cachePath = $outputText.Trim() } return [PSCustomObject]@{ PSTypeName = 'psfoundrylocal.CacheLocation' Path = $cachePath Exists = if ($cachePath) { Test-Path $cachePath } else { $false } } } <# .SYNOPSIS Converts the output from 'foundry cache cd' to a PowerShell object. .DESCRIPTION Parses the cache cd output and returns status information. .PARAMETER Output The raw output from the foundry cache cd command. #> function Convert-FoundryLocalCacheCdOutput { [CmdletBinding()] param( [Parameter(Mandatory = $false)] [object[]]$Output ) if (-not $Output) { return } $outputText = ($Output -join "`n").Trim() $success = $outputText -notmatch 'error|failed|not found|invalid' return [PSCustomObject]@{ PSTypeName = 'psfoundrylocal.CacheLocationChange' Success = $success Message = $outputText } } <# .SYNOPSIS Converts the output from 'foundry cache remove' to a PowerShell object. .DESCRIPTION Parses the cache remove output and returns status information. .PARAMETER Output The raw output from the foundry cache remove command. #> function Convert-FoundryLocalCacheRemoveOutput { [CmdletBinding()] param( [Parameter(Mandatory = $false)] [object[]]$Output ) if (-not $Output) { return } $outputText = ($Output -join "`n").Trim() $success = $outputText -match 'removed|deleted|success' -or $outputText -notmatch 'error|failed|not found' return [PSCustomObject]@{ PSTypeName = 'psfoundrylocal.CacheRemoveResult' Success = $success Message = $outputText } } #endregion Output Handler Functions |