providers/openai.ps1

<#
    Esta função é usada como base para invocar a a API da OpenAI!
#>

function InvokeOpenai {
    [CmdletBinding()]
    param(
        $endpoint
        ,$body
        ,$method = 'POST'
        ,$StreamCallback = $null
        ,$Token = $null
    )

    $Provider = Get-AiCurrentProvider
    write-verbose "InvokeOpenAI, current provider = $($Provider.name)"
    $TokenRequired = GetCurrentProviderData RequireToken;

    if(!$Token){
        $TokenEnvName = GetCurrentProviderData TokenEnvName;
        
        if($TokenEnvName){
            write-verbose "Trying get token from environment var: $($TokenEnvName)"
            $Token = (get-item "Env:$TokenEnvName"  -ErrorAction SilentlyContinue).Value
        }
    }    
    
    if(!$Token){
            $Token = GetCurrentProviderData Token;
            
            if(!$token -and $TokenRequired){
                throw "POWERSHAI_OPENAI_NOTOKEN: No token was defined and is required! Provider = $($Provider.name)";
            }
    }
    
    $headers = @{}
    
    if($Token){
         $headers["Authorization"] = "Bearer $token"
    }


    
    if($endpoint -match '^https?://'){
        $url = $endpoint
    } else {
        $BaseUrl = GetCurrentProviderData BaseUrl
        $url = "$BaseUrl/$endpoint"
    }

    if($StreamCallback){
        $body.stream = $true;
        $body.stream_options = @{include_usage = $true};
    }

    $JsonParams = @{Depth = 10}
    
    write-verbose "InvokeOpenai: Converting body to json (depth: $($JsonParams.Depth))..."
    $ReqBodyPrint = $body | ConvertTo-Json @JsonParams
    write-verbose "ReqBody:`n$($ReqBodyPrint|out-string)"
    
    $ReqBody = $body | ConvertTo-Json @JsonParams -Compress
    $ReqParams = @{
        data            = $ReqBody
        url             = $url
        method          = $method
        Headers         = $headers
    }

    $StreamData = @{
        #Todas as respostas enviadas!
        answers = @()
        
        fullContent = ""
        
        FinishMessage = $null
        
        #Todas as functions calls
        calls = @{
            all = @()
            funcs = @{}
        }
        
        CurrentCall = $null
    }
                
    if($StreamCallback){
        $ReqParams['SseCallBack'] = {
            param($data)

            $line = $data.line;
            $StreamData.lines += $line;
            
            if($line -like 'data: {*'){
                $RawJson = $line.replace("data: ","");
                $Answer = $RawJson | ConvertFrom-Json;
                
                $FinishReason     = $Answer.choices[0].finish_reason;
                $DeltaResp         = $Answer.choices[0].delta;
                
                $Role = $DeltaResp.role; #Parece vir somente no primeiro chunk...
                
                $StreamData.fullContent += $DeltaResp.content;
                
                if($FinishReason){
                    $StreamData.FinishMessage = $Answer
                }
                

                foreach($ToolCall in $DeltaResp.tool_calls){
                    $CallId         = $ToolCall.id;
                    $CallType         = $ToolCall.type;
                    verbose "Processing tool`n$($ToolCall|out-string)"
                    
                    
                    if($CallId){
                        $StreamData.calls.all += $ToolCall;
                        $StreamData.CurrentCall = $ToolCall;
                        continue;
                    }
                    
                    $CurrentCall = $StreamData.CurrentCall;
                
                    
                    if($CurrentCall.type -eq 'function' -and $ToolCall.function){
                        $CurrentCall.function.arguments += $ToolCall.function.arguments;
                    }
                }
                
                
                $StreamData.answers += $Answer
                & $StreamCallback $Answer
            }
            elseif($line -eq 'data: [DONE]'){
                #[DONE] documentando aqui:
                #https://platform.openai.com/docs/api-reference/chat/create#chat-create-stream
                return $false;
            }
        }
    }


    write-verbose "ReqParams:`n$($ReqParams|out-string)"
    $RawResp     = InvokeHttp @ReqParams
    write-verbose "RawResp: `n$($RawResp|out-string)"
    
    if($RawResp.stream){
        
        #Isso imita a mensagem de resposta, para ficar igual ao resultado quando está sem Stream!
        $MessageResponse = @{
            role             = "assistant"
            content         = $StreamData.fullContent
        }
        
        if($StreamData.calls.all){
            $MessageResponse.tool_calls = $StreamData.calls.all;
        }
        
        return @{
            stream = @{
                RawResp = $RawResp
                answers = $StreamData.answers
                tools     = $StreamData.calls.all
            }
            
            message = $MessageResponse
            finish_reason = $StreamData.FinishMessage.choices[0].finish_reason
            usage = $StreamData.answers[-1].usage
            model = $StreamData.answers[-1].model
        }
    }

    return $RawResp.text | ConvertFrom-Json
}




