Metro.AI.psm1

#region Helper Functions

function Get-MetroAuthHeader {
    <#
    .SYNOPSIS
        Returns a header hashtable with an authorization token for the specified API type.
    .PARAMETER ApiType
        The API type: Agent or Assistant.
    .OUTPUTS
        A hashtable suitable for use as HTTP headers.
    .EXAMPLE
        Get-MetroAuthHeader -ApiType Agent
    #>

    param (
        [Parameter(Mandatory=$true)]
        [ValidateSet('Agent', 'Assistant')]
        [string]$ApiType
    )
    try {
        $resourceUrl = ($ApiType -eq 'Agent') ? "https://ml.azure.com/" : "https://cognitiveservices.azure.com"

        $token = (Get-AzAccessToken -ResourceUrl $resourceUrl -AsSecureString).Token | ConvertFrom-SecureString -AsPlainText

        if (-not $token) { throw "Token retrieval failed." }

        return @{ Authorization = "Bearer $token" }
    }
    catch {
        Write-Error "Get-MetroAuthHeader error for '$ApiType': $_"
    }
}


function Get-MetroBaseUri {
    <#
    .SYNOPSIS
        Constructs the base URI for a given service.
    .DESCRIPTION
        Builds the full base URI by prepending "openai/" only when the API type is Assistant and the UseOpenPrefix switch is provided.
        For example, for an Assistant API:
            https://aoai-policyassistant.openai.azure.com/openai/files
        And for an Agent API:
            https://swedencentral.api.azureml.ms/agents/v1.0/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.MachineLearningServices/workspaces/{workspace}/files
    .PARAMETER Endpoint
        The base URL of the API.
    .PARAMETER ApiType
        The API type: Agent or Assistant.
    .PARAMETER Service
        The service segment (e.g. "assistants", "files", "threads").
    .PARAMETER UseOpenPrefix
        When specified and ApiType is Assistant, "openai/" is prepended.
    .OUTPUTS
        A string representing the full base URI.
    .EXAMPLE
        Get-MetroBaseUri -Endpoint "https://aoai-policyassistant.openai.azure.com" -ApiType Assistant -Service files -UseOpenPrefix
    #>

    param (
        [Parameter(Mandatory=$true)]
        [string]$Endpoint,
        [Parameter(Mandatory=$true)]
        [ValidateSet('Agent', 'Assistant')]
        [string]$ApiType,
        [Parameter(Mandatory=$true)]
        [string]$Service,
        [switch]$UseOpenPrefix
    )

    $prefix = ($ApiType -eq 'Assistant' -and $UseOpenPrefix) ? "openai/" : ""

    return "$Endpoint/$prefix$Service"
}


function Get-MetroApiVersion {
    <#
    .SYNOPSIS
        Returns the API version for a given operation.
    .DESCRIPTION
        Retrieves the API version string for the specified operation.
    .PARAMETER Operation
        The operation name (upload, create, get, thread, threadStatus, messages, openapi).
    .PARAMETER ApiType
        The API type: Agent or Assistant.
    .OUTPUTS
        A string with the API version.
    .EXAMPLE
        Get-MetroApiVersion -Operation create -ApiType Agent
    #>

    param (
        [Parameter(Mandatory=$true)]
        [string]$Operation,
        [Parameter(Mandatory=$true)]
        [ValidateSet('Agent', 'Assistant')]
        [string]$ApiType
    )
    switch ($Operation) {
        'upload' { return '2024-05-01-preview' }
        'create' { return '2024-07-01-preview' }
        'get' { return '2024-02-15-preview' }
        'thread' { return '2024-03-01-preview' }
        'threadStatus' { return '2024-05-01-preview' }
        'messages' { return '2024-05-01-preview' }
        'openapi' { return '2024-12-01-preview' }
        default { return '2024-05-01-preview' }
    }
}


