providers/google.ps1

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

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

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

    if(!$Token){
        $TokenEnvName = GetCurrentProviderData TokenEnvName;
        
        if($TokenEnvName){
            verbose "Trying get token from environment var: $($TokenEnvName)"
            $Token = (get-item "Env:$TokenEnvName"  -ErrorAction SilentlyContinue).Value
            if($Token){
                verbose " Token got from ENV VAR $TokenEnvName!";
            }
            
        }
    }    
    
    if($TokenRequired -and !$Token){
            $Token = GetCurrentProviderData Token;
            
            if(!$token){
                throw "POWERSHAI_GOOGLE_NOTOKEN: No token was defined and is required! Provider = $($Provider.name)";
            }
    }
    
    $headers = @{}
    
    if($TokenRequired){
         $headers["x-goog-api-key"] = "$token"
    }


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

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

            
    if($StreamCallback){
        $ReqParams['SseCallBack'] = $StreamCallback
    }


    verbose "ReqParams:`n$($ReqParams|out-string)"
    $RawResp     = InvokeHttp @ReqParams
    verbose "RawResp: `n$($RawResp|out-string)"

    if($RawResp.stream){
        return $RawResp;
    }

    return $RawResp.text | ConvertFrom-Json
}

<#
    .SYNOPSIS
        Define a API Key (o Token) da Api do Google.
#>

function Set-GoogleApiKey {
    [CmdletBinding()]
    param()
    
    $ErrorActionPreference = "Stop";
    
    write-host "Forneça o token no campo senha na tela em que se abrir";
    
    $Provider = Get-AiCurrentProvider
    $creds = Get-Credential "GOOGLE API KEY";
    
    $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;
}



<#
    .SYNOPSIS
        Obtém a lista de modelos do Google. Endpoint: https://ai.google.dev/api/models
#>

function Get-GoogleModels(){
    [CmdletBinding()]
    param()
    
    (InvokeGoogleApi -method GET "v1beta/models").models
}


function google_GetModels(){
    param()
    
    $Models = Get-GoogleModels
    $Models | %{
        $_.name = $_.name -replace '^models/',''
    }
    return $models;
}

<#
    .SYNOPSIS
        Endpoint: https://ai.google.dev/api/generate-content
        Stream: https://ai.google.dev/api/generate-content#method:-models.streamgeneratecontent
#>

function Invoke-GoogleGenerateContent {
    [CmdletBinding()]
    param(
        $messages = @()
        ,$model = "gemini-1.5-flash"
        ,$StreamCallback = $null
    )
    
    
    #Note que reaproveitamos o convert to openai message do provider!
    $GoogleContent = @(ConvertTo-GoogleContentMessage $messages)
    
    $Data = @{
        contents = $GoogleContent.content
        #tools = @()
        systemInstruction  = @{
                parts = @(
                        @{text = $GoogleContent.SystemMessage }
                    )
            }
    }
    
    $ReqParams = @{
        body = $Data 
        method = "POST"
    }
    
    $OpName = "generateContent"
    if($StreamCallback){
        $ReqParams['StreamCallback'] = $StreamCallback 
        $OpName = "streamGenerateContent?alt=sse"
    }
    
    InvokeGoogleApi "v1beta/models/$($model):$OpName" @ReqParams
}

