Private/Helpers.ps1
|
# Private helper functions for PowerCraft.AI #region Provider Configuration $script:Providers = @{ openai = @{ ChatEndpoint = 'https://api.openai.com/v1/chat/completions' ResponsesEndpoint = 'https://api.openai.com/v1/responses' ModelsEndpoint = 'https://api.openai.com/v1/models' SecretName = 'openai-api-key' DefaultModel = 'gpt-4o' } gemini = @{ ChatEndpoint = 'https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent' ModelsEndpoint = 'https://generativelanguage.googleapis.com/v1beta/models' SecretName = 'gemini-api-key' DefaultModel = 'gemini-2.5-flash' } anthropic = @{ MessagesEndpoint = 'https://api.anthropic.com/v1/messages' SecretName = 'anthropic-api-key' DefaultModel = 'claude-sonnet-4-20250514' } } #endregion #region Key Resolution function Resolve-PCApiKey { [CmdletBinding()] param( [Parameter(Mandatory)][string]$Provider ) $secretName = $script:Providers[$Provider].SecretName # Try PowerCraft.Secrets first if (Get-Command Get-PCSecret -ErrorAction SilentlyContinue) { $key = Get-PCSecret -Name $secretName -ErrorAction SilentlyContinue if ($key) { return $key } } # Fallback to environment variables $envMap = @{ openai = @('OPENAI_API_KEY') gemini = @('GEMINI_API_KEY', 'GOOGLE_API_KEY') anthropic = @('ANTHROPIC_API_KEY') } foreach ($envVar in $envMap[$Provider]) { $val = [Environment]::GetEnvironmentVariable($envVar) if ($val) { return $val } } return $null } #endregion #region Provider Request Functions function Invoke-PCOpenAIRequest { [CmdletBinding()] param( [Parameter(Mandatory)][string]$ApiKey, [Parameter(Mandatory)][string]$Model, [Parameter(Mandatory)][array]$Messages, [int]$MaxTokens = 4096, [double]$Temperature = 0.3, [switch]$JsonMode, [switch]$WebSearch, [int]$TimeoutSec = 120 ) $headers = @{ 'Authorization' = "Bearer $ApiKey" 'Content-Type' = 'application/json' } $isResponsesAPI = $Model -match '^(gpt-5|o[1-9])' if ($isResponsesAPI) { # Responses API for reasoning models $systemMsg = ($Messages | Where-Object { $_.role -eq 'system' } | ForEach-Object { $_.content }) -join "`n" $userMsg = ($Messages | Where-Object { $_.role -eq 'user' } | ForEach-Object { $_.content }) -join "`n" $combinedInput = if ($systemMsg) { "$systemMsg`n`n$userMsg" } else { $userMsg } $body = @{ model = $Model input = $combinedInput } if ($WebSearch) { $body.tools = @(@{ type = 'web_search_preview' }) } if ($JsonMode -and -not $WebSearch) { $body.text = @{ format = @{ type = 'json_object' } } } $response = Invoke-RestMethod ` -Uri $script:Providers.openai.ResponsesEndpoint ` -Method POST ` -Headers $headers ` -Body ($body | ConvertTo-Json -Depth 20) ` -TimeoutSec $TimeoutSec $messageOutput = $response.output | Where-Object { $_.type -eq 'message' } | Select-Object -First 1 if ($messageOutput -and $messageOutput.content) { $textContent = $messageOutput.content | Where-Object { $_.type -eq 'output_text' } | Select-Object -First 1 if ($textContent) { return $textContent.text.Trim() } } throw 'No output_text found in OpenAI Responses API response' } else { # Standard Chat Completions API $body = @{ model = $Model messages = $Messages max_tokens = $MaxTokens temperature = $Temperature } if ($JsonMode) { $body.response_format = @{ type = 'json_object' } } $response = Invoke-RestMethod ` -Uri $script:Providers.openai.ChatEndpoint ` -Method POST ` -Headers $headers ` -Body ($body | ConvertTo-Json -Depth 20) ` -TimeoutSec $TimeoutSec return $response.choices[0].message.content.Trim() } } function Invoke-PCGeminiRequest { [CmdletBinding()] param( [Parameter(Mandatory)][string]$ApiKey, [Parameter(Mandatory)][string]$Model, [Parameter(Mandatory)][array]$Messages, [int]$MaxTokens = 4096, [double]$Temperature = 0.3, [switch]$JsonMode, [int]$TimeoutSec = 120 ) $endpoint = $script:Providers.gemini.ChatEndpoint -replace '\{model\}', $Model $uri = "${endpoint}?key=$ApiKey" $systemInstruction = $null $contents = @() foreach ($msg in $Messages) { if ($msg.role -eq 'system') { $systemInstruction = @{ parts = @(@{ text = $msg.content }) } } elseif ($msg.role -eq 'user') { $contents += @{ role = 'user'; parts = @(@{ text = $msg.content }) } } elseif ($msg.role -eq 'assistant') { $contents += @{ role = 'model'; parts = @(@{ text = $msg.content }) } } } $body = @{ contents = $contents generationConfig = @{ temperature = $Temperature maxOutputTokens = $MaxTokens } } if ($systemInstruction) { $body.systemInstruction = $systemInstruction } if ($JsonMode) { $body.generationConfig.responseMimeType = 'application/json' } $response = Invoke-RestMethod ` -Uri $uri ` -Method POST ` -Headers @{ 'Content-Type' = 'application/json' } ` -Body ($body | ConvertTo-Json -Depth 20) ` -TimeoutSec $TimeoutSec if ($response.candidates -and $response.candidates[0].content.parts) { $text = ($response.candidates[0].content.parts | ForEach-Object { $_.text }) -join '' return $text.Trim() } throw 'No content found in Gemini response' } function Invoke-PCAnthropicRequest { [CmdletBinding()] param( [Parameter(Mandatory)][string]$ApiKey, [Parameter(Mandatory)][string]$Model, [Parameter(Mandatory)][array]$Messages, [int]$MaxTokens = 4096, [double]$Temperature = 0.3, [switch]$JsonMode, [int]$TimeoutSec = 120 ) $headers = @{ 'x-api-key' = $ApiKey 'anthropic-version' = '2023-06-01' 'Content-Type' = 'application/json' } $systemContent = $null $apiMessages = @() foreach ($msg in $Messages) { if ($msg.role -eq 'system') { $systemContent = $msg.content } else { $apiMessages += @{ role = $msg.role; content = $msg.content } } } $body = @{ model = $Model max_tokens = $MaxTokens messages = $apiMessages } if ($systemContent) { $body.system = $systemContent } if ($Temperature -ne 1.0) { $body.temperature = $Temperature } $response = Invoke-RestMethod ` -Uri $script:Providers.anthropic.MessagesEndpoint ` -Method POST ` -Headers $headers ` -Body ($body | ConvertTo-Json -Depth 20) ` -TimeoutSec $TimeoutSec $textBlocks = $response.content | Where-Object { $_.type -eq 'text' } if ($textBlocks) { return ($textBlocks | ForEach-Object { $_.text }) -join '' } throw 'No text content found in Anthropic response' } #endregion |