function Get-MetroUri {
    <#
    .SYNOPSIS
        Builds the complete URI for an API call.
    .DESCRIPTION
        Constructs the final URI by calling Get-MetroBaseUri to obtain the base URI and Get-MetroApiVersion for the version.
        An optional additional path may be appended.
    .PARAMETER Endpoint
        The base URL of the API.
    .PARAMETER ApiType
        The API type: Agent or Assistant.
    .PARAMETER Service
        The service segment (e.g. "assistants", "files", "threads").
    .PARAMETER Operation
        The operation name (used to get the API version).
    .PARAMETER Path
        Optional. Additional path to append to the base URI.
    .OUTPUTS
        The full URI string.
    .EXAMPLE
        Get-MetroUri -Endpoint "https://swedencentral.api.azureml.ms/agents/v1.0/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.MachineLearningServices/workspaces/{workspace}" `
                     -ApiType Agent -Service files -Operation upload
    #>

    param (
        [Parameter(Mandatory=$true)]
        [string]$Endpoint,
        [Parameter(Mandatory=$true)]
        [ValidateSet('Agent', 'Assistant')]
        [string]$ApiType,
        [Parameter(Mandatory=$true)]
        [string]$Service,
        [Parameter(Mandatory=$true)]
        [string]$Operation,
        [string]$Path
    )
    $params = @{
        Endpoint = $Endpoint
        ApiType = $ApiType
        Service = $Service
    }
    if ($ApiType -eq 'Assistant') {
        $params.UseOpenPrefix = $true
    }

    $baseUri = Get-MetroBaseUri @params
    if ($Path) { $baseUri = "$baseUri/$Path" }
    $version = Get-MetroApiVersion -Operation $Operation -ApiType $ApiType
    $uri = '{0}?api-version={1}' -f $baseUri, $version
    Write-Verbose $uri
    return $uri
}

#endregion


#region File Upload & Output Files

function Invoke-MetroAIUploadFile {
    <#
    .SYNOPSIS
        Uploads a file to the API endpoint.
    .DESCRIPTION
        Reads a local file and uploads it via a multipart/form-data request to an agent or assistant endpoint.
    .PARAMETER FilePath
        The local path to the file.
    .PARAMETER Endpoint
        The base API URL.
        For Agent, for example:
          https://swedencentral.api.azureml.ms/agents/v1.0/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.MachineLearningServices/workspaces/{workspace}
    .PARAMETER ApiType
        Specifies whether the call is for an Agent or an Assistant.
    .PARAMETER Purpose
        The purpose of the file upload (default is "assistants").
    .EXAMPLE
        Invoke-MetroAIUploadFile -FilePath ".\doc.txt" -Endpoint "https://swedencentral.api.azureml.ms/agents/v1.0/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.MachineLearningServices/workspaces/{workspace}" -ApiType Agent
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]$FilePath,
        [Parameter(Mandatory=$true)]
        [string]$Endpoint,
        [Parameter(Mandatory=$true)]
        [ValidateSet('Agent', 'Assistant')]
        [string]$ApiType,
        [string]$Purpose = "assistants"
    )
    try {
        $authHeader = Get-MetroAuthHeader -ApiType $ApiType
        $uploadUri = Get-MetroUri -Endpoint $Endpoint -ApiType $ApiType -Service 'files' -Operation 'upload'
        $fileItem = Get-Item -Path $FilePath -ErrorAction Stop
        $body = @{ purpose = $Purpose; file = $fileItem }
        return Invoke-RestMethod -Uri $uploadUri -Method Post -Headers $authHeader -ContentType "multipart/form-data" -Form $body
    }
    catch {
        Write-Error "Invoke-MetroAIUploadFile error: $_"
    }
}


function Get-MetroAIOutputFiles {
    <#
    .SYNOPSIS
        Retrieves output files for an assistant.
    .DESCRIPTION
        Downloads output files (with purpose "assistants_output") from an assistant endpoint and optionally saves them locally.
    .PARAMETER Endpoint
        The base API URL.
        For Assistant, for example:
          https://aoai-policyassistant.openai.azure.com
    .PARAMETER ApiType
        Specifies whether to target an Agent or an Assistant.
    .PARAMETER FileId
        Optional. A specific file ID.
    .PARAMETER LocalFilePath
        Optional. A path to save the file content.
    .EXAMPLE
        Get-MetroAIOutputFiles -Endpoint "https://aoai-policyassistant.openai.azure.com" -ApiType Assistant -FileId "file123"
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]$Endpoint,
        [Parameter(Mandatory=$true)]
        [ValidateSet('Agent', 'Assistant')]
        [string]$ApiType,
        [string]$FileId,
        [string]$LocalFilePath
    )
    try {
        $authHeader = Get-MetroAuthHeader -ApiType $ApiType
        $uri = Get-MetroUri -Endpoint $Endpoint -ApiType $ApiType -Service 'files' -Operation 'upload'
        $files = Invoke-RestMethod -Uri $uri -Headers $authHeader -Method Get
        if (![string]::IsNullOrWhiteSpace($FileId)) {
            $item = $files.data | Where-Object { $_.id -eq $FileId -and $_.purpose -eq "assistants_output" }
            if ($item) {
                $downloadUri = Get-MetroUri -Endpoint $Endpoint -ApiType $ApiType -Service 'files' -Operation 'upload' -Path ("{0}/content" -f $FileId)
                $content = Invoke-RestMethod -Uri $downloadUri -Headers $authHeader -Method Get
                if ($LocalFilePath) {
                    $content | Out-File -FilePath $LocalFilePath -Force -Verbose
                }
                else {
                    return $content
                }
            }
            else {
                Write-Error "File $FileId not found or wrong purpose."
            }
        }
        else {
            $outputFiles = $files.data | Where-Object { $_.purpose -eq "assistants_output" }
            if ($outputFiles.Count -gt 0) {
                return $outputFiles
            }
            else {
                Write-Output "No output files found."
            }
        }
    }
    catch {
        Write-Error "Get-MetroAIOutputFiles error: $_"
    }
}


function Remove-MetroAIFiles {
    <#
    .SYNOPSIS
        Deletes files from an endpoint.
    .DESCRIPTION
        Removes the specified file (or all files if FileId is not provided) from an agent or assistant endpoint.
    .PARAMETER Endpoint
        The base API URL.
    .PARAMETER ApiType
        Specifies whether to target an Agent or an Assistant.
    .PARAMETER FileId
        Optional. The specific file ID to delete.
    .EXAMPLE
        Remove-MetroAIFiles -Endpoint "https://swedencentral.api.azureml.ms/agents/v1.0/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.MachineLearningServices/workspaces/{workspace}" -ApiType Agent -FileId "file123"
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]$Endpoint,
        [Parameter(Mandatory=$true)]
        [ValidateSet('Agent', 'Assistant')]
        [string]$ApiType,
        [string]$FileId
    )
    try {
        $authHeader = Get-MetroAuthHeader -ApiType $ApiType
        $uri = Get-MetroUri -Endpoint $Endpoint -ApiType $ApiType -Service 'files' -Operation 'upload'
        $files = Invoke-RestMethod -Uri $uri -Headers $authHeader -Method Get
        if ($FileId) {
            $item = $files.data | Where-Object { $_.id -eq $FileId }
            if ($item) {
                $deleteUri = "{0}/{1}" -f $uri, $FileId
                Invoke-RestMethod -Uri $deleteUri -Headers $authHeader -Method Delete
                Write-Output "File $FileId deleted."
            }
            else {
                Write-Error "File $FileId not found."
            }
        }
        else {
            foreach ($file in $files.data) {
                $deleteUri = "{0}/{1}" -f $uri, $file.id
                try {
                    Invoke-RestMethod -Uri $deleteUri -Headers $authHeader -Method Delete
                }
                catch {
                    Write-Error "Error deleting file $($file.id): $_"
                }
            }
        }
    }
    catch {
        Write-Error "Remove-MetroAIFiles error: $_"
    }
}

#endregion


#region Resource Management

function New-MetroAIResource {
    [Alias("New-MetroAIAgent")]
    [Alias("New-MetroAIAssistant")]
    <#
    .SYNOPSIS
        Creates a new agent or assistant.
    .DESCRIPTION
        Uses an optional meta prompt file, model name, and (for assistants) file IDs to create a new agent or assistant instance.
    .PARAMETER MetaPromptFile
        Optional file path for the meta prompt.
    .PARAMETER Model
        The model to use.
    .PARAMETER Endpoint
        The base API URL.
        For Agent, for example:
          https://swedencentral.api.azureml.ms/agents/v1.0/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.MachineLearningServices/workspaces/{workspace}
        For Assistant, for example:
          https://aoai-policyassistant.openai.azure.com
    .PARAMETER ApiType
        Agent or Assistant.
    .PARAMETER FileIds
        (For Assistant only) An array of file IDs.
    .PARAMETER ResourceName
        Optional name for the agent or assistant; if omitted, a timestamp-based name is generated.
    .EXAMPLE
        New-MetroAIResource -MetaPromptFile ".\prompt.txt" -Model "gpt-4" -Endpoint "https://swedencentral.api.azureml.ms/agents/v1.0/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.MachineLearningServices/workspaces/{workspace}" -ApiType Agent
    #>

    [CmdletBinding()]
    param (
        [string]$MetaPromptFile = "",
        [Parameter(Mandatory=$true)]
        [string]$Model,
        [Parameter(Mandatory=$true)]
        [string]$Endpoint,
        [Parameter(Mandatory=$true)]
        [ValidateSet('Agent', 'Assistant')]
        [string]$ApiType,
        [string[]]$FileIds,
        [Parameter(Mandatory=$false)]
        [string]$ResourceName = ""
    )
    try {
        $authHeader = Get-MetroAuthHeader -ApiType $ApiType
        $metaPrompt = if ($MetaPromptFile) { (Get-Content -Path $MetaPromptFile -ErrorAction Stop) -join "`n" } else { "" }
        if (-not $ResourceName) {
            $ResourceName = (Get-Date -Format "dd-HH-mm-ss") + "-resource"
        }
        $body = @{
            instructions = $metaPrompt
            name         = $ResourceName
            tools        = @(
                @{ type = "file_search" },
                @{ type = "code_interpreter" }
            )
            model        = $Model
        }
        if ($ApiType -eq 'Assistant' -and $FileIds) {
            $body.file_ids = $FileIds
        }
        $uri = Get-MetroUri -Endpoint $Endpoint -ApiType $ApiType -Service 'assistants' -Operation 'create'
        $bodyJson = $body | ConvertTo-Json -Depth 100
        return Invoke-RestMethod -Uri $uri -Method Post -Headers $authHeader -ContentType "application/json" -Body $bodyJson
    }
    catch {
        Write-Error "New-MetroAIResource error: $_"
    }
}


