Private/Invoke-GeminiAIApi.ps1
|
function Invoke-GeminiAIApi { <# .SYNOPSIS Calls the Gemini AI Proxy API with automatic API key management. .DESCRIPTION Invokes the Gemini AI API through the Azure Function proxy. Automatically generates and caches API keys using New-PSUApiKey. Supports automatic retry on failures and API key expiration. .PARAMETER Prompt The prompt to send to Gemini AI. .PARAMETER ReturnJsonResponse Request JSON-formatted response from Gemini. .PARAMETER TimeoutSeconds HTTP request timeout in seconds. Default is 90. .PARAMETER RetryCount Number of retry attempts on failure. Default is 3. .PARAMETER RetryDelaySeconds Delay between retries in seconds. Default is 2. .PARAMETER ApiUrl The Azure Function endpoint URL. .PARAMETER ForceNewApiKey Force generation of a new API key even if cached one exists. .EXAMPLE Invoke-GeminiAIApi -Prompt "What is PowerShell?" Makes a request using cached or newly generated API key .EXAMPLE Invoke-GeminiAIApi -Prompt "List 3 colors" -ReturnJsonResponse Requests JSON response format .EXAMPLE Invoke-GeminiAIApi -Prompt "Explain REST" -ForceNewApiKey Forces generation of new API key before making request .OUTPUTS System.String .NOTES Author: Lakshmanachari Panuganti Date: 10th December 2025 #> [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory = $true, Position = 0)] [ValidateNotNullOrEmpty()] [string]$Prompt, [Parameter()] [switch]$ReturnJsonResponse, [Parameter()] [ValidateRange(10, 300)] [int]$TimeoutSeconds = 90, [Parameter()] [ValidateRange(1, 10)] [int]$RetryCount = 3, [Parameter()] [ValidateRange(1, 30)] [int]$RetryDelaySeconds = 2, [Parameter()] [ValidateNotNullOrEmpty()] [string]$ApiUrl = "https://omg-gemini.azurewebsites.net/api/proxy", [Parameter()] [switch]$ForceNewApiKey ) begin { Write-Verbose "=== Starting Gemini AI API Call ===" Write-Verbose "Prompt length: $($Prompt.Length) characters" Write-Verbose "JSON response: $($ReturnJsonResponse.IsPresent)" Write-Verbose "API URL: $ApiUrl" } process { # ============================================ # 1. Ensure API Key is Available # ============================================ try { # Check if New-PSUApiKey function exists if (-not (Get-Command -Name New-PSUApiKey -ErrorAction SilentlyContinue)) { throw "Required function 'New-PSUApiKey' not found. Please ensure it is loaded in the current session." } # Check if we need a new API key $needNewKey = $false if ($ForceNewApiKey) { Write-Verbose "Force new API key requested" $needNewKey = $true } elseif (-not $script:PSU_API_KEY) { Write-Verbose "No cached API key found" $needNewKey = $true } elseif ($script:PSU_API_KEY_EXPIRY -and ([DateTime]::UtcNow -gt $script:PSU_API_KEY_EXPIRY)) { Write-Verbose "Cached API key has expired" $needNewKey = $true } # Generate new API key if needed if ($needNewKey) { Write-Host "Generating API key..." -ForegroundColor Cyan try { $apiKey = New-PSUApiKey -ErrorAction Stop Write-Verbose "API key generated and cached successfully" } catch { throw "Failed to generate API key: $($_.Exception.Message)" } } else { Write-Verbose "Using cached API key" $apiKey = $script:PSU_API_KEY # Log time remaining if ($script:PSU_API_KEY_EXPIRY) { $timeLeft = $script:PSU_API_KEY_EXPIRY - [DateTime]::UtcNow Write-Verbose "API key expires in $($timeLeft.TotalHours.ToString('F1')) hours" } } } catch { Write-Error "API key preparation failed: $($_.Exception.Message)" throw } # ============================================ # 2. Prepare Request # ============================================ $body = @{ Prompt = $Prompt ReturnJsonResponse = [bool]$ReturnJsonResponse } | ConvertTo-Json -Depth 20 # Validate required headers are present if (-not $headers['psu-clientusername'] -or -not $headers['psu-clientdevice'] -or -not $headers['psu-clientip']) { throw "Missing required authentication metadata. Ensure New-PSUApiKey has been called successfully." } Write-Verbose "Request prepared with Authorization and custom headers" Write-Verbose "Username: $($headers['psu-clientusername'])" Write-Verbose "Device: $($headers['psu-clientdevice'])" Write-Verbose "IP: $($headers['psu-clientip'])" # ============================================ # 3. Retry Logic with API Key Refresh # ============================================ $maxAttempts = $RetryCount $attempt = 0 while ($attempt -lt $maxAttempts) { $attempt++ try { Write-Verbose "Attempt $attempt of $maxAttempts..." $invokeParams = @{ Method = 'Post' Uri = $ApiUrl Body = $body Headers = $headers TimeoutSec = $TimeoutSeconds ErrorAction = 'Stop' } Write-Verbose "Calling Gemini AI API (attempt $attempt)..." $response = Invoke-RestMethod @invokeParams Write-Verbose "Response received successfully" return $response } catch { $errorMessage = $_.Exception.Message $statusCode = 0 # Extract status code if available if ($_.Exception.Response) { $statusCode = $_.Exception.Response.StatusCode.value__ } # Try to parse error details from response body $errorDetails = $null if ($_.ErrorDetails.Message) { try { $errorDetails = $_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction Stop Write-Verbose "Error details received from API:" Write-Verbose ($errorDetails | ConvertTo-Json -Depth 5) # Handle specific error types if ($errorDetails.error -eq "Rate Limit Exceeded") { $resetTime = if ($errorDetails.quota.resetTime) { $errorDetails.quota.resetTime } else { "unknown" } $fullMessage = "Rate limit exceeded: $($errorDetails.message). Reset in: $resetTime" Write-Error $fullMessage throw $fullMessage } if ($statusCode -eq 401) { $fullMessage = "Authentication failed: $($errorDetails.message)" if ($errorDetails.correlationId) { $fullMessage += " (CorrelationID: $($errorDetails.correlationId))" } Write-Error $fullMessage throw $fullMessage } if ($statusCode -eq 400) { $fullMessage = "Bad request: $($errorDetails.message)" if ($errorDetails.correlationId) { $fullMessage += " (CorrelationID: $($errorDetails.correlationId))" } Write-Error $fullMessage throw $fullMessage } # Generic error with details - use the actual message from response $errorMessage = $errorDetails.message if ($errorDetails.correlationId) { $errorMessage += " (CorrelationID: $($errorDetails.correlationId))" } # For verbose output, show full error object Write-Verbose "Parsed error message: $errorMessage" } catch { Write-Verbose "Could not parse error details as JSON: $($_.Exception.Message)" Write-Verbose "Raw error body: $($_.ErrorDetails.Message)" } } # Last attempt - throw error with full details if ($attempt -ge $maxAttempts) { $finalError = "All $maxAttempts retry attempts failed. Last error: $errorMessage" # If we have error details, show them if ($errorDetails) { Write-Host "`nError Details:" -ForegroundColor Red Write-Host " Error: $($errorDetails.error)" -ForegroundColor Yellow Write-Host " Message: $($errorDetails.message)" -ForegroundColor Yellow if ($errorDetails.correlationId) { Write-Host " CorrelationID: $($errorDetails.correlationId)" -ForegroundColor Yellow } } Write-Error $finalError throw $finalError } # Retry with delay (unless it's an auth error or rate limit) if ($statusCode -eq 401 -or ($errorDetails -and $errorDetails.error -eq "Rate Limit Exceeded")) { throw $errorMessage } Write-Warning "Attempt $attempt failed (status: $statusCode): $errorMessage" Start-Sleep -Seconds $RetryDelaySeconds } } # Should never reach here if retry logic works correctly Write-Warning "Retry loop completed without success or failure" } end { Write-Verbose "=== Gemini AI API Call Complete ===" } } |