Private/ChatCompletionFunction.ps1

using namespace System.Management.Automation

function New-ChatCompletionFunctionFromHashTable {
    [CmdletBinding()]
    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    param (
        [Parameter(Mandatory, Position = 0)]
        [ValidatePattern('^[a-zA-Z0-9_-]{1,64}$')]
        [string]$Name,

        [Parameter()]
        [string]$Description,

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

        [Parameter(DontShow)]
        [string]$ParametersType = 'object'
    )

    $object = [ordered]@{
        name = $Name
    }

    if ($Description) {
        $object.Add('description', $Description)
    }
    if ($ParametersHashTable) {
        $p = [ordered]@{
            type = $TargetParametersType
        }
        $p.Add('properties', $ParametersHashTable)
        $object.Add('parameters', $p)
    }

    $object
}

function New-ChatCompletionFunctionFromPSCommand {
    [CmdletBinding()]
    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    param (
        [Parameter(Mandatory, Position = 0)]
        [ValidateScript({ (Get-Command $_ -ea Ignore) -is [CommandInfo] })]
        [string]$Command,

        [Parameter()]
        [string]$Description,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string[]]$IncludeParameters,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string[]]$ExcludeParameters,

        [Parameter()]
        [string]$ParameterSetName,

        [Parameter()]
        [switch]$Strict
    )

    $FunctionDefinition = [ordered]@{}
    $MandatoryParams = [System.Collections.Generic.List[string]]::new()
    $ExcludeParamNames = ([Cmdlet]::CommonParameters + [Cmdlet]::OptionalCommonParameters + $ExcludeParameters)

    $CommandInfo = Get-Command $Command
    if ($null -eq $CommandInfo) {
        return
    }
    if ($CommandInfo -is [AliasInfo]) {
        $CommandInfo = $CommandInfo.ResolvedCommand
    }
    if ($CommandInfo -isnot [CmdletInfo] -and $CommandInfo -isnot [FunctionInfo]) {
        Write-Error "$Command is not a PowerShell command."
        return
    }

    $CommandHelp = Get-Help $CommandInfo -ErrorAction Ignore

    if ($ParameterSetName) {
        $TargetParameterSet = $CommandInfo.ParameterSets | Where-Object { $_.Name -eq $ParameterSetName }
        if (-not $TargetParameterSet) {
            Write-Error "$ParameterSetName does not exist."
            return
        }
    }
    else {
        $TargetParameterSet = $CommandInfo.ParameterSets | Where-Object { $_.IsDefault }
        if (-not $TargetParameterSet) {
            $TargetParameterSet = $CommandInfo.ParameterSets[0]
        }
    }

    $TargetParameters = $TargetParameterSet.Parameters | Where-Object { $_.Name -notin $ExcludeParamNames }
    if ($IncludeParameters.Count -gt 0) {
        $TargetParameters = $TargetParameters | Where-Object { $_.Name -in $IncludeParameters }
    }

    $FunctionDefinition.Add('name', $CommandInfo.Name)
    if ($Description) {
        $FunctionDefinition.Add('description', $Description)
    }
    elseif ($CommandHelp.description) {
        $FunctionDefinition.Add('description', (($CommandHelp.description.text -join "`n") -replace "`r", ''))
    }
    elseif ($CommandHelp.Synopsis) {
        $FunctionDefinition.Add('description', ($CommandHelp.Synopsis -replace "`r", ''))
    }

    if ($Strict) {
        $FunctionDefinition.Add('strict', $true)
    }

    $paramHash = [ordered]@{type = 'object' }
    $props = [ordered]@{}
    foreach ($param in $TargetParameters) {
        $isHiddenParam = $false
        $pName = $param.Name
        $propHash = ParseParameterType($param.ParameterType)

        # All fields must be required in Structured Outputs
        if ($param.IsMandatory -or $Strict) {
            $MandatoryParams.Add($pName)
        }

        if (-not $param.IsMandatory) {
            $propHash.'type' = [string[]]@($propHash.'type', 'null')
        }

        $helpmsg = (($CommandHelp.parameters.parameter | Where-Object { $_.name -eq $pName }).description.text -join "`n") -replace "`r", ''
        if ([string]::IsNullOrWhiteSpace($helpmsg)) {
            $helpmsg = [string]$param.HelpMessage
        }
        if (-not [string]::IsNullOrWhiteSpace($helpmsg)) {
            $propHash.Add('description', $helpmsg)
        }

        foreach ($attr in $param.Attributes) {
            if ($attr -is [Parameter] -and $attr.DontShow) {
                $isHiddenParam = $true
            }

            # Attributes are not yet supported in Structured Outputs
            if (-not $Strict) {
                if ($attr -is [ValidatePattern]) {
                    if ($attr.RegexPattern) { $propHash.pattern = $attr.RegexPattern }
                }
                elseif ($attr -is [ValidateCount]) {
                    $propHash.minItems = $attr.MinLength
                    $propHash.maxItems = $attr.MaxLength
                }
                elseif ($attr -is [ValidateLength]) {
                    $propHash.minLength = $attr.MinLength
                    $propHash.maxLength = $attr.MaxLength
                }
                elseif ($attr -is [ValidateRange]) {
                    if ($null -ne $attr.MinRange) { $propHash.minimum = $attr.MinRange }
                    if ($null -ne $attr.MaxRange) { $propHash.maximum = $attr.MaxRange }
                }
                elseif ($attr -is [ValidateSet]) {
                    $propHash.enum = $attr.ValidValues
                }
            }
        }

        if (-not $isHiddenParam) {
            $props.Add($pName, $propHash)
        }
    }
    $paramHash.Add('properties', $props)
    $paramHash.Add('additionalProperties', $false)
    if ($MandatoryParams.Count -gt 0) {
        $paramHash.Add('required', $MandatoryParams.ToArray())
    }
    $FunctionDefinition.Add('parameters', $paramHash)

    $FunctionDefinition
}


