// health.go implements the optional flyto.HealthChecker capability for // OpenAI-compatible endpoints that expose a liveness route (oMLX: GET // /health, no auth, {"status":"healthy",...}). Real OpenAI has no such // endpoint -- Config.HealthCheckPath stays empty there and CheckHealth is a // no-op nil, so the capability never misreports a healthy cloud endpoint as // down. // // health.go 为暴露存活路由的 OpenAI 兼容端点 (oMLX: GET /health, 免鉴权, // {"status":"healthy",...}) 实现可选 flyto.HealthChecker 能力. 真 OpenAI 无此 // 端点 -- 其 Config.HealthCheckPath 留空, CheckHealth 为 no-op nil, 该能力 // 绝不把健康的云端端点误报为挂. package openai import ( "context" "encoding/json" "fmt" "io" "net/http" "strings" "time" "git.flytoex.net/yuanwei/flyto-agent/core/pkg/flyto" ) // Provider satisfies the shared flyto.HealthChecker capability. var _ flyto.HealthChecker = (*Provider)(nil) // healthProbeTimeout bounds the liveness GET independently of the caller's // (long, transcription-sized) deadline: a probe against a sleeping Mac should // answer "offline" in seconds, not inherit a minutes-long budget. // // healthProbeTimeout 独立限探活 GET, 不吃调用方 (给转写准备的分钟级) deadline: // 对睡着的 Mac 探活应秒级报 "离线", 不继承分钟级预算. const healthProbeTimeout = 5 * time.Second // CheckHealth implements flyto.HealthChecker. No-op nil when // Config.HealthCheckPath is empty (no probe available != unhealthy). A // reachable endpoint must answer 2xx AND, when the body carries a "status" // field, report a healthy value -- {"status":"degraded"} with HTTP 200 is // still a failure (fail-loud, ADR-0006). Both "healthy" (single-backend oMLX) // and "ok" (oMLX multi-backend router since 2026-07) count as healthy: the // router's top-level status is {"status":"ok","backends":{...}}, and // rejecting it silently forced every transcription onto the cloud fallback // (prod incident 2026-07-17..19). // // CheckHealth 实现 flyto.HealthChecker. Config.HealthCheckPath 空时 no-op nil // (无探测可用 != 不健康). 可达端点须 2xx **且** body 带 "status" 字段时报健康值 // -- HTTP 200 + {"status":"degraded"} 仍算失败 (fail-loud, ADR-0006). // "healthy" (单后端 oMLX) 与 "ok" (2026-07 起的 oMLX 多后端 router, 顶层 // {"status":"ok","backends":{...}}) 都算健康 -- 拒掉 "ok" 曾把所有转写 // 静默逼上云端降级 (生产事故 2026-07-17..19). func (p *Provider) CheckHealth(ctx context.Context) error { if p.cfg.HealthCheckPath == "" { return nil } ctx, cancel := context.WithTimeout(ctx, healthProbeTimeout) defer cancel() url := strings.TrimSuffix(p.cfg.BaseURL, "/") + p.cfg.HealthCheckPath httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return fmt.Errorf("openai: new health request: %w", err) } resp, err := p.visionHTTPClient().Do(httpReq) if err != nil { return fmt.Errorf("openai: backend health probe failed (offline?): %w", err) } defer resp.Body.Close() body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096)) if resp.StatusCode/100 != 2 { return fmt.Errorf("openai: backend unhealthy: status %d: %s", resp.StatusCode, visionSnippet(body, 200)) } var h struct { Status string `json:"status"` } if err := json.Unmarshal(body, &h); err == nil && h.Status != "" && h.Status != "healthy" && h.Status != "ok" { return fmt.Errorf("openai: backend reports status %q (want healthy/ok)", h.Status) } return nil }