function Get-MetroAIResource {
    [Alias("Get-MetroAIAgent")]
    [Alias("Get-MetroAIAssistant")]
    <#
    .SYNOPSIS
        Retrieves details for an agent or assistant.
    .DESCRIPTION
        Returns details about a specified agent or assistant, or all if no identifier is provided.
    .PARAMETER ResourceId
        Optional. The agent or assistant ID.
    .PARAMETER Endpoint
        The base API URL.
    .PARAMETER ApiType
        Agent or Assistant.
    .EXAMPLE
        Get-MetroAIResource -ResourceId "res123" -Endpoint "https://aoai-policyassistant.openai.azure.com" -ApiType Assistant
    #>

    [CmdletBinding()]
    param (
        [string]$ResourceId,
        [Parameter(Mandatory=$true)]
        [string]$Endpoint,
        [Parameter(Mandatory=$true)]
        [ValidateSet('Agent', 'Assistant')]
        [string]$ApiType
    )
    try {
        $uri = Get-MetroUri -Endpoint $Endpoint -ApiType $ApiType -Service 'assistants' -Operation 'get' -Path $ResourceId
        $authHeader = Get-MetroAuthHeader -ApiType $ApiType
        return Invoke-RestMethod -Uri $uri -Method Get -Headers $authHeader
    }
    catch {
        Write-Error "Get-MetroAIResource error: $_"
    }
}