function ParseParameterType {
    param (
        [System.Reflection.TypeInfo]$ParameterType
    )

    if ($null -eq $ParameterType) {
        return
    }

    if ($ParameterType.IsArray) {
        $p = @{
            type = 'array'
        }
        $type = ($ParameterType.FullName -replace '\[\]', '') -as [type]
        if ($type) {
            $p.items = ParseParameterType($type)
        }
        $p
    }
    elseif ($ParameterType -in ([bool], [switch])) {
        @{
            type = 'boolean'
        }
    }
    elseif ($ParameterType -in ([int32], [int64], [int16], [bigint])) {
        @{
            type = 'integer'
        }
    }
    elseif ($ParameterType -in ([uint32], [uint64], [uint16])) {
        @{
            type    = 'integer'
            minimum = 0
        }
    }
    elseif ($ParameterType -in ([byte])) {
        @{
            type    = 'integer'
            minimum = 0
            maximum = 255
        }
    }
    elseif ($ParameterType -in ([Single], [double], [decimal])) {
        @{
            type = 'number'
        }
    }
    elseif ($ParameterType -is [string]) {
        @{
            type = 'string'
        }
    }
    elseif ($ParameterType.IsEnum) {
        @{
            type = 'string'
            enum = [string[]][enum]::GetValues($ParameterType)
        }
    }
    elseif ($ParameterType -is [datetime]) {
        @{
            type   = 'string'
            format = 'date-time'
        }
    }
    elseif ($ParameterType -is [regex]) {
        @{
            type   = 'string'
            format = 'regex'
        }
    }
    elseif ($ParameterType -is [uri]) {
        @{
            type   = 'string'
            format = 'uri'
        }
    }
    elseif ($ParameterType -is [guid]) {
        @{
            type   = 'string'
            format = 'uuid'
        }
    }
    elseif ($ParameterType -is [mailaddress]) {
        @{
            type   = 'string'
            format = 'email'
        }
    }
    elseif ($ParameterType.ImplementedInterfaces -contains [System.Collections.IDictionary]) {
        @{
            type = 'object'
        }
    }
    elseif ($ParameterType -is [System.Collections.IDictionary]) {
        @{
            type = 'object'
        }
    }
    else {
        @{
            type = 'string'
        }
    }
}