mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-31 11:01:36 +08:00
feat(health-check): replace real-LLM probe with HTTP reachability check
The provider panel health check sent a real streaming model request, which many third-party providers block (401/403/WAF), causing false negatives while only stable official endpoints passed. Replace it with a lightweight reachability probe: GET the provider base_url and treat any HTTP response (200/4xx/5xx) as reachable; only DNS/connect/TLS/timeout count as failure. Latency is the probe's TTFB. Backend (services/stream_check.rs): rewrite ~2200 -> ~350 lines, dropping real-request building, format conversion, auth and API-path resolution while keeping per-app base_url extraction. Defaults: 8s timeout, 1 retry, 1500ms degraded threshold. Failover invariant: the reachability check must never reset the circuit breaker (reachable != usable; a 403 host is reachable but broken for real traffic). Remove the resetCircuitBreaker call from useStreamCheck; failover failure detection stays driven solely by real proxy traffic (forwarder/circuit_breaker untouched). useResetCircuitBreaker is kept dormant for a future manual-recovery entry. Open the check to all providers: drop the official/copilot/codex-oauth/third-party gating and the 'sends a real request' confirm dialog. For official providers whose base_url is intentionally empty, fall back to the endpoint the client actually uses (Claude -> api.anthropic.com, Codex -> chatgpt.com/backend-api/codex, Gemini -> generativelanguage). Non-official providers with a missing base_url still error to avoid a false green light. Claude Desktop Official is native 1P mode (talks to claude.ai, cc-switch not in the request path, no reliable endpoint) so its button stays hidden. Slim StreamCheckConfig and per-provider testConfig to timeout/threshold/retries (drop test model + prompt); sync zh/en/ja/zh-TW. Retain the now-unused anthropic_to_openai/anthropic_to_gemini transform utilities and their test suites.
This commit is contained in:
@@ -61,7 +61,7 @@ export function ProviderAdvancedConfig({
|
||||
<FlaskConical className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="font-medium">
|
||||
{t("providerAdvanced.testConfig", {
|
||||
defaultValue: "模型测试配置",
|
||||
defaultValue: "连通检测配置",
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
@@ -106,31 +106,10 @@ export function ProviderAdvancedConfig({
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("providerAdvanced.testConfigDesc", {
|
||||
defaultValue:
|
||||
"为此供应商配置单独的模型测试参数,不启用时使用全局配置。",
|
||||
"为此供应商配置单独的连通检测参数(超时/阈值/重试),不启用时使用全局配置。",
|
||||
})}
|
||||
</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="test-model">
|
||||
{t("providerAdvanced.testModel", {
|
||||
defaultValue: "测试模型",
|
||||
})}
|
||||
</Label>
|
||||
<Input
|
||||
id="test-model"
|
||||
value={testConfig.testModel || ""}
|
||||
onChange={(e) =>
|
||||
onTestConfigChange({
|
||||
...testConfig,
|
||||
testModel: e.target.value || undefined,
|
||||
})
|
||||
}
|
||||
placeholder={t("providerAdvanced.testModelPlaceholder", {
|
||||
defaultValue: "留空使用全局配置",
|
||||
})}
|
||||
disabled={!testConfig.enabled}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="test-timeout">
|
||||
{t("providerAdvanced.timeoutSecs", {
|
||||
@@ -141,7 +120,7 @@ export function ProviderAdvancedConfig({
|
||||
id="test-timeout"
|
||||
type="number"
|
||||
min={1}
|
||||
max={300}
|
||||
max={60}
|
||||
value={testConfig.timeoutSecs || ""}
|
||||
onChange={(e) =>
|
||||
onTestConfigChange({
|
||||
@@ -151,26 +130,7 @@ export function ProviderAdvancedConfig({
|
||||
: undefined,
|
||||
})
|
||||
}
|
||||
placeholder="45"
|
||||
disabled={!testConfig.enabled}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="test-prompt">
|
||||
{t("providerAdvanced.testPrompt", {
|
||||
defaultValue: "测试提示词",
|
||||
})}
|
||||
</Label>
|
||||
<Input
|
||||
id="test-prompt"
|
||||
value={testConfig.testPrompt || ""}
|
||||
onChange={(e) =>
|
||||
onTestConfigChange({
|
||||
...testConfig,
|
||||
testPrompt: e.target.value || undefined,
|
||||
})
|
||||
}
|
||||
placeholder="Who are you?"
|
||||
placeholder="8"
|
||||
disabled={!testConfig.enabled}
|
||||
/>
|
||||
</div>
|
||||
@@ -184,7 +144,7 @@ export function ProviderAdvancedConfig({
|
||||
id="degraded-threshold"
|
||||
type="number"
|
||||
min={100}
|
||||
max={60000}
|
||||
max={10000}
|
||||
value={testConfig.degradedThresholdMs || ""}
|
||||
onChange={(e) =>
|
||||
onTestConfigChange({
|
||||
@@ -194,7 +154,7 @@ export function ProviderAdvancedConfig({
|
||||
: undefined,
|
||||
})
|
||||
}
|
||||
placeholder="6000"
|
||||
placeholder="1500"
|
||||
disabled={!testConfig.enabled}
|
||||
/>
|
||||
</div>
|
||||
@@ -208,7 +168,7 @@ export function ProviderAdvancedConfig({
|
||||
id="max-retries"
|
||||
type="number"
|
||||
min={0}
|
||||
max={10}
|
||||
max={5}
|
||||
value={testConfig.maxRetries ?? ""}
|
||||
onChange={(e) =>
|
||||
onTestConfigChange({
|
||||
@@ -218,7 +178,7 @@ export function ProviderAdvancedConfig({
|
||||
: undefined,
|
||||
})
|
||||
}
|
||||
placeholder="2"
|
||||
placeholder="1"
|
||||
disabled={!testConfig.enabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user