function Remove-MetroAIResource {
    [Alias("Remove-MetroAIAgent")]
    [Alias("Remove-MetroAIAssistant")]
    <#
    .SYNOPSIS
        Deletes all agents or assistants.
    .DESCRIPTION
        Retrieves all agents or assistants from the endpoint and deletes each one.
    .PARAMETER Endpoint
        The base API URL.
    .PARAMETER ApiType
        Agent or Assistant.
    .EXAMPLE
        Remove-MetroAIResource -Endpoint "https://swedencentral.api.azureml.ms/agents/v1.0/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.MachineLearningServices/workspaces/{workspace}" -ApiType Agent
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]$Endpoint,
        [Parameter(Mandatory=$true)]
        [ValidateSet('Agent', 'Assistant')]
        [string]$ApiType
    )
    try {
        $uri = Get-MetroUri -Endpoint $Endpoint -ApiType $ApiType -Service 'assistants' -Operation 'create'
        $authHeader = Get-MetroAuthHeader -ApiType $ApiType
        $resources = Invoke-RestMethod -Uri $uri -Headers $authHeader -Method Get
        foreach ($res in $resources.data) {
            $deleteUri = Get-MetroUri -Endpoint $Endpoint -ApiType $ApiType -Service 'assistants' -Operation 'create' -Path $res.id
            Invoke-RestMethod -Uri $deleteUri -Headers $authHeader -Method Delete
        }
    }
    catch {
        Write-Error "Remove-MetroAIResource error: $_"
    }
}