# Define o token a ser usado nas chamadas da OpenAI!
# Faz um testes antes para certificar de que é acessível!
function Set-OpenaiToken {
    [CmdletBinding()]
    param()
    
    $ErrorActionPreference = "Stop";
    
    write-host "Forneça o token no campo senha na tela em que se abrir";
    
    $Provider = Get-AiCurrentProvider
    $ProviderName = $Provider.name.toUpper();
    $creds = Get-Credential "$ProviderName API TOKEN";
    
    $TempToken = $creds.GetNetworkCredential().Password;
    
    write-host "Checando se o token é válido";
    try {
        $result = InvokeOpenai 'models' -m 'GET' -token $TempToken
    } catch [System.Net.WebException] {
        $resp = $_.exception.Response;
        
        if($resp.StatusCode -eq 401){
            throw "INVALID_TOKEN: Token is not valid!"
        }
        
        throw;
    }
    write-host -ForegroundColor green " TOKEN ALTERADO!";
    
    SetCurrentProviderData Token $TempToken;
    return;
}

# Configura a Url base a ser usada!
function Set-OpenaiBaseUrl {
    [CmdletBinding()]
    param($url)
    
    SetCurrentProviderData BaseUrl $url
}

function Reset-OpenaiBaseUrl {
    SetCurrentProviderData BaseUrl (GetCurrentProviderData ResetUrl)
}



# Get all models!
function Get-OpenaiModels(){
    return (InvokeOpenai 'models' -m 'GET').data
}

function openai_GetModels(){
    param()
    
    $Models = Get-OpenaiModels
    $Models | Add-Member -Type noteproperty -Name name -Value $null 
    $Models | %{ $_.name = $_.id }
    return $models;
}




