Public/Decisions/Request-Decision.ps1

function Request-Decision {
    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param (
        [Parameter(Position = 0, ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [Alias('Input')]
        [string[]]$Message,

        [Parameter()]
        [ValidateCount(1, 128)]
        [ValidateNotNullOrEmpty()]
        [string[]]$Images,

        [Parameter()]
        [Completions('auto', 'low', 'high', 'original')]
        [string][LowerCaseTransformation()]$ImageDetail = 'auto',

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [Completions('gpt-6-luna')]
        [string]$Model = 'gpt-6-luna',

        #region Questions
        # Predicate
        [Parameter()]
        [string[]]$PredicateQuestionName,

        [Parameter()]
        [string[]]$PredicateQuestionInstructions,

        # Choice
        [Parameter()]
        [string[]]$ChoiceQuestionName,

        [Parameter()]
        [string[]]$ChoiceQuestionInstructions,

        [Parameter()]
        [object[]]$ChoiceQuestionChoices,

        # Score
        [Parameter()]
        [string[]]$ScoreQuestionName,

        [Parameter()]
        [string[]]$ScoreQuestionInstructions,

        [Parameter()]
        [object[]]$ScoreQuestionLevels,
        #endregion

        [Parameter()]
        [AllowEmptyString()]
        [ValidateLength(0, 128)]
        [Alias('safety_identifier')]
        [string]$SafetyIdentifier,

        [Parameter()]
        [int]$TimeoutSec = 0,

        [Parameter()]
        [ValidateRange(0, 100)]
        [int]$MaxRetryCount = 0,

        [Parameter()]
        [System.Uri]$ApiBase,

        [Parameter()]
        [securestring][SecureStringTransformation()]$ApiKey,

        [Parameter()]
        [Alias('OrgId')]
        [string]$Organization,

        [Parameter()]
        [System.Collections.IDictionary]$AdditionalQuery,

        [Parameter()]
        [System.Collections.IDictionary]$AdditionalHeaders,

        [Parameter()]
        [object]$AdditionalBody
    )

    begin {
        $Questions = @()
        #Predicate
        $PredicationQuestions = @()
        if ($PredicateQuestionInstructions.Count -gt 0) {
            for ($i = 0; $i -lt $PredicateQuestionInstructions.Count; $i++) {
                $p = @{
                    type         = 'predicate'
                    instructions = $PredicateQuestionInstructions[$i]
                }
                if ($null -ne $PredicateQuestionName -and -not [string]::IsNullOrEmpty($PredicateQuestionName[$i])) {
                    $p.name = $PredicateQuestionName[$i]
                }
                $PredicationQuestions += $p
            }
            if ($PredicationQuestions.Count -gt 0) {
                $Questions += $PredicationQuestions
            }
        }

        #Choice
        $ChoiceQuestions = @()
        if ($ChoiceQuestionInstructions.Count -gt 0) {
            if ($ChoiceQuestionChoices.Count -ne $ChoiceQuestionInstructions.Count) {
                Write-Error -Exception ([System.ArgumentException]::new('The ChoiceQuestionChoices parameter must have one hashtable for each ChoiceQuestionInstructions entry.'))
            }
            else {
                for ($i = 0; $i -lt $ChoiceQuestionInstructions.Count; $i++) {
                    if ($ChoiceQuestionChoices[$i].Count -eq 0) {
                        Write-Error -Exception ([System.ArgumentException]::new('Each choice question requires at least one choice.'))
                        continue
                    }
                    $q = @{
                        type         = 'choice'
                        instructions = $ChoiceQuestionInstructions[$i]
                        choices      = @()
                    }
                    if ($null -ne $ChoiceQuestionName -and -not [string]::IsNullOrEmpty($ChoiceQuestionName[$i])) {
                        $q.name = $ChoiceQuestionName[$i]
                    }

                    if ($ChoiceQuestionChoices[$i] -is [System.Collections.IDictionary]) {
                        foreach ($key in $ChoiceQuestionChoices[$i].Keys) {
                            $c = @{
                                value = $key
                            }
                            if (-not [string]::IsNullOrEmpty($ChoiceQuestionChoices[$i][$key])) {
                                $c.description = [string]$ChoiceQuestionChoices[$i][$key]
                            }
                            $q.choices += $c
                        }
                    }
                    elseif ( $ChoiceQuestionChoices[$i] -is [System.Collections.IEnumerable]) {
                        foreach ($value in $ChoiceQuestionChoices[$i]) {
                            if ($value -is [System.Collections.IDictionary] -and $value.Contains('value')) {
                                $c = @{ value = $value['value'] }
                                if (-not [string]::IsNullOrEmpty($value['description'])) {
                                    $c.description = [string]$value['description']
                                }
                            }
                            elseif ($null -ne $value -and $null -ne $value.PSObject.Properties['value']) {
                                $c = @{ value = $value.value }
                                if (-not [string]::IsNullOrEmpty($value.description)) {
                                    $c.description = [string]$value.description
                                }
                            }
                            else {
                                $c = @{ value = $value }
                            }
                            $q.choices += $c
                        }
                    }
                    elseif ( $ChoiceQuestionChoices[$i].value -is [string] -or $ChoiceQuestionChoices[$i].value -is [bool]) {
                        $c = @{ value = $ChoiceQuestionChoices[$i].value }
                        if (-not [string]::IsNullOrEmpty($ChoiceQuestionChoices[$i].description)) {
                            $c.description = [string]$ChoiceQuestionChoices[$i].description
                        }
                        $q.choices += $c
                    }
                    else {
                        Write-Error -Exception ([System.ArgumentException]::new('Each ChoiceQuestionChoices entry must be a hashtable or an array.'))
                        continue
                    }

                    if ($q.choices.Count -lt 2) {
                        Write-Error -Exception ([System.ArgumentException]::new('Each choice question requires at least two choices.'))
                        continue
                    }
                    $ChoiceQuestions += $q
                }
                if ($ChoiceQuestions.Count -gt 0) {
                    $Questions += $ChoiceQuestions
                }
            }
        }

        #Score
        $ScoreQuestions = @()
        if ($ScoreQuestionInstructions.Count -gt 0) {
            if ($ScoreQuestionLevels.Count -ne $ScoreQuestionInstructions.Count) {
                Write-Error -Exception ([System.ArgumentException]::new('The ScoreQuestionLevels parameter must have one hashtable for each ScoreQuestionInstructions entry.'))
            }
            else {
                for ($i = 0; $i -lt $ScoreQuestionInstructions.Count; $i++) {
                    if ($ScoreQuestionLevels[$i].Count -eq 0) {
                        Write-Error -Exception ([System.ArgumentException]::new('Each score question requires at least one level.'))
                        continue
                    }
                    $q = @{
                        type         = 'score'
                        instructions = $ScoreQuestionInstructions[$i]
                        levels       = @()
                    }
                    if ($null -ne $ScoreQuestionName -and -not [string]::IsNullOrEmpty($ScoreQuestionName[$i])) {
                        $q.name = $ScoreQuestionName[$i]
                    }

                    if ($ScoreQuestionLevels[$i] -is [System.Collections.IDictionary]) {
                        if ($ScoreQuestionLevels[$i] -is [hashtable]) {
                            Write-Warning ('ScoreQuestionLevels[{0}] is a Hashtable, so level order is not guaranteed. Use an ordered dictionary or an array to preserve order.' -f $i)
                        }
                        foreach ($key in $ScoreQuestionLevels[$i].Keys) {
                            $l = @{
                                label = [string]$key
                            }
                            if (-not [string]::IsNullOrEmpty($ScoreQuestionLevels[$i][$key])) {
                                $l.description = [string]$ScoreQuestionLevels[$i][$key]
                            }
                            $q.levels += $l
                        }
                    }
                    elseif ( $ScoreQuestionLevels[$i] -is [System.Collections.IEnumerable]) {
                        foreach ($value in $ScoreQuestionLevels[$i]) {
                            if ($value -is [System.Collections.IDictionary] -and $value.Contains('label')) {
                                $l = @{ label = [string]$value['label'] }
                                if (-not [string]::IsNullOrEmpty($value['description'])) {
                                    $l.description = [string]$value['description']
                                }
                            }
                            elseif ($null -ne $value -and $null -ne $value.PSObject.Properties['label']) {
                                $l = @{ label = [string]$value.label }
                                if (-not [string]::IsNullOrEmpty($value.description)) {
                                    $l.description = [string]$value.description
                                }
                            }
                            else {
                                $l = @{ label = [string]$value }
                            }
                            $q.levels += $l
                        }
                    }
                    elseif ( $ScoreQuestionLevels[$i].label -as [string]) {
                        $l = @{ label = [string]$ScoreQuestionLevels[$i].label }
                        if (-not [string]::IsNullOrEmpty($ScoreQuestionLevels[$i].description)) {
                            $l.description = [string]$ScoreQuestionLevels[$i].description
                        }
                        $q.levels += $l
                    }
                    else {
                        Write-Error -Exception ([System.ArgumentException]::new('Each ScoreQuestionLevels entry must be a hashtable or an array.'))
                        continue
                    }

                    $ScoreQuestions += $q
                }
                if ($ScoreQuestions.Count -gt 0) {
                    $Questions += $ScoreQuestions
                }
            }
        }

        if ($Questions.Count -eq 0) {
            $er = [System.Management.Automation.ErrorRecord]::new(
                [System.ArgumentException]::new('At least one question is required.'),
                'NoQuestions',
                [System.Management.Automation.ErrorCategory]::InvalidArgument,
                $null
            )
            $PSCmdlet.ThrowTerminatingError($er)
        }

        $OpenAIParameter = Get-OpenAIAPIParameter -EndpointName 'Decisions' -Parameters $PSBoundParameters -ErrorAction Stop
    }

    process {
        $PostBody = [System.Collections.Specialized.OrderedDictionary]::new()
        $PostBody.model = $Model
        $PostBody.questions = $Questions

        if ($Images.Count -gt 0 -or $Message.Count -gt 1) {
            $Parts = @()
            foreach ($Text in $Message) {
                $Parts += @{ type = 'input_text'; text = $Text }
            }
            foreach ($Image in $Images) {
                $ImageUri = $null
                if ($Image -match '^data:image/[^;,]+;base64,') {
                    $ImageUrl = $Image
                }
                elseif ([System.Uri]::TryCreate($Image, [System.UriKind]::Absolute, [ref]$ImageUri) -and $ImageUri.Scheme -in @('http', 'https')) {
                    $ImageUrl = $Image
                }
                else {
                    # This parameter accepts local files, inline data URLs, and HTTP(S) URLs.
                    # Missing files and unsupported URI schemes must fail before sending.
                    $ImageUrl = Convert-ImageToDataURL -File $Image -ErrorAction Stop
                }
                $Parts += @{ type = 'input_image'; image_url = $ImageUrl; detail = $ImageDetail }
            }
            $PostBody.input = @(@{ role = 'user'; content = $Parts })
        }
        elseif ($PSBoundParameters.ContainsKey('Message')) {
            $PostBody.input = $Message[0]
        }
        else {
            Write-Error -Exception ([System.ArgumentException]::new('Message or Images is required.'))
            return
        }

        if ($PSBoundParameters.ContainsKey('SafetyIdentifier')) {
            $PostBody.safety_identifier = $SafetyIdentifier
        }

        $params = @{
            Method            = $OpenAIParameter.Method
            Uri               = $OpenAIParameter.Uri
            ContentType       = $OpenAIParameter.ContentType
            TimeoutSec        = $OpenAIParameter.TimeoutSec
            MaxRetryCount     = $OpenAIParameter.MaxRetryCount
            ApiKey            = $OpenAIParameter.ApiKey
            Organization      = $OpenAIParameter.Organization
            Body              = $PostBody
            AdditionalQuery   = $AdditionalQuery
            AdditionalHeaders = $AdditionalHeaders
            AdditionalBody    = $AdditionalBody
        }
        $Response = Invoke-OpenAIHttpRequest @params
        if ($null -eq $Response) {
            return
        }

        try {
            $Response = $Response | ConvertFrom-Json -ErrorAction Stop
        }
        catch {
            Write-Error -Exception $_.Exception
            return
        }

        if ($null -ne $Response) {
            $Response.PSObject.TypeNames.Insert(0, 'PSOpenAI.Decision')
            Write-Output $Response
        }
    }
}