diff --git a/open-sse/executors/cliproxyapi.ts b/open-sse/executors/cliproxyapi.ts index df14e8ff80..86a0670404 100644 --- a/open-sse/executors/cliproxyapi.ts +++ b/open-sse/executors/cliproxyapi.ts @@ -344,11 +344,18 @@ export class CliproxyapiExecutor extends BaseExecutor { /** * Health check — verifies CLIProxyAPI is reachable. + * + * CPA 6.x doesn't expose a /health endpoint; previously we hit /health + * and got 404, which made the dashboard report "CLIProxyAPI not + * detected" even when the service was up and successfully serving + * /v1/messages. Probe /v1/models instead (returns 200 with the + * advertised model list), which is the closest thing CPA has to a + * liveness probe and works on every CPA version we've tested. */ async healthCheck(): Promise<{ ok: boolean; latencyMs: number; error?: string }> { const start = Date.now(); try { - const res = await fetch(`${this.upstreamBaseUrl}/health`, { + const res = await fetch(`${this.upstreamBaseUrl}/v1/models`, { signal: AbortSignal.timeout(HEALTH_CHECK_TIMEOUT_MS), }); return { diff --git a/tests/unit/cliproxyapi-executor.test.ts b/tests/unit/cliproxyapi-executor.test.ts index a66c39f9c5..c5f1aab86c 100644 --- a/tests/unit/cliproxyapi-executor.test.ts +++ b/tests/unit/cliproxyapi-executor.test.ts @@ -296,6 +296,27 @@ describe("CliproxyapiExecutor", () => { }); }); + describe("healthCheck", () => { + it("probes /v1/models instead of /health", async () => { + process.env.CLIPROXYAPI_HOST = "127.0.0.1"; + process.env.CLIPROXYAPI_PORT = "8317"; + + let capturedUrl; + globalThis.fetch = async (url) => { + capturedUrl = String(url); + return new Response(JSON.stringify({ data: [] }), { status: 200 }); + }; + + const exec = new CliproxyapiExecutor(); + const result = await exec.healthCheck(); + + assert.equal(capturedUrl, "http://127.0.0.1:8317/v1/models"); + assert.equal(result.ok, true); + assert.equal(result.error, undefined); + assert.ok(result.latencyMs >= 0); + }); + }); + describe("Anthropic-shape detection", () => { it("detects Anthropic-shape when top-level system field present", () => { const exec = new CliproxyapiExecutor();