<#
    Esta função chama o endpoint /completions (https://platform.openai.com/docs/api-reference/completions/create)
    Exemplo:
        $res = Get-OpenAiTextCompletion "Gere um nome aleatorio"
        $res.choices[0].text;
     
    Ela retorna o mesmo objeto retornado pela API da OpenAI!
    Por enquanto, apenas os parâmetros temperature, model e MaxTokens foram implementados!
#>

function Get-OpenAiTextCompletion {
    [CmdletBinding()]
    param(
            $prompt 
            ,$temperature   = 0.6
            ,$model         = "gpt-3.5-turbo-instruct"
            ,$MaxTokens     = 200
    )
    
    write-warning "LEGACY! This endpoint is legacy. Use Chat Completion!"

    $FullPrompt = @($prompt) -Join "`n";

    $Body = @{
        model       = $model
        prompt      = $FullPrompt
        max_tokens  = $MaxTokens
        temperature = $temperature 
    }

    InvokeOpenai -endpoint 'completions' -body $Body
}



<#
    Esta função chama o endpoint /chat/completions (https://platform.openai.com/docs/api-reference/chat/create)
    Este endpoint permite você conversar com modelos mais avançados como o GPT-3 e o GPT-4 (veja a disponiblidadena doc)
     
    O Chat Completion tem uma forma de conversa um pouco diferente do que o Text Completion.
    No Chat Completion, você pode especificar um role, que é uma espécie de categorização do autor da mensagem.
     
    A API suporta 3 roles:
        user
            Representa um prompt genérico do usuário.
             
        system
            Representa uma mensagem de controle, que pode dar instruções que o modelo vai levar em conta para gerar a resposta.
             
        assistant
            Representa mensagens prévias. É útil para que o modelo possa aprender como gerar, entender o contexto, etc.
             
    Basicamente, o system e o assistant são úteis para calibrar melhor a resposta.
    Enquanto que o user, é o que de fato você quer de resposta (você, ou o seu usuário)
     
     
    Nesta função, para tentar facilitar sua vida, eu deixei duas formas pela qual você usar.
    A primeira forma é a mais simples:
     
        $res = OpenAiChat "Oi GPT, tudo bem?"
        $res.choices[0].message;
         
        Nesta forma, você passa apenas uma mensagem padrão, e a função vai cuidar de enviar como o role "user".
        Você pode passar várias linhas de texto, usando um simples array do PowerShell:
         
        $res = OpenAiChat "Oi GPT, tudo bem?","Me de uma dica aleatoria sobre o PowerShell"
         
        Isso vai enviar o seguinte prompt ao modelo:
            Oi,GPT, tudo bem?
            Me de uma dica aleatoria sobre o PowerShell
         
         
    Caso, você queria especificar um role, basta usar um dos prefixos. (u - user, s - system, a - assitant"
     
        $res = OpenAiChat "s: Use muita informalidade e humor!","u: Olá, me explique o que é o PowerShell!"
        $res.choices[0].message.content;
     
    Você pode usar um array no script:
     
        $Prompt = @(
            'a: function Abc($p){ return $p*100 }'
            "s: Gere uma explicação bastante dramática com no máximo 100 palavras!"
            "Me explique o que a função Abc faz!"
        )
         
        $res = OpenAiChat $Prompt -MaxTokens 1000
         
        DICA: Note que na última mensagem, e não precisei especificar o "u: mensagem", visto que ele ja usa como default se não encontra o prefixo.
        DICA 2: Note que eu usei o parâmetro MaxTokens para aumentar o limite padrão.
#>

function Get-OpenaiChat {
    [CmdletBinding()]
    param(
         $prompt
        ,$temperature   = 0.6
        ,$model         = $null
        ,$MaxTokens     = 1000
        ,$ResponseFormat = $null
        
        ,#Function list, as acceptable by Openai
         #OpenAuxFunc2Tool can be used to convert from powershell to this format!
            $Functions     = @()    
            
        #Add raw params directly to api!
        #overwite previous
        ,$RawParams    = @{}
        
        ,$StreamCallback = $null
        
        ,#Overwrite endpoint url!
            $endpoint = "chat/completions"
    )
    
   
    $Provider = Get-AiCurrentProvider;
    if(!$model){
        $DefaultModel = $Provider.DefaultModel;
        
        if(!$DefaultModel){
            throw "POWERSHAI_NODEFAULT_MODEL: Must set default model using Set-AiDefaultModel"
        }
        
        $model = $DefaultModel
    }

    [object[]]$Messages = @(ConvertTo-OpenaiMessage $prompt);
    
    $Body = @{
        model       = $model
        messages    = $Messages 
        max_tokens  = $MaxTokens
        temperature = $temperature 
    }
    
    if($RawParams){
        $RawParams.keys | %{ $Body[$_] = $RawParams[$_] }
    }
    
    if($ResponseFormat){
        $Body.response_format = $ResponseFormat
    }
    
    if($Functions){
        $Body.tools = $Functions
        $Body.tool_choice = "auto";
    }
    
    write-verbose "Body:$($body|out-string)"
    InvokeOpenai -endpoint $endpoint -body $Body -StreamCallback $StreamCallback
}


Set-Alias -Name OpenAiChat -Value Get-OpenaiChat;
Set-Alias -Name openai_Chat -Value Get-OpenaiChat;



<#
    .SYNOPSIS
        Converte array de string e objetos para um formato de mensagens padrão da OpenAI!
     
    .DESCRIPTION
        Voce pode passar uma array misto onde cada item pode ser uma string ou um objeto.
        Se for uma string, pode iniciar com o prefixo s, u ou a, que significa, respestivamente, system, user ou assistant.
        Se for um objeto, ele adicionado diretamente ao array resultanete.
         
        Veja: https://platform.openai.com/docs/api-reference/chat/create#chat-create-messages
         
    .EXAMPLE
        ConvertTo-OpenaiMessage "Isso é um texto",@{role:"assistant";content="Resposta assistant"}, "s:Msg system"
         
        Retorna o seguinte array:
             
            @{ role = "user", content = "Isso é um texto" }
            @{role:"assistant";content="Resposta assistant"}
            @{ role = "system", content = "Msg system" }
#>

function ConvertTo-OpenaiMessage {
    [CmdletBinding()]
    param($prompt)
    
    $Messages = @();

    $ShortRoles = @{
        s = "system"
        u = "user"
        a = "assistant"
    }
    
    [object[]]$InputMessages = @($prompt);
    
    foreach($m in $InputMessages){
        $ChatMessage =  $null;
        
        #Se não for uma string, assume que é um objeto message contendo as props necessarias
        if($m -isnot [string]){
            write-verbose "Adding chat message directly:`n$($m|out-string)"
            $ChatMessage = $m;
        } else {
            if($m -match '(?s)^([sua]): (.+)'){
                $ShortName  = $matches[1];
                $Content    = $matches[2];

                $RoleName = $ShortRoles[$ShortName];

                if(!$RoleName){
                    $RoleName   = "user"
                    $Content    = $m;
                }
            } else {
                $RoleName   = "user";
                $Content    = $m;
            }
            
            $ChatMessage = @{role = $RoleName; content = $Content};
        }
        
        write-verbose " ChatMessage: $($ChatMessage|out-string)"

        $Messages += $ChatMessage
    }
    
    return $Messages;
}


<#
    Cmdlets Get-OpenaiTool*
    Comandos com este prefixo transformam um objeto de entrada em um OpenaiTool.
    Um OpenaiTools é um objeto que pode ser usado com Invoke-AiChatFunctions, parametro -Functions.
    Com eles, você adiciona a capacidade do modelo decidir executar código, e o PowershAI é a propria engine usada para rodar esse codigo e responder ao modelo.
    A doc desse comando esclarece melhor como ele utiliza esses objetos!
#>


<#
 
    .DESCRIPTION
        Função auxiliar para converter um script .ps1 em um formato de schema esperado pela OpenAI.
        Basicamente, o que essa fução faz é ler um arquivo .ps1 (ou string) juntamente com sua help doc.
        Então, ele retorna um objeto no formato especifiado pela OpenAI para que o modelo possa invocar!
         
        Retorna um hashtable contendo as seguintes keys:
            functions - A lista de funções, com seu codigo lido do arquivo.
                        Quando o modelo invocar, você pode executar diretamente daqui.
                         
            tools - Lista de tools, para ser enviando na chamada da OpenAI.
             
        Você pode documentar suas funções e parâmetros seguindo o Comment Based Help do PowerShell:
        https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_comment_based_help?view=powershell-7.4
#>

function Get-OpenaiToolFromScript {
    [CmdLetBinding()]
    param(
        $ScriptPath
    )
    
    #Defs is : @{ FuncName = @{}, FuncName2 ..., FuncName3... }
    $FunctionDefs = @{};
    
    $ResolvedPath     = Resolve-Path $ScriptPath -EA SilentlyContinue;
    
    if(!$ResolvedPath){
        throw "POWERSHAI_OPENAI_sCRIPT2OPENAI_NOTFOUND: File not found $ScriptPath";
    }
    

    # func é um file?
    # existing path!
    $IsPs1             = $ScriptPath -match '\.ps1$';
    
    if(!$IsPs1 -or !$ResolvedPath){
        throw "POWERSHAI_OPENAI_SCRIPT2OPENAI_ISNOTSCRIPT: $ScriptPath"
    }
    
    [string]$FilePath = $ResolvedPath
    write-verbose "Loading function from file $FilePath"
    
    <#
        Aqui é onde fazemos uma mágica interessante...
        Usamos um scriptblock para carregar o arquivo.
        Logo, o arquivo será carregado apenas no contexto desse scriptblock.
        Então, obtemos todas os comandos, usando o Get-Command, e filtramos apenas os que foram definidos no arquivo.
        Com isso conseguimos trazer uma referencia para todas as funcoes definidas no arquivo, aproveitando o proprio interpretador
        do PowerShell.
        Assim, conseguimos acessar tanto a DOC da funcao, quanto manter um objeto que pode ser usado para executá-la.
    #>

    $GetFunctionsScript = {
        write-verbose "Running $FilePath"
        . $FilePath
        
        $AllFunctions = @{}
        
        #Obtem todas as funcoes definidas no arquivo.
        #For each function get a object
        Get-Command | ? {$_.scriptblock.file -eq $FilePath} | %{
            write-verbose "Function: $($_.name)"
            
            $help = get-help $_.name;
            
            $AllFunctions[$_.name] = @{
                    func = $_
                    help = get-help $_.Name
                }
        }
        
        return $AllFunctions;
    }

    $FunctionDefs = & $GetFunctionsScript
    
    [object[]]$AllTools = @();
    
    
    
    #for each function!
    foreach($KeyName in @($FunctionDefs.keys) ){
        $Def = $FunctionDefs[$KeyName];

        $FuncName     = $KeyName
        $FuncDef     = $Def
        $FuncHelp     = $Def.help;
        $FuncCmd     = $Def.func;
        
        write-verbose "Creating Tool for Function $FuncName"
        
        $OpenAiFunction = @{
                    name = $null 
                    description = $null
                    parameters = @{}
                }
        
        $OpenAiTool = @{
            type         = "function"
            'function'     = $OpenAiFunction
        }
        
        $AllTools += $OpenAiTool;
        
        
        $OpenAiFunction.name         = $FuncHelp.name;
        $description                 = ( @($FuncHelp.Synopsis) + @($FuncHelp.description|%{$_.text})) -join "`n"
        
        if($description.length -gt 1024){
            $description = $description.substring(1,1024);
        }
        
        $OpenaiFunction.description = $description;
        
        # get all parameters!
        $FuncParams = $FuncHelp.parameters.parameter;
        
        $FuncParamSchema = @{}
        $OpenaiFunction.parameters = @{
            type         = "object"
            properties     = $FuncParamSchema
            required     = @()
        }
        
        if(!$FuncParams){
            continue;
        }
        
        foreach($param in $FuncParams){
            $ParamHelp = $param; 
            
            $ParamName = $ParamHelp.name;
            $ParamType = $ParamHelp.type;
            $ParamDesc = @($ParamHelp.description|%{$_.text}) -Join "`n"
            
            $ParamSchema = @{
                    type         = "string"
                    description = $ParamDesc
                    
                    #enum?
                    #items:@{type}
            }
            
            $FuncParamSchema[$ParamName] = $ParamSchema
            
            #Get the typename!
            try {
                $ParamRealType = [type]$ParamType.name
                if($ParamRealType -eq [int]){
                    $ParamSchema.type = "number"
                }
            } catch{
                write-warning "Cannot determined type of param $ParamName! TypeName = $($ParamType.name)"
            }
        }
        
    }
    
    
    return @{
        src         = $ScriptPath
        type         = "script"
        tools         = $AllTools
        functions     = $FunctionDefs
        
        #mapeia um comando para um script!
        map = {
            param($ToolName, $me)
            return $me.functions[$ToolName]
        }
    }
}


<#
    .DESCRIPTION
        Converte comandos do powershell para OpenaiTool.
#>

function Get-OpenaiToolFromCommand {
    [CmdletBinding()]
    param(
        $functions
        ,$parameters = "*"
        ,$UserDescription = $null
    )
    
    $ToolList = @();
    $ConvertErrors = @()
    $Applications = @{};
    
    $ToolData = @{}
    
    
    foreach($function in $functions){
    
    
        $Command = Get-Command -EA SilentlyContinue $function;
        
    
        if($Command.CommandType -eq "Application"){
            
            $AppFriendName = $function.replace(".exe","");
            $Applications[$AppFriendName] = $Command;
            $CommandHelp = @{
                name = $AppFriendName
                Synopsis = @(
                    "Executable application"
                    "FullPath: $($Command.source)"
                    ($Command.FileVersionInfo | select ProductVersion,ProductName,FileVersion,CompanyName  |out-string)
                ) -Join "`n"            
            }
        } else {
            $CommandHelp = Get-Help $function;
        }
        
        $ErrorSlot = @()

        $OpenAiFunction = @{
                    name = $null 
                    description = $nul 
                    parameters = @{}
                }
            
        $OpenAiTool = @{
            type         = "function"
            'function'     = $OpenAiFunction
        }
        
        $ToolList += $OpenAiTool;

        $OpenAiFunction.name         = $CommandHelp.name;
        $description                 = ( @($CommandHelp.Synopsis) + @($CommandHelp.description|%{$_.text})) -join "`n"
        
        if($description.length -gt 1024){
            $description = $description.substring(1,1024);
        }
        
        $OpenaiFunction.description = $description
        
        if($UserDescription){
            $OpenAiFunction.description += "`n" + $UserDescription
        }
            
        # get all parameters!
        $FuncParams = $CommandHelp.parameters.parameter;
            
        $FuncParamSchema = @{}
        $OpenaiFunction.parameters = @{
            type         = "object"
            properties     = $FuncParamSchema
            required     = @()
        }
        
        if(!$FuncParams){
            continue;
        }
        
        $ParametersMeta = $Command.Parameters
        
        foreach($param in $FuncParams){
            $ParamHelp = $param; 
            $ParamName = $ParamHelp.name;
            

            write-verbose "Processing parameter $ParamName";
            
            if($parameters -ne "*" -and $ParamName -notin @($parameters)){
                continue;
            }
            
            
            $ParamType = $ParamHelp.type;
            $ParamDesc = @($ParamHelp.description|%{$_.text}) -Join "`n"
            
            #Obtem o metadatado do parametro!
            if($ParametersMeta){
                $ParamMeta  = $ParametersMeta[$ParamName]
            } else {
                $ParamMeta = $null
            }
            
            
            write-verbose " Type = $ParamType"
            
            $ParamSchema = @{
                    type         = "string"
                    description = $ParamDesc
            }
            
            $FuncParamSchema[$ParamName] = $ParamSchema
            
            #Get the typename!
            try {
                $ParamRealType = [type]$ParamType.name
                
                if($ParamRealType -eq [int]){
                    $ParamSchema.type = "number"
                }
                
                if($ParamRealType -eq [System.Management.Automation.SwitchParameter]){
                    $ParamSchema.type = "boolean"
                }
                
                # Enum {}
                $EnumList = @();
                
                if($ParamRealType.IsEnum){
                    $PossibleValues = $ParamRealType.GetEnumNames();
                    $EnumList += $PossibleValues
                }
                
                if($ParamMeta){
                    $AttrValidateSet = $ParamMeta.Attributes | ? { $_ -is [ValidateSet] }
                    
                    if($AttrValidateSet){
                        $EnumList += $AttrValidateSet.ValidValues
                    }
                }
                
                if($EnumList){
                    $ParamSchema.enum = $EnumList
                }
                
            } catch{
                write-warning "Cannot determined type of param $ParamName! TypeName = $($ParamType.name)"
            }
        }
                
    }

    return @{
        #Lista de tools que pode ser enviando na OpenAI
        tools     = $ToolList
        apps     = $Applications
        type     = "commands"
        src     = "commands"
        
        map = {
            param($ToolName, $me)
            
            $FuncName = $ToolName;
            $App = $me.apps[$ToolName];
            
            if($App){
                $FuncName = $App.Name;
            }
            
            return @{ func = $FuncName }
        }
    }
}


#Gets cost of answer!
$POWERSHAI_CACHED_MODELS_PRICE = @{};
function Get-OpenAiAnswerCost($answers){
    
    #Pricings at 17/01/2024
    $Pricings = @{
        'estimated' = @{
            input     = 0.1
            output  = 0.3
            match     = '.+'
        }
        
        'gpt-4-turbo' = @{
            match     = "gpt-4.+-preview"
            input     = 0.01
            output     = 0.03 
        }
        
        'gpt-4' = @{
            match     = "^gpt-4"
            input     = 0.03
            output     = 0.06 
        }
        
        'gpt-3' = @{
            match   = "gpt-3.+"
            input     = 0.0010 
            output     = 0.0020
        }
        
        'embedding' = @{
            match     = '.+embedding.+'
            input    = 0.0001
            output    = 0
        }
    }
    
    $AllAnswers = @();
    

    $costs = @{
        answers = @()
        input     = 0
        output     = 0
        total     = 0
        tokensTotal = 0
        tokensInput = 0
        tokensOutput = 0
    }
    
    
    foreach($answer in @($answers)){
    
        $model = $answer.model;
        $inputTokens = $answer.usage.prompt_tokens;
        $outputTokens = $answer.usage.completion_tokens;
        
        if(!$outputTokens){
            $outputTokens = 0;
        }
        
        if(!$model){
            break;
        }
        
        #Is in cache?
        $CachedMatch = $POWERSHAI_CACHED_MODELS_PRICE[$model]
        
        if(!$CachedMatch){
            # Find high pricing...
            $SortedPrices = $Pricings.GetEnumerator() | ? {$model -match $_.value.match} | Sort-Object {$_.value.match.length} -Desc
            
            if($SortedPrices){
                $BestMatch = $SortedPrices[0];
            } else {
                $BestMatch = $SortedPrices['estimated'];
            }
            
            $CachedMatch = $BestMatch
            $POWERSHAI_CACHED_MODELS_PRICE[$model] = $CachedMatch
        }

        $AnswerCosts = @{
            pricings     = $CachedMatch.value
            table          = $CachedMatch.key
            inputCost     = [decimal]($CachedMatch.value.input * $inputTokens/1000)
            outputCost     = [decimal]($CachedMatch.value.output * $outputTokens/1000)
            totalCost    = $null
        }
        
        $AnswerCosts.totalCost = $AnswerCosts.inputCost + $AnswerCosts.outputCost;

        $costs.answers     += $AnswerCosts;
        $costs.input     += $AnswerCosts.inputCost;
        $costs.output     += $AnswerCosts.outputCost;
        $costs.tokensTotal += $inputTokens + $outputTokens;
        $costs.tokensInput += $inputTokens;
        $costs.tokensOutput += $outputTokens;
    }
    
    $costs.total = $costs.output + $costs.input;
    
    return $costs;
}


# Representa cada interação feita com o modelo!
function NewAiInteraction {
    return @{
            req          = $null    # the current req count!
            sent         = $null # Sent data!
            rawAnswer      = $null # the raw answer returned by api!
            stopReason     = $null    # The reason stopped!
            error         = $null # If apply, the error ocurred when processing the answer!
            seqError     = $null # current sequence error
            toolResults    = @() # lot of about running functino!
        }
}




# Invoca a api para ober os mebddings
function Get-OpenaiEmbeddings {
    param($inputText,$model)
    
    if(!$model){
        $model = 'text-embedding-3-small'
    }
    
    $body = @{
        input = $inputText 
        model = $model 
    }
    
    InvokeOpenai -endpoint 'embeddings' -body $Body
}

<#
    Gera o embedding de um texto!
#>

function Get-OpenaiEmbeddings {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline)]
        $text
        ,$model
    )
    
    process {
    
        $ans = Invoke-OpenaiEmbeddings  -input $text -model $model
        $costs = Get-OpenAiAnswerCost $ans
        
        [object[]]$AllEmbeddings = @($null) * $ans.data.length;
        
        $ans.data | %{ $AllEmbeddings[$_.index] = $_.embedding }
        
        return [PsCustomObject]@{
            rawAnswer     = $ans
            costs         = $costs
            embeddings     = $AllEmbeddings
            text          = $text
        }
        
    }
}




#quebra o texto em tokens...
function SplitOpenAiString {
    write-host "TODO..."
}



return @{
    RequireToken     = $true
    BaseUrl         = "https://api.openai.com/v1"
    ResetUrl         = "https://api.openai.com/v1"
    DefaultModel     = "gpt-4o-mini"
    TokenEnvName     = "OPENAI_API_KEY"
    
    info = @{
        desc    = "OpenAI"
        url     = "https://openai.com/"
    }
}