#endregion


#region Function Registration

function New-MetroAIFunction {
    <#
    .SYNOPSIS
        Registers a custom function for an agent or assistant.
    .DESCRIPTION
        Adds a new tool (custom function) definition to an existing agent or assistant.
    .PARAMETER Name
        The name of the function.
    .PARAMETER Description
        A description of the function.
    .PARAMETER RequiredPropertyName
        The required parameter name.
    .PARAMETER PropertyDescription
        A description for the required parameter.
    .PARAMETER Endpoint
        The base API URL.
    .PARAMETER ResourceId
        The ID of the target agent or assistant.
    .PARAMETER Instructions
        The instructions for the function.
    .PARAMETER ApiType
        Agent or Assistant.
    .EXAMPLE
        New-MetroAIFunction -Name "MyFunc" -Description "Does something" -RequiredPropertyName "input" -PropertyDescription "An input value" `
            -Endpoint "https://swedencentral.api.azureml.ms/agents/v1.0/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.MachineLearningServices/workspaces/{workspace}" `
            -ResourceId "res123" -Instructions "Run this" -ApiType Agent
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]$Name,
        [Parameter(Mandatory=$true)]
        [string]$Description,
        [Parameter(Mandatory=$true)]
        [string]$RequiredPropertyName,
        [Parameter(Mandatory=$true)]
        [string]$PropertyDescription,
        [Parameter(Mandatory=$true)]
        [string]$Endpoint,
        [Parameter(Mandatory=$true)]
        [string]$ResourceId,
        [Parameter(Mandatory=$true)]
        [string]$Instructions,
        [Parameter(Mandatory=$true)]
        [ValidateSet('Agent','Assistant')]
        [string]$ApiType
    )
    try {
        $authHeader = Get-MetroAuthHeader -ApiType $ApiType
        $resource = Get-MetroAIResource -ResourceId $ResourceId -Endpoint $Endpoint -ApiType $ApiType
        $model = $resource.model
        $reqProps = @{
            $RequiredPropertyName = @{
                type        = "string"
                description = $PropertyDescription
            }
        }
        $body = @{
            instructions = $Instructions
            tools        = @(
                @{
                    type     = "function"
                    function = @{
                        name        = $Name
                        description = $Description
                        parameters  = @{
                            type       = "object"
                            properties = $reqProps
                            required   = @($RequiredPropertyName)
                        }
                    }
                }
            )
            id    = $ResourceId
            model = $model
        } | ConvertTo-Json -Depth 100
        $uri = Get-MetroUri -Endpoint $Endpoint -ApiType $ApiType -Service 'assistants' -Operation 'get'
        return Invoke-RestMethod -Uri $uri -Method Post -Headers $authHeader -ContentType "application/json" -Body $body
    }
    catch {
        Write-Error "New-MetroAIFunction error: $_"
    }
}

#endregion


#region Threads and Messaging

