diff --git a/auto-setup.ps1 b/auto-setup.ps1 index 4abe302..d2df9f2 100644 --- a/auto-setup.ps1 +++ b/auto-setup.ps1 @@ -20,7 +20,7 @@ param( $ErrorActionPreference = "Stop" # ─── 版本号 (每次更新递增, build.ps1 自动读取) ────── -$BWVersion = "3.0.3" # +PS7 MSI 直链兜底 (Win10 无 winget 仍能装 pwsh, 避免跌落到 PS 5.1) +$BWVersion = "3.0.4" # fix: Key 验证模型兜底 + 默认模型反推 + Win11 指纹 wmic→CIM 三级 fallback # DryRun 模式日志标记 if ($DryRun) { $global:BWDryRun = $DryRun } else { $global:BWDryRun = $null } @@ -488,23 +488,24 @@ function Show-LicenseKeyDialog($attempt = 1, $maxAttempts = 3) { # 向中转站发一个最小请求验证 key 是否可用 function Test-ApiKey([string]$apiKey, [string]$baseUrl = "https://bww.letcareme.com") { + $script:LastValidatedModel = $null + $script:LastAuthFailCodes = @() if (-not $apiKey -or $apiKey.Length -lt 10) { return $false } - # 中转站/官方的模型命名差异大, 依次试候选列表, 任一 2xx/4xx(非认证) 即认 Key 有效 - # 顺序: 当前主流 (4-6/4-7) → 老版兼容 (4-5/3-5) - # 中转站 (bww.letcareme.com) 目前限定这 5 个模型, 严格匹配防 503 误判 - # 默认主模型: claude-opus-4-7 + # v3.0.4: 模型候选按中转站兼容性+套餐覆盖面排序 + # sonnet-4-6 放首位 - 中转站基础套餐都有, opus-4-7 可能被低档套餐 403 + # 去掉 haiku (多数中转站已移除), 新增 sonnet-4-5 兼容老套餐 $modelCandidates = @( + "claude-sonnet-4-6", "claude-opus-4-7", "claude-opus-4-6", "claude-opus-4-6-thinking", - "claude-sonnet-4-6", "claude-sonnet-4-6-thinking" ) - # v3.0.1 错误分类 (red-team-logic P0): - # $true = 认证通过 (200/400) - # $false = 认证失败 (401/403) 明确 Key 无效 + # 错误分类: + # $true = 认证通过 (200/400) — $script:LastValidatedModel 记录通过的 model + # $false = 认证失败 (全部候选都返 401/403) 明确 Key 无效或权限不足 # $null = 网络/中转站故障 (5xx/404/timeout), 非 Key 问题, 外层应放行 - $lastStatus = 0 + $authFailModels = @() $hadNetworkError = $false foreach ($model in $modelCandidates) { try { @@ -521,21 +522,27 @@ function Test-ApiKey([string]$apiKey, [string]$baseUrl = "https://bww.letcareme. $req.ContentLength = $bytes.Length $stream = $req.GetRequestStream(); $stream.Write($bytes, 0, $bytes.Length); $stream.Close() $resp = $req.GetResponse(); $resp.Close() + $script:LastValidatedModel = $model return $true # 200 = Key 有效 } catch [System.Net.WebException] { $lastStatus = 0 try { $lastStatus = [int]$_.Exception.Response.StatusCode } catch {} - # 401/403 = 明确认证失败, 立即返回 false - if ($lastStatus -eq 401 -or $lastStatus -eq 403) { return $false } + # 401/403 = 这个 model 维度的认证/权限失败, 继续试其他 model (v3.0.4 核心改动) + # 只有全部候选都 401/403 才认 Key 无效; 任一 200/400 就算 Key 有效 + if ($lastStatus -eq 401 -or $lastStatus -eq 403) { + $authFailModels += "$model=$lastStatus" + continue + } # 400 = 请求体问题 (模型名等), Key 本身通过 - if ($lastStatus -eq 400) { return $true } + if ($lastStatus -eq 400) { $script:LastValidatedModel = $model; return $true } # 5xx/404/0 = 网络或中转站故障, 不能归咎 Key $hadNetworkError = $true continue } catch { $hadNetworkError = $true; continue } } + $script:LastAuthFailCodes = $authFailModels # 全部候选都遇网络故障 → 返回 $null (外层应: 接受 Key, 首次真实请求时再判) - if ($hadNetworkError) { return $null } + if ($hadNetworkError -and $authFailModels.Count -eq 0) { return $null } return $false } @@ -1299,17 +1306,18 @@ if (-not $secretsDecrypted -and (Get-CachedSecrets)) { $secretsDecrypted = $true } -# v3.0.1: 强制设默认模型 = claude-opus-4-7 (中转站支持, Claude Code 2.0.1 默认 4-5 会 503) -# 顺序: 已显式设置 > Worker /config 拉取 > fallback 硬编码 +# v3.0.4: 默认模型优先级重排(修复: opus-4-7 硬编码导致 sonnet-only 套餐用户启动后全 403) +# 顺序: 已显式设置 > Test-ApiKey 实际通过的 model (延后设) > Worker /config > 兜底 sonnet-4-6 +# 注: 实际验证通过后会在 Phase 4 末尾用 $script:LastValidatedModel 覆盖为"真·可用模型" if (-not $env:ANTHROPIC_MODEL) { - $defaultModel = "claude-opus-4-7" + $defaultModel = "claude-sonnet-4-6" # v3.0.4: 兜底改 sonnet-4-6 (基础套餐都有, opus 常被低档 403) try { $cfg = Invoke-RestMethod "https://bookworm-router.bookworm-api.workers.dev/config" -TimeoutSec 8 -ErrorAction Stop if ($cfg.default_model) { $defaultModel = $cfg.default_model } } catch {} [System.Environment]::SetEnvironmentVariable("ANTHROPIC_MODEL", $defaultModel, "User") $env:ANTHROPIC_MODEL = $defaultModel - Log-OK "ANTHROPIC_MODEL=$defaultModel (中转站兼容)" + Log-OK "ANTHROPIC_MODEL=$defaultModel (暂设, Phase 4 验证通过后会根据实际可用 model 覆盖)" } # 优先级 3: 直接输入中转站 API Key (v2.3 新增) if (-not $secretsDecrypted) { @@ -1330,30 +1338,40 @@ if (-not $secretsDecrypted) { continue } Log-Info "正在验证..." - # P1-2: 统一调用 change-key.js (stdin 管道, 无 argv 泄露) - $injectJs = Join-Path $ClaudeDir "change-key.js" + # v3.0.4: 统一走 Test-ApiKey (移除 change-key.js 优先分支) + # 删除原因: change-key.js 硬编码 claude-3-haiku-20240307 做验证, + # sonnet-only 套餐用户的 Key 在 haiku 必返 403 → 误判 Key 无效, + # 而 Test-ApiKey 本来就支持多模型 fallback + 三值错误分类, 覆盖更广. + # 同步修复: stderr 不再 2>&1 | Out-Null, 改为记录到 phase4-validate.log 便于用户报障 + # 残留清理: 如 .claude/change-key.js 存在, 保留但不再调用 (未来版本会彻底移除) $ckOk = $false - if ((Test-Path $injectJs) -and (Test-Cmd "node")) { - try { - $apiKey | & node $injectJs 2>&1 | Out-Null - $ckOk = ($LASTEXITCODE -eq 0) - } catch { $ckOk = $false } + $checkResult = Test-ApiKey $apiKey + $phase4Log = Join-Path $env:TEMP "bw-phase4-validate.log" + if ($checkResult -eq $true) { + $ckOk = $true + $validatedModel = if ($script:LastValidatedModel) { $script:LastValidatedModel } else { "(unknown)" } + Log-OK "Key 在线验证通过 (model=$validatedModel)" + try { "[$([DateTime]::Now.ToString('s'))] PASS model=$validatedModel" | Out-File -FilePath $phase4Log -Append -Encoding UTF8 } catch {} + } elseif ($null -eq $checkResult) { + # 全部候选网络故障, 不能归咎 Key, 先接受 (首次真实请求时再判) + $ckOk = $true + Log-Warn "中转站暂不可达, 暂接受此 Key (首次使用时若失败请重装换 Key)" + try { "[$([DateTime]::Now.ToString('s'))] NETWORK_ACCEPT baseUrl=https://bww.letcareme.com" | Out-File -FilePath $phase4Log -Append -Encoding UTF8 } catch {} } else { - # v3.0.1: Test-ApiKey 三值返回 (true=有效, false=认证失败, null=网络故障放行) - $checkResult = Test-ApiKey $apiKey - if ($checkResult -eq $true) { - $ckOk = $true - Log-OK "Key 在线验证通过" - } elseif ($null -eq $checkResult) { - # 网络故障, 不能归咎 Key, 先接受 (首次真实请求时再判) - $ckOk = $true - Log-Warn "中转站暂不可达, 暂接受此 Key (首次使用时若失败请重装换 Key)" - } else { - $ckOk = $false # 明确认证失败 - } - if ($ckOk) { - [System.Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", $apiKey, "User") - [System.Environment]::SetEnvironmentVariable("ANTHROPIC_BASE_URL", "https://bww.letcareme.com", "User") + # 全部候选都 401/403 = 明确认证失败 + $ckOk = $false + $failDetail = if ($script:LastAuthFailCodes) { $script:LastAuthFailCodes -join ',' } else { 'all_models_auth_fail' } + Log-Warn "Key 被拒绝 ($failDetail)" + try { "[$([DateTime]::Now.ToString('s'))] AUTH_FAIL $failDetail" | Out-File -FilePath $phase4Log -Append -Encoding UTF8 } catch {} + } + if ($ckOk) { + [System.Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", $apiKey, "User") + [System.Environment]::SetEnvironmentVariable("ANTHROPIC_BASE_URL", "https://bww.letcareme.com", "User") + # v3.0.4: 如果 Test-ApiKey 返回了真实通过的模型, 用它覆盖先前设的兜底默认 + if ($script:LastValidatedModel) { + [System.Environment]::SetEnvironmentVariable("ANTHROPIC_MODEL", $script:LastValidatedModel, "User") + $env:ANTHROPIC_MODEL = $script:LastValidatedModel + Log-OK "ANTHROPIC_MODEL 锁定为实际可用: $($script:LastValidatedModel)" } } if ($ckOk) {