Public/Invoke-MetroAIConversation.ps1

function Invoke-MetroAIConversation {
    <#
        .SYNOPSIS
            Sends a turn to an agent within a conversation using the Responses API.
        .EXAMPLE
            Invoke-MetroAIConversation -AgentId "my-agent" -ConversationId "abc123" -Input "Hello"
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [Alias('AgentName')]
        [string]$AgentId,

        [Parameter(Mandatory = $true)]
        [string]$ConversationId,

        # Avoid conflict with PowerShell's automatic $input variable
        [Parameter(Mandatory = $true)]
        [Alias('UserMessage','Message','Prompt')]
        [object]$UserInput,

        [switch]$PassThru,

        [switch]$AutoApprove
    )
    try {
        if ($null -eq $UserInput) {
            throw "Input is required and cannot be empty. The Responses API needs either 'input' or 'prompt'."
        }

        # Normalize input. The Foundry Agents Responses API accepts either:
        # - a simple string (most common), or
        # - an array/object of typed input items (e.g., input_text/input_audio).
        # Avoid wrapping into message-role/content arrays, which the service rejects.
        $inputPayload = $null

        if ($UserInput -is [string]) {
            if ([string]::IsNullOrWhiteSpace($UserInput)) {
                throw "Input is required and cannot be empty. The Responses API needs either 'input' or 'prompt'."
            }
            $inputPayload = $UserInput
        }
        elseif ($UserInput -is [System.Collections.IEnumerable] -and -not ($UserInput -is [string])) {
            $inputPayload = @($UserInput)
            if ($inputPayload.Count -eq 0) {
                throw "Input is required and cannot be empty. The Responses API needs either 'input' or 'prompt'."
            }
        }
        elseif ($UserInput.PSObject.Properties.Name -contains 'type') {
            # Single typed input object (e.g., @{ type='input_text'; text='hi' })
            $inputPayload = $UserInput
        }
        else {
            # Fallback to a single string representation.
            $inputPayload = ($UserInput | Out-String).Trim()
            if ([string]::IsNullOrWhiteSpace($inputPayload)) {
                throw "Input is required and cannot be empty. The Responses API needs either 'input' or 'prompt'."
            }
        }

        $currentInput = $inputPayload
        $assistantTextParts = @()
        $lastResponse = $null
        $keepGoing = $true

        while ($keepGoing) {
            $body = @{
                agent        = @{ type = 'agent_reference'; name = $AgentId }
                input        = $currentInput
            }

            if ($lastResponse -and $lastResponse.id) {
                $body['previous_response_id'] = $lastResponse.id
            }
            else {
                $body['conversation'] = $ConversationId
            }

            if ($PSBoundParameters['Verbose'] -or $VerbosePreference -ne 'SilentlyContinue') {
                try {
                    $jsonBody = $body | ConvertTo-Json -Depth 10 -Compress
                    Write-Verbose "Request Body: $jsonBody"
                } catch {
                    Write-Verbose "Request Body: (Failed to serialize for logging)"
                }
            }

            $response = Invoke-MetroAIApiCall -Service 'openai/responses' -Operation 'responses' -Method Post -ContentType "application/json" -Body $body
            $lastResponse = $response

            if (-not $response) {
                throw "No response returned from the service."
            }

            $approvalRequests = @()

            if ($response.PSObject.Properties.Name -contains "output") {
                foreach ($outputItem in $response.output) {
                    if ($outputItem.type -eq "message" -and $outputItem.role -eq "assistant") {
                        foreach ($contentPart in $outputItem.content) {
                            if ($contentPart.type -eq "output_text" -and $contentPart.text) {
                                $textValue = $null
                                if ($contentPart.text -is [string]) {
                                    $textValue = $contentPart.text
                                }
                                elseif ($contentPart.text.PSObject.Properties.Name -contains "value") {
                                    $textValue = $contentPart.text.value
                                }
                                if ($textValue) {
                                    $assistantTextParts += $textValue
                                }
                            }
                        }
                    }
                    elseif ($outputItem.type -eq "mcp_approval_request") {
                        $approvalRequests += $outputItem
                    }
                }
            }

            if ($approvalRequests.Count -gt 0) {
                # Use a generic list to ensure ConvertTo-Json serializes as an array, even with a single item.
                $nextInputItems = [System.Collections.Generic.List[object]]::new()
                foreach ($req in $approvalRequests) {
                    Write-Host "MCP Approval Request:" -ForegroundColor Yellow
                    Write-Host " Server: $($req.server_label)" -ForegroundColor Cyan
                    Write-Host " Tool: $($req.name)" -ForegroundColor Cyan
                    Write-Host " Args: $($req.arguments)" -ForegroundColor Cyan
                    Write-Verbose " Request ID: $($req.id)"
                    
                    $isApproved = $false
                    if ($AutoApprove) {
                        Write-Host "Auto-approving action due to -AutoApprove switch." -ForegroundColor Green
                        $isApproved = $true
                    }
                    else {
                        $userChoice = Read-Host "Do you approve this action? (y/n)"
                        $isApproved = $userChoice -eq 'y'
                    }
                    
                    $nextInputItems.Add(@{
                        type = 'mcp_approval_response'
                        approval_request_id = $req.id
                        approve = $isApproved
                    })
                }
                $currentInput = $nextInputItems
            }
            else {
                $keepGoing = $false
            }
        }

        $assistantText = ($assistantTextParts -join "`n")

        [PSCustomObject]@{
            AssistantText  = $assistantText
            ResponseId     = $lastResponse.id
            ConversationId = $ConversationId
            RawResponse    = $(if ($PassThru) { $lastResponse } else { $null })
        }
    }
    catch {
        Write-Error "Invoke-MetroAIConversation error: $_"
    }
}