function New-MetroAIThread {
    <#
    .SYNOPSIS
        Creates a new thread.
    .DESCRIPTION
        Initiates a new thread for an agent or assistant.
    .PARAMETER Endpoint
        The base API URL.
    .PARAMETER ApiType
        Agent or Assistant.
    .EXAMPLE
        New-MetroAIThread -Endpoint "https://swedencentral.api.azureml.ms/agents/v1.0/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.MachineLearningServices/workspaces/{workspace}" `
            -ApiType Agent
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]$Endpoint,
        [Parameter(Mandatory=$true)]
        [ValidateSet('Agent','Assistant')]
        [string]$ApiType
    )
    try {
        $authHeader = Get-MetroAuthHeader -ApiType $ApiType
        $uri = Get-MetroUri -Endpoint $Endpoint -ApiType $ApiType -Service 'threads' -Operation 'thread'
        return Invoke-RestMethod -Uri $uri -Method Post -Headers $authHeader -ContentType "application/json"
    }
    catch {
        Write-Error "New-MetroAIThread error: $_"
    }
}


function Get-MetroAIThread {
    <#
    .SYNOPSIS
        Retrieves thread details.
    .DESCRIPTION
        Returns details of a specified thread for an agent or assistant.
    .PARAMETER Endpoint
        The base API URL.
    .PARAMETER ThreadID
        Optional. The thread ID.
    .PARAMETER ApiType
        Agent or Assistant.
    .EXAMPLE
        Get-MetroAIThread -Endpoint "https://aoai-policyassistant.openai.azure.com" -ThreadID "thread123" -ApiType Assistant
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]$Endpoint,
        [string]$ThreadID,
        [Parameter(Mandatory=$true)]
        [ValidateSet('Agent','Assistant')]
        [string]$ApiType
    )
    try {
        $uri = Get-MetroUri -Endpoint $Endpoint -ApiType $ApiType -Service 'threads' -Operation 'thread' -Path $ThreadID
        $authHeader = Get-MetroAuthHeader -ApiType $ApiType
        return Invoke-RestMethod -Uri $uri -Method Get -Headers $authHeader
    }
    catch {
        Write-Error "Get-MetroAIThread error: $_"
    }
}


function Invoke-MetroAIMessage {
    <#
    .SYNOPSIS
        Sends a message to a thread.
    .DESCRIPTION
        Uses the approved verb "Invoke" to send a message payload to the specified thread for an agent or assistant.
    .PARAMETER ThreadID
        The thread ID.
    .PARAMETER Message
        The message content.
    .PARAMETER Endpoint
        The base API URL.
    .PARAMETER ApiType
        Agent or Assistant.
    .EXAMPLE
        Invoke-MetroAIMessage -ThreadID "thread123" -Message "Hello" -Endpoint "https://swedencentral.api.azureml.ms/agents/v1.0/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.MachineLearningServices/workspaces/{workspace}" -ApiType Agent
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]$ThreadID,
        [Parameter(Mandatory=$true)]
        [string]$Message,
        [Parameter(Mandatory=$true)]
        [string]$Endpoint,
        [Parameter(Mandatory=$true)]
        [ValidateSet('Agent','Assistant')]
        [string]$ApiType
    )
    try {
        $authHeader = Get-MetroAuthHeader -ApiType $ApiType
        $body = @(
            @{ role = "user"; content = $Message }
        ) | ConvertTo-Json -Depth 100
        $uri = Get-MetroUri -Endpoint $Endpoint -ApiType $ApiType -Service 'threads' -Operation 'thread' -Path ("{0}/messages" -f $ThreadID)
        return Invoke-RestMethod -Uri $uri -Method Post -Headers $authHeader -ContentType "application/json" -Body $body
    }
    catch {
        Write-Error "Invoke-MetroAIMessage error: $_"
    }
}


function Start-MetroAIThreadRun {
    <#
    .SYNOPSIS
        Initiates a run on a thread.
    .DESCRIPTION
        Starts a run on the specified thread for an agent or assistant and waits for completion unless Async is specified.
    .PARAMETER ResourceId
        The ID of the agent or assistant.
    .PARAMETER ThreadID
        The thread ID.
    .PARAMETER Endpoint
        The base API URL.
    .PARAMETER ApiType
        Agent or Assistant.
    .PARAMETER Async
        Switch to run asynchronously.
    .EXAMPLE
        Start-MetroAIThreadRun -ResourceId "res123" -ThreadID "thread123" -Endpoint "https://aoai-policyassistant.openai.azure.com" -ApiType Assistant
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]$ResourceId,
        [Parameter(Mandatory=$true)]
        [string]$ThreadID,
        [Parameter(Mandatory=$true)]
        [string]$Endpoint,
        [Parameter(Mandatory=$true)]
        [ValidateSet('Agent','Assistant')]
        [string]$ApiType,
        [switch]$Async
    )
    try {
        $authHeader = Get-MetroAuthHeader -ApiType $ApiType
        $body = @{ assistant_id = $ResourceId } | ConvertTo-Json -Depth 100
        $uri = Get-MetroUri -Endpoint $Endpoint -ApiType $ApiType -Service 'threads' -Operation 'threadStatus' -Path ("{0}/runs" -f $ThreadID)
        $runResponse = Invoke-RestMethod -Uri $uri -Method Post -Headers $authHeader -ContentType "application/json" -Body $body
        if (-not $Async) {
            $i = 0
            do {
                Start-Sleep -Seconds 10
                $statusUri = Get-MetroUri -Endpoint $Endpoint -ApiType $ApiType -Service 'threads' -Operation 'threadStatus' -Path ("{0}/runs/{1}" -f $ThreadID, $runResponse.id)
                $runResult = Invoke-RestMethod -Uri $statusUri -Headers $authHeader
                $i++
            } while ($runResult.status -ne "completed" -and $i -lt 100)
            if ($runResult.status -eq "completed") {
                $messagesUri = Get-MetroUri -Endpoint $Endpoint -ApiType $ApiType -Service 'threads' -Operation 'messages' -Path $ThreadID
                $result = Invoke-RestMethod -Uri $messagesUri -Headers $authHeader
                return $result.data | ForEach-Object { $_.content.text }
            }
            else {
                Write-Error "Run did not complete in time."
            }
        }
        else {
            Write-Output "Run started asynchronously. Use Get-MetroAIThreadStatus to check."
        }
        return $runResponse
    }
    catch {
        Write-Error "Start-MetroAIThreadRun error: $_"
    }
}


function Get-MetroAIThreadStatus {
    <#
    .SYNOPSIS
        Retrieves the status of a thread run.
    .DESCRIPTION
        Returns the status details of a run for the specified thread in an agent or assistant.
    .PARAMETER ThreadID
        The thread ID.
    .PARAMETER RunID
        The run ID.
    .PARAMETER Endpoint
        The base API URL.
    .PARAMETER ApiType
        Agent or Assistant.
    .EXAMPLE
        Get-MetroAIThreadStatus -ThreadID "thread123" -RunID "run456" -Endpoint "https://swedencentral.api.azureml.ms/agents/v1.0/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.MachineLearningServices/workspaces/{workspace}" -ApiType Agent
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]$ThreadID,
        [Parameter(Mandatory=$true)]
        [string]$RunID,
        [Parameter(Mandatory=$true)]
        [string]$Endpoint,
        [Parameter(Mandatory=$true)]
        [ValidateSet('Agent','Assistant')]
        [string]$ApiType
    )
    try {
        $uri = Get-MetroUri -Endpoint $Endpoint -ApiType $ApiType -Service 'threads' -Operation 'threadStatus' -Path ("{0}/runs/{1}" -f $ThreadID, $RunID)
        $authHeader = Get-MetroAuthHeader -ApiType $ApiType
        return Invoke-RestMethod -Uri $uri -Method Get -Headers $authHeader
    }
    catch {
        Write-Error "Get-MetroAIThreadStatus error: $_"
    }
}


function Get-MetroAIMessages {
    <#
    .SYNOPSIS
        Retrieves messages from a thread.
    .DESCRIPTION
        Returns the messages from the specified thread for an agent or assistant.
    .PARAMETER ThreadID
        The thread ID.
    .PARAMETER Endpoint
        The base API URL.
    .PARAMETER ApiType
        Agent or Assistant.
    .EXAMPLE
        Get-MetroAIMessages -ThreadID "thread123" -Endpoint "https://aoai-policyassistant.openai.azure.com" -ApiType Assistant
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]$ThreadID,
        [Parameter(Mandatory=$true)]
        [string]$Endpoint,
        [Parameter(Mandatory=$true)]
        [ValidateSet('Agent','Assistant')]
        [string]$ApiType
    )
    try {
        $uri = Get-MetroUri -Endpoint $Endpoint -ApiType $ApiType -Service 'threads' -Operation 'messages' -Path $ThreadID
        $authHeader = Get-MetroAuthHeader -ApiType $ApiType
        return Invoke-RestMethod -Uri $uri -Headers $authHeader
    }
    catch {
        Write-Error "Get-MetroAIMessages error: $_"
    }
}


function Start-MetroAIThreadWithMessages {
    <#
    .SYNOPSIS
        Creates a new thread with an initial message.
    .DESCRIPTION
        Initiates a new thread for an agent or assistant and sends an initial message.
    .PARAMETER ResourceId
        The ID of the agent or assistant.
    .PARAMETER Endpoint
        The base API URL.
    .PARAMETER MessageContent
        The initial message.
    .PARAMETER ApiType
        Agent or Assistant.
    .PARAMETER Async
        Optional. Run asynchronously.
    .EXAMPLE
        Start-MetroAIThreadWithMessages -ResourceId "res123" -Endpoint "https://swedencentral.api.azureml.ms/agents/v1.0/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.MachineLearningServices/workspaces/{workspace}" -MessageContent "Hello" -ApiType Agent
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]$ResourceId,
        [Parameter(Mandatory=$true)]
        [string]$Endpoint,
        [Parameter(Mandatory=$true)]
        [string]$MessageContent,
        [Parameter(Mandatory=$true)]
        [ValidateSet('Agent','Assistant')]
        [string]$ApiType,
        [switch]$Async
    )
    try {
        $authHeader = Get-MetroAuthHeader -ApiType $ApiType
        $body = @{
            assistant_id = $ResourceId;
            thread      = @{ messages = @(@{ role = "user"; content = $MessageContent }) }
        } | ConvertTo-Json -Depth 100
        $uri = Get-MetroUri -Endpoint $Endpoint -ApiType $ApiType -Service 'threads' -Operation 'thread' -Path "runs"
        $response = Invoke-RestMethod -Uri $uri -Method Post -Headers $authHeader -ContentType "application/json" -Body $body
        if (-not $Async) {
            $i = 0
            do {
                Start-Sleep -Seconds 10
                $statusUri = Get-MetroUri -Endpoint $Endpoint -ApiType $ApiType -Service 'threads' -Operation 'threadStatus' -Path ("{0}/runs/{1}" -f $response.thread_id, $response.id)
                $runResult = Invoke-RestMethod -Uri $statusUri -Headers $authHeader
                $i++
            } while ($runResult.status -ne "completed" -and $i -lt 100)
            if ($runResult.status -eq "completed") {
                $messagesUri = Get-MetroUri -Endpoint $Endpoint -ApiType $ApiType -Service 'threads' -Operation 'messages' -Path $response.thread_id
                $result = Invoke-RestMethod -Uri $messagesUri -Headers $authHeader
                return $result.data | ForEach-Object { $_.content.text }
            }
            else {
                Write-Error "Thread run did not complete in time."
            }
        }
        else {
            Write-Output "Run started asynchronously. Use Get-MetroAIThreadStatus to check."
        }
        return @{ ThreadID = $response.thread_id; RunID = $response.id }
    }
    catch {
        Write-Error "Start-MetroAIThreadWithMessages error: $_"
    }
}

#endregion


#region OpenAPI Definition (Agent Only)

function Add-MetroAIAgentOpenAPIDefinition {
    <#
    .SYNOPSIS
        Adds an OpenAPI definition to an agent.
    .DESCRIPTION
        Reads an OpenAPI JSON file and adds it as a tool to the specified agent.
        This function only supports the Agent API type.
    .PARAMETER AgentId
        The agent ID.
    .PARAMETER Endpoint
        The base API URL.
        For Agent, for example:
            https://swedencentral.api.azureml.ms/agents/v1.0/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.MachineLearningServices/workspaces/{workspace}
    .PARAMETER DefinitionFile
        The path to the OpenAPI JSON file.
    .PARAMETER Name
        Optional name for the OpenAPI definition.
    .PARAMETER Description
        Optional description for the OpenAPI definition.
    .PARAMETER ApiType
        Must be Agent.
    .EXAMPLE
        Add-MetroAIAgentOpenAPIDefinition -AgentId "agent123" -Endpoint "https://swedencentral.api.azureml.ms/agents/v1.0/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.MachineLearningServices/workspaces/{workspace}" -DefinitionFile ".\openapi.json" -ApiType Agent
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]$AgentId,
        [Parameter(Mandatory=$true)]
        [string]$Endpoint,
        [Parameter(Mandatory=$true)]
        [string]$DefinitionFile,
        [string]$Name = "",
        [string]$Description = "",
        [Parameter(Mandatory=$true)]
        [ValidateSet('Agent')]
        [string]$ApiType
    )
    try {
        if ($ApiType -ne 'Agent') { throw "Only Agent API type is supported." }
        $authHeader = Get-MetroAuthHeader -ApiType $ApiType
        $openAPISpec = Get-Content -Path $DefinitionFile -Raw | ConvertFrom-Json
        $body = @{
            tools = @(
                @{
                    type    = "openapi"
                    openapi = @{
                        name        = $Name
                        description = $Description
                        auth        = @{
                            type            = "managed_identity"
                            security_scheme = @{
                                audience = "https://cognitiveservices.azure.com/"
                            }
                        }
                        spec = $openAPISpec
                    }
                }
            )
        } | ConvertTo-Json -Depth 100
        $uri = Get-MetroUri -Endpoint $Endpoint -ApiType $ApiType -Service 'assistants' -Operation 'openapi' -Path $AgentId
        return Invoke-RestMethod -Uri $uri -Method Post -Headers $authHeader -ContentType "application/json" -Body $body
    }
    catch {
        Write-Error "Add-MetroAIAgentOpenAPIDefinition error: $_"
    }
}

#endregion


# Export module members with the Metro prefix.
Export-ModuleMember -Function * -Alias *