Public/Invoke-PCCompletionCached.ps1
|
function Invoke-PCCompletionCached { <# .SYNOPSIS Send a prompt to an AI provider with file-based response caching. .DESCRIPTION Same as Invoke-PCCompletion but caches responses to disk. Cache hits return instantly without making API calls. .EXAMPLE Invoke-PCCompletionCached -UserPrompt "Analyze this" -CachePath ./cache -CacheKey "item-001" #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$UserPrompt, [Parameter(Mandatory)] [string]$CachePath, [Parameter(Mandatory)] [string]$CacheKey, [string]$SystemPrompt, [ValidateSet('openai', 'gemini', 'anthropic')] [string]$Provider, [string]$Model, [int]$MaxTokens = 4096, [ValidateRange(0.0, 2.0)] [double]$Temperature = 0.3, [switch]$JsonMode, [switch]$WebSearch, [int]$TimeoutSec = 120, [int]$CacheTTLHours = -1 ) # Ensure cache directory exists if (-not (Test-Path $CachePath)) { New-Item -ItemType Directory -Path $CachePath -Force | Out-Null } # Sanitize cache key for filename $safeKey = $CacheKey -replace '[^\w\-\.]', '_' $cacheFile = Join-Path $CachePath "$safeKey.json" # Check cache if (Test-Path $cacheFile) { $cached = Get-Content $cacheFile -Raw | ConvertFrom-Json if ($cached.schema -eq 'ai-response-cache') { # Check TTL if ($CacheTTLHours -gt 0) { $cachedTime = [datetime]$cached.metadata.timestamp $age = (Get-Date) - $cachedTime if ($age.TotalHours -lt $CacheTTLHours) { Write-Verbose "Cache HIT: $CacheKey (age: $([int]$age.TotalMinutes) min)" return $cached.raw_response } Write-Verbose "Cache EXPIRED: $CacheKey (age: $([int]$age.TotalHours) hours)" } else { Write-Verbose "Cache HIT: $CacheKey (no TTL)" return $cached.raw_response } } } Write-Verbose "Cache MISS: $CacheKey — calling provider" # Build params for Invoke-PCCompletion $splat = @{ UserPrompt = $UserPrompt MaxTokens = $MaxTokens Temperature = $Temperature TimeoutSec = $TimeoutSec } if ($SystemPrompt) { $splat['SystemPrompt'] = $SystemPrompt } if ($Provider) { $splat['Provider'] = $Provider } if ($Model) { $splat['Model'] = $Model } if ($JsonMode) { $splat['JsonMode'] = $true } if ($WebSearch) { $splat['WebSearch'] = $true } $startTime = Get-Date $response = Invoke-PCCompletion @splat $duration = ((Get-Date) - $startTime).TotalMilliseconds # Resolve actual provider/model for metadata $actualProvider = if ($Provider) { $Provider } elseif ($Model -match '^gemini') { 'gemini' } elseif ($Model -match '^claude') { 'anthropic' } else { 'openai' } $actualModel = if ($Model) { $Model } else { $script:Providers[$actualProvider].DefaultModel } # Write cache $cacheObj = @{ schema = 'ai-response-cache' version = '1.0' cache_key = $CacheKey metadata = @{ model = $actualModel provider = $actualProvider timestamp = (Get-Date).ToString('o') duration_ms = [int]$duration max_tokens = $MaxTokens response_length = $response.Length } raw_response = $response } $cacheObj | ConvertTo-Json -Depth 5 | Set-Content -Path $cacheFile -Encoding UTF8 Write-Verbose "Cached response: $cacheFile ($($response.Length) chars)" $response } |