Public/Translation/Set-TranslationProvider.ps1

function Set-TranslationProvider {
    <#
    .SYNOPSIS
        Activates a saved provider, or saves and activates a new/updated provider config.
    .DESCRIPTION
        Calling with only -Name activates an already-saved provider as the default.
        Passing additional parameters creates or updates that provider's saved config.
        API keys are encrypted with Windows DPAPI (CurrentUser) and stored in:
          %APPDATA%\SubtitleTools\providers.json
        No vault or password is required.
    .PARAMETER Name
        Provider name: OpenAI, Anthropic, Google, or OpenRouter.
    .PARAMETER Model
        Model ID. Defaults to the recommended model for each provider.
    .PARAMETER ApiKeyPlainText
        API key as plain text (encrypted before saving).
    .PARAMETER ApiKey
        API key as SecureString (encrypted before saving).
    .PARAMETER BaseUrl
        Custom API endpoint (for proxies or alternative endpoints).
    .PARAMETER RateLimitRpm
        Maximum requests per minute.
    .PARAMETER MaxTokensPerBatch
        Client-side input batching budget: how many subtitle entries get packed into one
        outbound API call, estimated as tokens x ~4 chars/token. This does NOT limit the
        API's output/response size - see -MaxOutputTokens for that.
    .PARAMETER MaxEntriesPerBatch
        Hard ceiling on how many subtitle entries go into a single API call, applied on top
        of -MaxTokensPerBatch. This is the setting that bounds how much the model has to
        WRITE per call: a short file can easily fit under the character budget in one batch,
        and asking a model for hundreds of translated lines in one response invites both
        truncation and line-numbering drift. Lower it for verbose target languages or
        reasoning models; raise it for fewer, larger calls. Default: 40.
    .PARAMETER MaxOutputTokens
        Maximum tokens the provider is allowed to generate in its response (sent to the API
        as e.g. Anthropic's max_tokens). This is the output cap, separate from
        -MaxTokensPerBatch (which only controls input batching). Default: 16384. Note that
        on reasoning models the thinking tokens are billed against this same budget, so a
        value sized only for the visible answer will truncate the response.
    .PARAMETER Temperature
        Sampling temperature (0.0-1.0).
    .EXAMPLE
        # First-time setup
        Set-TranslationProvider -Name Google -ApiKeyPlainText 'AIza...'
    .EXAMPLE
        # Switch to a previously saved provider
        Set-TranslationProvider -Name Anthropic
    .EXAMPLE
        # Update just the model for an existing provider
        Set-TranslationProvider -Name Google -Model 'gemini-1.5-pro'
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateSet('OpenAI', 'Anthropic', 'Google', 'OpenRouter')]
        [string] $Name,

        [string]      $Model,
        [string]      $ApiKeyPlainText,
        [SecureString] $ApiKey,
        [string]      $BaseUrl,
        [int]         $RateLimitRpm,
        [int]         $MaxTokensPerBatch,
        [ValidateRange(1, 500)]
        [int]         $MaxEntriesPerBatch,
        [int]         $MaxOutputTokens,
        [decimal]     $Temperature
    )

    $configParams = 'Model','ApiKeyPlainText','ApiKey','BaseUrl','RateLimitRpm','MaxTokensPerBatch','MaxEntriesPerBatch','Temperature','MaxOutputTokens'
    $isUpdate = @($configParams | Where-Object { $PSBoundParameters.ContainsKey($_) }).Count -gt 0

    if ($isUpdate) {
        # Load existing saved config, or seed from defaults for a new provider
        if ($script:ConfiguredProviders.ContainsKey($Name)) {
            $provider = $script:ConfiguredProviders[$Name]
        } else {
            $d = $script:ProviderDefaults.$Name
            if (-not $d) { throw "No defaults found for provider '$Name'." }
            $provider                    = [TranslationProvider]::new()
            $provider.Name               = $Name
            $provider.Model              = $d.Model
            $provider.BaseUrl            = $d.BaseUrl
            $provider.RateLimitRpm       = $d.RateLimitRpm
            $provider.MaxTokensPerBatch  = $d.MaxTokensPerBatch
            $provider.MaxEntriesPerBatch = if ($d.MaxEntriesPerBatch) { $d.MaxEntriesPerBatch } else { 40 }
            $provider.MaxOutputTokens    = if ($d.MaxOutputTokens) { $d.MaxOutputTokens } else { 16384 }
            $provider.Temperature        = $d.Temperature
        }

        # Apply only the params that were explicitly supplied
        if ($PSBoundParameters.ContainsKey('Model'))             { $provider.Model             = $Model }
        if ($PSBoundParameters.ContainsKey('BaseUrl'))           { $provider.BaseUrl           = $BaseUrl }
        if ($PSBoundParameters.ContainsKey('RateLimitRpm'))      { $provider.RateLimitRpm      = $RateLimitRpm }
        if ($PSBoundParameters.ContainsKey('MaxTokensPerBatch'))  { $provider.MaxTokensPerBatch  = $MaxTokensPerBatch }
        if ($PSBoundParameters.ContainsKey('MaxEntriesPerBatch')) { $provider.MaxEntriesPerBatch = $MaxEntriesPerBatch }
        if ($PSBoundParameters.ContainsKey('MaxOutputTokens'))    { $provider.MaxOutputTokens    = $MaxOutputTokens }
        if ($PSBoundParameters.ContainsKey('Temperature'))       { $provider.Temperature       = $Temperature }

        # Encrypt and store API key if supplied
        $plainKey = $null
        if ($ApiKeyPlainText) {
            $plainKey = $ApiKeyPlainText
        } elseif ($ApiKey) {
            $plainKey = [System.Net.NetworkCredential]::new('', $ApiKey).Password
        }
        if ($plainKey) {
            $provider.ApiKeyEncrypted = Protect-ApiKey -PlainText $plainKey
            Write-Verbose "API key encrypted and saved for '$Name'."
        }

        $script:ConfiguredProviders[$Name] = $provider
    } else {
        # Activate-only: provider must already be saved
        if (-not $script:ConfiguredProviders.ContainsKey($Name)) {
            throw "Provider '$Name' has no saved configuration. Run Set-TranslationProvider -Name $Name -ApiKeyPlainText 'your-key' to set it up first."
        }
    }

    $script:DefaultProvider = $Name
    Save-ProviderStore
    Write-Verbose "Provider '$Name' is now active."
}