function google_Chat {
    [CmdletBinding()]
    param(
         $prompt
        ,$temperature   = 0.6
        ,$model         = $null
        ,$MaxTokens     = 200
        ,$ResponseFormat = $null
        ,$Functions     = @()    
        ,$RawParams    = @{}
        ,$StreamCallback = $null
        ,[switch]$IncludeRawResp
    )
    
    $Params = @{
        messages = $prompt
        model = $model
    }
    
    if($StreamCallback){
        
        # dados para serem usados durante a leitura do stream!
        $StreamData = @{
            #Todas as respostas enviadas!
            answers = @()
            fullContent = ""
            
            #Todas as functions calls
            calls = @{
                all = @()
                funcs = @{}
            }
            
            CurrentCall = $null
        }
        
        $UserScriptCallback = $StreamCallback
        $StreamScript = {
            param($data)
            
            $line = $data.line
            
            if($line -like 'data: {*'){
                $RawJson = $line.replace("data: ","");
            } else {
                return;
            }
            
            $AnswerJson = $RawJson | ConvertFrom-Json;
            
            $AnswerText = @($AnswerJson.candidates)[0].content.parts[0].text;
            $StreamData.answers += $AnswerJson;
            
            $StreamData.fullContent += $AnswerText
                
            $StdAnswer = @{
                choices = @(
                    @{
                        delta = @{content = $AnswerText }
                    }
                )
            }

            & $UserScriptCallback $StdAnswer
            
            
                
            # 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;
            # }
            # }
            
            #
        }
        
        $Params['StreamCallback'] = $StreamScript 
    }
    
    $Resp = Invoke-GoogleGenerateContent @Params
    
    if($resp.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 = $resp
                answers = $StreamData.answers
                tools     = $null
            }
            
            message = $MessageResponse
            finish_reason = $StreamData.answers[-1].candidates[0].finishReason
            usage = @{
                    prompt_tokens         = $StreamData.answers[-1].usageMetadata.promptTokenCount
                    completion_tokens     = $StreamData.answers[-1].usageMetadata.candidatesTokenCount
                    total_tokens         = $StreamData.answers[-1].usageMetadata.totalTokenCount
                }
            model = $model
        }
        
    }

    #Nao-stream!
    $ResultObj = @{
        choices = @(
            @{
                finish_reason     = $resp.candidates[0].finishReason.ToLower()
                index             = $resp.candidates[0].index
                logprobs         = $null
                message         = @{
                                    role = "assistant"
                                    content = $resp.candidates[0].content.parts[0].text
                                }
            }
        )
        
        usage     = @{
                    prompt_tokens         = $resp.usageMetadata.promptTokenCount
                    completion_tokens     = $resp.usageMetadata.candidatesTokenCount
                    total_tokens         = $resp.usageMetadata.totalTokenCount
                }
        model     = $model
        created = [int](((Get-Date) - (Get-Date "1970-01-01T00:00:00Z")).TotalSeconds)
        object    = "chat.completion"
        id         = $null
        system_fingerprint = $null
    }
    
    if($IncludeRawResp){
        $ResultObj.RawResp = $Resp
    }
    
    return $ResultObj;
}

<#
    .SYNOPSIS
        Converte OpenAI messages para um array de Content message
        https://ai.google.dev/api/caching#Content
         
#>

function ConvertTo-GoogleContentMessage {
    param($messages)
    
    [object[]]$messages = @(ConvertTo-OpenaiMessage $messages)
    
    
    $ContentMessages = @()
    $SystemMessage = @()
    
    foreach($m in $messages){
        
        $NewContent = @{
            parts = @()
            role = $null
        }
        
        $MsgContent = $m.content;
        $NewContent.role = $m.role;
        if($m.role -ne "user"){
            $NewContent.role = "model"
        }
        
        if($m.role -eq "system"){
            $SystemMessage += $MsgContent;
            continue;
        }
        
        $NewContent.parts = @(
                @{ text = $MsgContent }
            )
        
        $ContentMessages += $NewContent;
        
    }
    
    
    return [PsCustomObject]@{
        SystemMessage = ($SystemMessage -Join "`n")
        content = $ContentMessages
    }
    
}


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





return @{
    RequireToken     = $true
    ApiVersion         = "v1"
    BaseUrl         = "https://generativelanguage.googleapis.com"
    DefaultModel     = "gemini-1.5-flash"
    TokenEnvName     = "GOOGLE_API_KEY"
    
    info = @{
        desc    = "Google Gemini"
        url     = "https://ai.google.dev/"
    }
}