@@ -1147,6 +1147,9 @@ function Get-AiProbeModel {
11471147 [Parameter (Mandatory = $true )]$Profile
11481148 )
11491149
1150+ if ((Get-AiProfileMode - Profile $Profile ) -ne " api" ) {
1151+ return " -"
1152+ }
11501153 $explicit = Get-AiProperty - Object $Profile - Name " probe_model"
11511154 if ($explicit ) {
11521155 return [string ]$explicit
@@ -1460,7 +1463,57 @@ function Test-AiProbeModelUnsupported {
14601463 param ([string ]$Detail )
14611464 if (-not $Detail ) { return $false }
14621465 $l = $Detail.ToLowerInvariant ()
1463- return ($l -match " no available providers|model_not_found|model not found|model does not exist|unknown model|unsupported model|model .*not supported|not support.*model|invalid model|model_not_supported" )
1466+ return ($l -match " no available providers|model_not_found|model not found|model does not exist|unknown model|unsupported model|model .*not supported|not support.*model|invalid model|model_not_supported|模型不存在|模型.*不存在|请检查模型代码" )
1467+ }
1468+
1469+ function ConvertTo-AiHealthDisplayError {
1470+ param ([string ]$Detail )
1471+ if (-not $Detail ) { return " " }
1472+ $text = ($Detail -replace " \s+" , " " ).Trim()
1473+ function ShortProbeDetail ([string ]$Item ) {
1474+ $itemText = ($Item -replace " \s+" , " " ).Trim()
1475+ if (Test-AiProbeModelUnsupported $itemText ) {
1476+ return " probe model unsupported; set probe_model"
1477+ }
1478+ if ($itemText -match " ^(HTTP \d{3})(?:\s+(.+))?$" ) {
1479+ $code = $Matches [1 ]
1480+ $body = if ($Matches.Count -gt 2 ) { $Matches [2 ] } else { " " }
1481+ if (-not $body ) { return $code }
1482+ try {
1483+ $json = $body | ConvertFrom-Json - ErrorAction Stop
1484+ $msg = $null
1485+ if ($json.error -and $json.error.message ) { $msg = [string ]$json.error.message }
1486+ elseif ($json.message ) { $msg = [string ]$json.message }
1487+ elseif ($json.error ) { $msg = [string ]$json.error }
1488+ elseif ($json.type ) { $msg = [string ]$json.type }
1489+ if ($msg ) { return ($code + " " + (($msg -replace " \s+" , " " ).Trim())) }
1490+ } catch { }
1491+ if ($body -match ' "message"\s*:\s*"([^"]+)"' ) {
1492+ return ($code + " " + (($Matches [1 ] -replace " \s+" , " " ).Trim()))
1493+ }
1494+ return $itemText
1495+ }
1496+ return $itemText
1497+ }
1498+ if ($text -match " ^(POST\s+/\S+\s+->\s+)(.+?)(;\s+/\S+\s+->\s+)(.+)$" ) {
1499+ $prefix = $Matches [1 ]
1500+ $first = $Matches [2 ]
1501+ $middle = $Matches [3 ]
1502+ $second = $Matches [4 ]
1503+ return $prefix + (ShortProbeDetail $first ) + $middle + (ShortProbeDetail $second )
1504+ }
1505+ if ($text -match " ^(POST\s+/\S+\s+->\s+)(.+?)(;\s+but\s+/\S+\s+works\s+->\s+.+)$" ) {
1506+ $prefix = $Matches [1 ]
1507+ $first = $Matches [2 ]
1508+ $suffix = $Matches [3 ]
1509+ return $prefix + (ShortProbeDetail $first ) + $suffix
1510+ }
1511+ if ($text -match " ^(POST\s+/\S+\s+)(.+)$" ) {
1512+ $prefix = $Matches [1 ]
1513+ $detail = $Matches [2 ]
1514+ return $prefix + (ShortProbeDetail $detail )
1515+ }
1516+ return ShortProbeDetail $text
14641517}
14651518
14661519function Resolve-AiProfileHealth {
@@ -1472,10 +1525,10 @@ function Resolve-AiProfileHealth {
14721525 $st = if ($m.LatencyMs -gt $DegradedMs ) { " degraded" } else { " healthy" }
14731526 return [pscustomobject ]@ { Status = $st ; LatencyMs = $m.LatencyMs ; Method = " generation" ; Error = $null }
14741527 }
1475- $md = ConvertTo-AiProbeError $m.Detail
14761528 if (Test-AiProbeModelUnsupported $m.Detail ) {
1477- return [pscustomobject ]@ { Status = " degraded" ; LatencyMs = $m.LatencyMs ; Method = " none" ; Error = (" POST /v1/messages " + $md ) }
1529+ return [pscustomobject ]@ { Status = " degraded" ; LatencyMs = $m.LatencyMs ; Method = " none" ; Error = (" POST /v1/messages " + $m .Detail ) }
14781530 }
1531+ $md = ConvertTo-AiProbeError $m.Detail
14791532 if ($m.Code -eq 429 -or ($m.Code -ge 500 -and $m.Code -lt 600 )) {
14801533 return [pscustomobject ]@ { Status = " degraded" ; LatencyMs = $m.LatencyMs ; Method = " none" ; Error = (" POST /v1/messages " + $md + " (transient)" ) }
14811534 }
@@ -1491,15 +1544,16 @@ function Resolve-AiProfileHealth {
14911544 }
14921545 $effD = ConvertTo-AiProbeError $eff.Detail
14931546 $altD = ConvertTo-AiProbeError $alt.Detail
1494- $note = " POST /$ ( $Plan.EffLabel ) -> $effD "
1547+ $note = " POST /$ ( $Plan.EffLabel ) -> $ ( $eff .Detail ) "
14951548 if ($alt.Ok ) {
14961549 $note += " ; but /$ ( $Plan.AltLabel ) works -> set wire_api = `" $ ( $Plan.AltLabel ) `" in config.toml"
14971550 return [pscustomobject ]@ { Status = " degraded" ; LatencyMs = $eff.LatencyMs ; Method = " none" ; Error = $note }
14981551 }
14991552 if ((Test-AiProbeModelUnsupported $eff.Detail ) -or (Test-AiProbeModelUnsupported $alt.Detail )) {
1500- $note += " ; /$ ( $Plan.AltLabel ) -> $altD "
1553+ $note += " ; /$ ( $Plan.AltLabel ) -> $ ( $alt .Detail ) "
15011554 return [pscustomobject ]@ { Status = " degraded" ; LatencyMs = $eff.LatencyMs ; Method = " none" ; Error = $note }
15021555 }
1556+ $note = " POST /$ ( $Plan.EffLabel ) -> $effD "
15031557 if ($eff.Code -eq 429 -or ($eff.Code -ge 500 -and $eff.Code -lt 600 )) {
15041558 $note += " ; /$ ( $Plan.AltLabel ) -> $altD (transient)"
15051559 return [pscustomobject ]@ { Status = " degraded" ; LatencyMs = $eff.LatencyMs ; Method = " none" ; Error = $note }
@@ -2041,6 +2095,7 @@ function Write-CodexSwitchStatus {
20412095 Write-Host " Profile: <default> ($profilePath missing)"
20422096 }
20432097 Write-Host " Base URL: $ ( Get-CodexBaseUrl - Profile $Profile ) "
2098+ Write-Host " Probe model: $ ( Get-AiProbeModel - Tool codex - Profile $Profile ) "
20442099 Write-Host " Cached login: $ ( Get-CodexLoginStatusText ) "
20452100
20462101 if ($mode -eq " api" ) {
@@ -2069,6 +2124,7 @@ function Write-ClaudeSwitchStatus {
20692124 Write-Host " Claude Code state switched: $ ( Get-AiProfileName - Profile $Profile ) "
20702125 Write-Host " Run next: claude"
20712126 Write-Host " Registry: $script :AiRegistryPath "
2127+ Write-Host " Probe model: $ ( Get-AiProbeModel - Tool claude - Profile $Profile ) "
20722128 if ($mode -eq " api" ) {
20732129 Write-Host " ANTHROPIC_BASE_URL: $env: ANTHROPIC_BASE_URL "
20742130 Write-Host " ANTHROPIC_API_KEY: $ ( Format-AiSecretPreview $env: ANTHROPIC_API_KEY ) "
@@ -2080,6 +2136,8 @@ function Write-ClaudeSwitchStatus {
20802136 Write-Host " Subscription status: local Claude login is used if present"
20812137 }
20822138
2139+ $h = Get-AiProfileHealthCached - Tool " claude" - Profile $Profile - CacheOnly
2140+ Write-Host (" Health: " + (Format-AiHealthCell $h ) + $ (if ($h.Error ) { " " + $h.Error } else { ' ' }))
20832141 Write-ClaudeExternalStatus
20842142}
20852143
@@ -2088,13 +2146,13 @@ function Show-CxHelp {
20882146cx - switch Codex state for this PowerShell session
20892147
20902148Usage:
2091- cx Cycle through enabled Codex profiles
2149+ cx Auto-select a cached healthy Codex profile (default fallback)
20922150 cx sub Use a named subscription profile
20932151 cx sub:work Use another subscription profile, if registered
20942152 cx api Use the default API profile
20952153 cx api:docker Use a named API profile
2096- cx list List registry profiles and local file status
2097- cx status Print current saved/process state; --refresh re-probes health
2154+ cx list List registry profiles and cached health
2155+ cx status Print current saved/process state; --fresh/-- refresh re-probes selected profile
20982156 cx stats Summarize local rollout token usage
20992157 cx add-api NAME Register a Codex API profile that shares ~/.codex by default
21002158 Options: --base-url URL --env-key NAME --provider-name NAME
@@ -2103,11 +2161,11 @@ Usage:
21032161 cx add-sub NAME Register an isolated Codex subscription CODEX_HOME
21042162 cx remove NAME Remove a Codex profile registration
21052163 cx probe-model NAME [MODEL] Set/clear health-probe model override
2106- cx default [NAME] Show/set the default (primary) profile
2107- cx edit Open the profile registry (profiles.json) in $ EDITOR
2108- cx health Probe & report profile health (🟢🟡🔴, parallel); --fresh re-probes
2109- cx doctor Run codex doctor full diagnostic (slow, on-demand)
2110- cx health-clear Clear the health probe cache
2164+ cx default [NAME] Show/set the default (primary) profile
2165+ cx edit Open the profile registry (profiles.json) in EDITOR
2166+ cx health Probe & report profile health table (🟢🟡🔴, parallel); --fresh/--refresh re-probes
2167+ cx doctor Run codex doctor full diagnostic (slow, on-demand)
2168+ cx health-clear Clear the health probe cache
21112169 cx next Cycle to the next enabled profile
21122170 cx help Show this help
21132171
@@ -2126,6 +2184,7 @@ Notes:
21262184 API mode does not run codex login --with-api-key; it loads OPENAI_API_KEY only for this shell.
21272185 Multiple API profiles can share ~/.codex. Multiple subscription accounts need separate home values.
21282186 Add commands only write profile metadata and Codex config. Put real tokens in secrets.toml.
2187+ Without probe_model, Codex probes use runtime/global config.toml model, then a cheap fallback.
21292188 Legacy ~/.ai-secrets/*.ps1 files are still accepted as a fallback.
21302189"@ | Write-Host
21312190}
@@ -2135,23 +2194,23 @@ function Show-CcHelp {
21352194cc - switch Claude Code state for this PowerShell session
21362195
21372196Usage:
2138- cc Cycle through enabled Claude Code profiles
2197+ cc Auto-select a cached healthy Claude Code profile (default fallback)
21392198 cc sub Clear Anthropic API env and use local Claude subscription login
21402199 cc sub:work Use another subscription profile, if registered
21412200 cc api Use the default API profile
21422201 cc api:docker Use a named API profile
2143- cc list List registry profiles and local file status
2144- cc status Print current saved/process state; --refresh re-probes health
2202+ cc list List registry profiles and cached health
2203+ cc status Print current saved/process state; --fresh/-- refresh re-probes selected profile
21452204 cc add-api NAME Register a Claude Code API profile
21462205 Options: --base-url URL --env-key NAME --env KEY=VALUE (repeatable)
21472206 Prompts for missing base-url and the secret in a terminal.
21482207 cc add-sub NAME Register a Claude Code subscription label
21492208 cc remove NAME Remove a Claude Code profile registration
21502209 cc probe-model NAME [MODEL] Set/clear health-probe model override
2151- cc default [NAME] Show/set the default (primary) profile
2152- cc edit Open the profile registry (profiles.json) in $ EDITOR
2153- cc health Probe & report profile health (🟢🟡🔴, parallel); --fresh re-probes
2154- cc health-clear Clear the health probe cache
2210+ cc default [NAME] Show/set the default (primary) profile
2211+ cc edit Open the profile registry (profiles.json) in EDITOR
2212+ cc health Probe & report profile health table (🟢🟡🔴, parallel); --fresh/--refresh re-probes
2213+ cc health-clear Clear the health probe cache
21552214 cc next Cycle to the next enabled profile
21562215 cc help Show this help
21572216
@@ -2170,6 +2229,7 @@ Notes:
21702229 CLAUDE_CODE_AUTO_COMPACT_WINDOW) stored in the registry; exported on switch and
21712230 cleared when switching to another profile so values do not leak.
21722231 Add commands only write profile metadata. Put real tokens in secrets.toml.
2232+ Without probe_model, Claude probes use ANTHROPIC_MODEL, ANTHROPIC_DEFAULT_HAIKU_MODEL, then a cheap fallback.
21732233 Legacy ~/.ai-secrets/*.ps1 files are still accepted as a fallback.
21742234"@ | Write-Host
21752235}
@@ -2192,7 +2252,7 @@ function Format-AiHealthCell {
21922252function Test-AiFreshFlag {
21932253 param ([AllowNull ()][string []]$Tokens )
21942254 if (-not $Tokens ) { return $false }
2195- foreach ($t in $Tokens ) { if ($t -in @ (" --fresh" , " -f" )) { return $true } }
2255+ foreach ($t in $Tokens ) { if ($t -in @ (" --fresh" , " -f" , " --refresh " , " -r " )) { return $true } }
21962256 return $false
21972257}
21982258
@@ -2475,15 +2535,15 @@ function Show-AiHealth {
24752535 $plan = Get-AiProfileProbePlan - Tool $Tool - Profile $p
24762536 if ($plan.ContainsKey (" Early" )) {
24772537 $e = $plan.Early
2478- $display [$n ] = @ { Health = (& $cellOf $e ); Method = ($ (if ($e.Method ) { $e.Method } else { " -" })); Note = ($ (if ($e.Error ) { $e.Error } else { " " })); Pending = $false }
2538+ $display [$n ] = @ { Health = (& $cellOf $e ); Method = ($ (if ($e.Method ) { $e.Method } else { " -" })); Note = ($ (if ($e.Error ) { ConvertTo-AiHealthDisplayError $e.Error } else { " " })); Pending = $false }
24792539 continue
24802540 }
24812541 $plans [$n ] = $plan
24822542 $useCache = $false
24832543 if (-not $Fresh ) {
24842544 $cached = Get-AiProfileHealthCached - Tool $Tool - Profile $p - CacheOnly
24852545 if ($cached.Cached ) {
2486- $display [$n ] = @ { Health = (& $cellOf $cached ); Method = ($ (if ($cached.Method ) { $cached.Method } else { " -" })); Note = ($ (if ($cached.Error ) { $cached.Error } else { " " })); Pending = $false }
2546+ $display [$n ] = @ { Health = (& $cellOf $cached ); Method = ($ (if ($cached.Method ) { $cached.Method } else { " -" })); Note = ($ (if ($cached.Error ) { ConvertTo-AiHealthDisplayError $cached.Error } else { " " })); Pending = $false }
24872547 $useCache = $true
24882548 }
24892549 }
@@ -2552,7 +2612,7 @@ function Show-AiHealth {
25522612 Write-AiHealthCacheEntry - Key " $Tool .$n " - Entry ([pscustomobject ]@ {
25532613 status = $h.Status ; latencyMs = $h.LatencyMs ; method = $h.Method ; error = $h.Error ; probedAt = $now
25542614 })
2555- $display [$n ] = @ { Health = (Format-AiHealthCell $h ); Method = ($ (if ($h.Method ) { $h.Method } else { " -" })); Note = ($ (if ($h.Error ) { $h.Error } else { " " })); Pending = $false }
2615+ $display [$n ] = @ { Health = (Format-AiHealthCell $h ); Method = ($ (if ($h.Method ) { $h.Method } else { " -" })); Note = ($ (if ($h.Error ) { ConvertTo-AiHealthDisplayError $h.Error } else { " " })); Pending = $false }
25562616 }
25572617
25582618 $footer = " (health " + ($ (if ($Fresh ) { " re-probed (fresh, parallel)" } else { " cached <=5min" })) + " ; '" + $Tool + " health --fresh' re-probe, '" + $Tool + " health-clear' clears)"
@@ -2646,7 +2706,7 @@ foreach ($c in $Candidates) {
26462706 $h = Resolve-AiProfileHealth - Plan $runspaces [$n ].Plan - Results $results - DegradedMs $DegradedMs
26472707 $now = [DateTimeOffset ]::UtcNow.ToUnixTimeSeconds()
26482708 Write-AiHealthCacheEntry - Key " $Tool .$n " - Entry ([pscustomobject ]@ { status = $h.Status ; latencyMs = $h.LatencyMs ; method = $h.Method ; error = $h.Error ; probedAt = $now })
2649- $display [$n ] = @ { Health = (Format-AiHealthCell $h ); Method = ($ (if ($h.Method ) { $h.Method } else { " -" })); Note = ($ (if ($h.Error ) { $h.Error } else { " " })); Pending = $false }
2709+ $display [$n ] = @ { Health = (Format-AiHealthCell $h ); Method = ($ (if ($h.Method ) { $h.Method } else { " -" })); Note = ($ (if ($h.Error ) { ConvertTo-AiHealthDisplayError $h.Error } else { " " })); Pending = $false }
26502710 $done += $n
26512711 }
26522712 }
@@ -2705,6 +2765,7 @@ function Show-CodexStatus {
27052765 Write-Host " OPENAI_API_KEY: $ ( Format-AiSecretPreview $env: OPENAI_API_KEY ) "
27062766 Write-Host " Cached login: $ ( Get-CodexLoginStatusText ) "
27072767 if ($profile ) {
2768+ Write-Host " Probe model: $ ( Get-AiProbeModel - Tool codex - Profile $profile ) "
27082769 $h = Get-AiProfileHealthCached - Tool " codex" - Profile $profile - Fresh:$Fresh - CacheOnly:(-not $Fresh )
27092770 Write-Host (" Health: " + (Format-AiHealthCell $h ) + $ (if ($h.Error ) { " " + $h.Error } else { ' ' }))
27102771 }
@@ -2735,6 +2796,7 @@ function Show-ClaudeStatus {
27352796 $cprofile = Get-AiProfileByName - Tool " claude" - Name ($env: AI_CLAUDE_LABEL ?? $saved )
27362797 if (-not $cprofile ) { $cprofile = Get-AiProfileByName - Tool " claude" - Name (Get-AiDefaultProfileName - Tool " claude" ) }
27372798 if ($cprofile ) {
2799+ Write-Host " Probe model: $ ( Get-AiProbeModel - Tool claude - Profile $cprofile ) "
27382800 $h = Get-AiProfileHealthCached - Tool " claude" - Profile $cprofile - Fresh:$Fresh - CacheOnly:(-not $Fresh )
27392801 Write-Host (" Health: " + (Format-AiHealthCell $h ) + $ (if ($h.Error ) { " " + $h.Error } else { ' ' }))
27402802 }
0 commit comments