public/Invoke-AIToolSimple.ps1
|
function Invoke-AIToolSimple { <# .SYNOPSIS Sends a simple prompt to an AI tool and returns the response. .DESCRIPTION A simplified interface for AI tool invocation that doesn't require file processing. Designed for chatbot-style interactions, SQL analysis, and other prompt/response scenarios. Unlike Invoke-AITool which is optimized for batch file processing, this function: - Takes a prompt and returns the response directly - Supports optional system prompts for context - Handles credential setup automatically - Returns structured output with response text, duration, and token usage .PARAMETER Tool The AI tool to use. Defaults to Claude if not specified. .PARAMETER Prompt The prompt to send to the AI tool. .PARAMETER SystemPrompt Optional system prompt for context (prepended to the user prompt). .PARAMETER Model Optional model override for the AI tool. .PARAMETER OutputFormat Output format: json, text. Defaults to json. .PARAMETER AllowedTools Optional list of tools the AI can use (tool-specific whitelist). .PARAMETER CredentialPath Optional path to a credential/config file for token authentication. .PARAMETER MaxPromptLength Maximum prompt length before switching to file-based input. Default: 7000 characters (avoids Claude CLI empty output bug). .EXAMPLE Invoke-AIToolSimple -Prompt "Explain the concept of recursion" .EXAMPLE Invoke-AIToolSimple -Tool Claude -Prompt $sqlQuery -SystemPrompt "You are a SQL Server DBA" .EXAMPLE $result = Invoke-AIToolSimple -Prompt "What is 2+2?" $result.Response # "4" .OUTPUTS PSCustomObject with: - Success: Boolean indicating if the call succeeded - Response: The AI's response text - DurationMs: Time taken in milliseconds - TokenUsage: Token counts (if available from the tool) - Error: Error message if Success is false #> [CmdletBinding()] param( [Parameter()] [string]$Tool = 'Claude', [Parameter(Mandatory)] [string]$Prompt, [Parameter()] [string]$SystemPrompt, [Parameter()] [string]$Model, [Parameter()] [ValidateSet('json', 'text')] [string]$OutputFormat = 'json', [Parameter()] [string[]]$AllowedTools, [Parameter()] [string]$CredentialPath, [Parameter()] [int]$MaxPromptLength = 7000 ) $result = [PSCustomObject]@{ Success = $false Response = $null DurationMs = 0 TokenUsage = $null Error = $null } # Resolve tool alias to canonical name $Tool = Resolve-ToolAlias -ToolName $Tool # Get tool definition $toolDef = $script:ToolDefinitions[$Tool] if (-not $toolDef) { $result.Error = "Unknown AI tool: $Tool" return $result } # Verify tool is available if (-not (Test-Command -Command $toolDef.Command)) { $result.Error = "$Tool CLI is not installed. Run Install-AITool -Tool $Tool to install it." return $result } # Handle credential/token setup $credentialSetup = Get-AIToolCredential -Tool $Tool -CredentialPath $CredentialPath if (-not $credentialSetup.Configured) { $result.Error = "$Tool credentials not configured. Use Set-AIToolCredential to configure." return $result } foreach ($envVar in $credentialSetup.EnvironmentVariables.GetEnumerator()) { [Environment]::SetEnvironmentVariable($envVar.Key, $envVar.Value, 'Process') } # Build the full prompt $fullPrompt = $Prompt if ($SystemPrompt) { $fullPrompt = "$SystemPrompt`n`n$Prompt" } # Determine if we need file-based input for large prompts $usePromptFile = $fullPrompt.Length -gt $MaxPromptLength $promptFile = $null try { $startTime = Get-Date # Build arguments based on tool $arguments = switch ($Tool) { 'Claude' { # -p flag is REQUIRED for headless/non-interactive mode in containers $args = @('-p') if ($usePromptFile) { $promptFile = [System.IO.Path]::GetTempFileName() [System.IO.File]::WriteAllText($promptFile, $fullPrompt) # Keep -p for headless mode AND add --prompt-file for large prompts $args += '--prompt-file', $promptFile } else { $args += $fullPrompt } $args += '--output-format', $OutputFormat if ($Model) { $args += '--model', $Model } if ($AllowedTools -and $AllowedTools.Count -gt 0) { $args += '--allowedTools', ($AllowedTools -join ',') } $args } 'Gemini' { $args = @('-p') if ($usePromptFile) { $promptFile = [System.IO.Path]::GetTempFileName() [System.IO.File]::WriteAllText($promptFile, $fullPrompt) # Keep -p for headless mode AND add --prompt-file $args += '--prompt-file', $promptFile } else { $args += $fullPrompt } if ($Model) { $args += '--model', $Model } $args } 'Aider' { $args = @('--message', $fullPrompt, '--no-git', '--yes') if ($Model) { $args += '--model', $Model } $args } default { @('-p', $fullPrompt) } } # Execute the tool Write-PSFMessage -Level Verbose -Message "Executing $Tool with arguments: $($arguments -join ' ')" $rawOutput = & $toolDef.Command @arguments 2>&1 $exitCode = $LASTEXITCODE $duration = (Get-Date) - $startTime $result.DurationMs = [int]$duration.TotalMilliseconds if ($exitCode -ne 0) { $errorOutput = if ($rawOutput) { $rawOutput -join "`n" } else { "No output" } $result.Error = "$Tool CLI exited with code $exitCode`: $errorOutput" return $result } # Process output based on format $responseText = if ($rawOutput -is [array]) { $rawOutput -join "`n" } else { [string]$rawOutput } # Try to parse JSON output if ($OutputFormat -eq 'json' -and $responseText -match '^\s*\{') { try { $parsed = $responseText | ConvertFrom-Json $result.Response = $parsed.result if ($parsed.usage) { $result.TokenUsage = @{ InputTokens = $parsed.usage.input_tokens OutputTokens = $parsed.usage.output_tokens } } } catch { # JSON parsing failed, use raw response $result.Response = $responseText } } else { $result.Response = $responseText } $result.Success = $true } catch { $result.Error = $_.Exception.Message } finally { # Clean up temp file if ($promptFile -and (Test-Path $promptFile)) { Remove-Item $promptFile -Force -ErrorAction SilentlyContinue } } return $result } |