diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 9fae13e631..f1564a656d 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -5577,6 +5577,32 @@ paths: "200": description: Circuit breakers reset + /api/health: + get: + tags: [System] + summary: Liveness probe + description: >- + Unauthenticated liveness probe. Returns `{ status, timestamp }` and nothing + else — version, uptime and memory stay behind the authenticated + `GET /api/monitoring/health`. Sent with + `Cache-Control: no-store, no-cache, must-revalidate`. + security: [] + responses: + "200": + description: Service is alive. + content: + application/json: + schema: + type: object + required: [status, timestamp] + properties: + status: + type: string + enum: [ok] + timestamp: + type: string + format: date-time + /api/monitoring/health: get: tags: [System] @@ -7395,6 +7421,145 @@ paths: $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" + /api/v1/voices: + get: + tags: [Audio] + summary: List ElevenLabs voices + description: >- + Proxies `GET https://api.elevenlabs.io/v1/voices` using the stored + `elevenlabs` provider credentials (the caller never sends `xi-api-key`). + The incoming query string is forwarded unchanged. + security: + - BearerAuth: [] + responses: + "200": + description: ElevenLabs voice list, relayed verbatim. + "401": + description: No usable `elevenlabs` credential is configured. + "429": + description: Every configured `elevenlabs` credential is rate limited. + "500": + $ref: "#/components/responses/InternalError" + /api/v1/speech-to-text: + post: + tags: [Audio] + summary: ElevenLabs speech-to-text + description: >- + Streams the request body to `POST https://api.elevenlabs.io/v1/speech-to-text` + using the stored `elevenlabs` provider credentials. `content-type` and + `accept` are forwarded; the upstream body is relayed unchanged. + security: + - BearerAuth: [] + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + responses: + "200": + description: Transcription payload, relayed verbatim from ElevenLabs. + "401": + description: No usable `elevenlabs` credential is configured. + "429": + description: Every configured `elevenlabs` credential is rate limited. + "500": + $ref: "#/components/responses/InternalError" + /api/v1/text-to-speech/{voiceId}: + post: + tags: [Audio] + summary: ElevenLabs text-to-speech + description: >- + Streams the request body to + `POST https://api.elevenlabs.io/v1/text-to-speech/{voiceId}` using the stored + `elevenlabs` provider credentials. `voiceId` must match `^[A-Za-z0-9_-]+$` + or the request is rejected with 400 before any upstream call. + security: + - BearerAuth: [] + parameters: + - name: voiceId + in: path + required: true + schema: + type: string + pattern: "^[A-Za-z0-9_-]+$" + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + text: + type: string + responses: + "200": + description: Audio stream, relayed verbatim from ElevenLabs. + "400": + description: Invalid ElevenLabs voice ID. + "401": + description: No usable `elevenlabs` credential is configured. + "429": + description: Every configured `elevenlabs` credential is rate limited. + "500": + $ref: "#/components/responses/InternalError" + /api/v1/explain/routing: + get: + tags: [Telemetry] + summary: Routing explainability snapshot + description: >- + Returns the most recent routing events (bounded in-memory ring buffer) plus + the per-provider/model quality snapshot from `open-sse/services/routing`. + Routing metadata only — never prompts, bodies, headers or credentials. + Auth mirrors `/api/v1/combos`: a valid Bearer API key or a dashboard + session; with `REQUIRE_API_KEY=false` anonymous reads are allowed. + security: + - BearerAuth: [] + parameters: + - name: limit + in: query + required: false + description: Events/quality rows to return; clamped to 1–500 (default 50). + schema: + type: integer + minimum: 1 + maximum: 500 + default: 50 + responses: + "200": + description: Routing explain payload. + content: + application/json: + schema: + type: object + properties: + object: + type: string + enum: [routing_explain] + sinks: + type: array + items: + type: string + otelEnabled: + type: boolean + events: + type: array + items: + type: object + quality: + type: array + items: + type: object + otel: + type: object + "401": + $ref: "#/components/responses/Unauthorized" + "500": + $ref: "#/components/responses/InternalError" /api/v1/providers/suggested-models: get: tags: diff --git a/tests/unit/hard-session-lease-bypass-inventory.test.ts b/tests/unit/hard-session-lease-bypass-inventory.test.ts index bebf43c0e6..63b88b50f2 100644 --- a/tests/unit/hard-session-lease-bypass-inventory.test.ts +++ b/tests/unit/hard-session-lease-bypass-inventory.test.ts @@ -71,6 +71,10 @@ const EXPECTED: Record> = { "open-sse/handlers/cursorCliProxy.ts": 1, "open-sse/services/alibabaFreeTier.ts": 1, "open-sse/services/alibabaFreeTierQuotaFetcher.ts": 1, + // Volcano Ark plan cycle: readConnectionForCooldownGate() reads the row backing the + // pre-dispatch persisted-cooldown gate, i.e. it is on the routing/dispatch path - same + // class as providerWildcard/autoComboCandidates, so it is classified B below. + "open-sse/services/combo.ts": 1, "open-sse/services/combo/providerWildcard.ts": 1, "open-sse/services/tokenRefresh.ts": 1, "src/app/(dashboard)/dashboard/tools/agent-bridge/page.tsx": 1, @@ -128,6 +132,14 @@ const EXPECTED: Record> = { "src/lib/oauth/utils/codexAuthImport.ts": 1, "src/lib/providerModels/managedModelImport.ts": 1, "src/lib/providers/codexConnectionDefaults.ts": 1, + // Volcano Ark plan connect flow (commit d732cf615): both are connection *persistence* + // sites, not dispatch. volcenginePlanBinding looks the plan connection up by name to + // decide update-vs-create during connect (same shape as oauth/connectionPersistence); + // volcPlanAutoSyncBackfill is a one-shot boot backfill that patches a providerSpecificData + // flag and issues no upstream call. Neither selects a connection to serve a request, so + // both stay class C (see CLASSIFICATION below). + "src/lib/providers/volcPlanAutoSyncBackfill.ts": 1, + "src/lib/providers/volcenginePlanBinding.ts": 1, "src/lib/proxyEgress.ts": 1, "src/lib/quota/connectionRecovery.ts": 2, "src/lib/sync/bundle.ts": 1, @@ -177,6 +189,7 @@ const CLASSIFICATION: Record> = { [ "open-sse/handlers/autoComboCandidates.ts", "open-sse/handlers/chatCore.ts", + "open-sse/services/combo.ts", "open-sse/services/alibabaFreeTier.ts", "open-sse/services/alibabaFreeTierQuotaFetcher.ts", "open-sse/services/combo/providerWildcard.ts", diff --git a/tests/unit/providers-constants-split.test.ts b/tests/unit/providers-constants-split.test.ts index 50079fad5c..17aff617a7 100644 --- a/tests/unit/providers-constants-split.test.ts +++ b/tests/unit/providers-constants-split.test.ts @@ -26,7 +26,10 @@ // merge-train batch — independently bumped the gateways family too, landing at 231; Freebuff // (gateways, #10531) brings it to 232. #8864 moves uncloseai (gateways family) into // NOAUTH_PROVIDERS, dropping the APIKEY_PROVIDERS count to 231. Logfare (gateways, #10987) brings it back to 232. -// #11176 removes hackclub (gateways family), landing at 231. +// #11176 removes hackclub (gateways family), landing at 231. The Volcano Ark plan providers +// (volcengine-agent-plan + volcengine-coding-plan, regional family, commit d732cf615) bring it +// to 233 - measured on the tip: merged 233 keys, 233 unique, family sum 233 with an empty +// cross-family duplicate set, so the strict partition is intact. import { test } from "node:test"; import assert from "node:assert/strict"; @@ -55,12 +58,12 @@ test("barrel still exports every catalog + key helpers", () => { } }); -test("APIKEY_PROVIDERS merges the 6 family files into 231 entries (no loss / no dup)", async () => { +test("APIKEY_PROVIDERS merges the 6 family files into 233 entries (no loss / no dup)", async () => { const keys = Object.keys((P as Record).APIKEY_PROVIDERS); - assert.equal(keys.length, 231); - assert.equal(new Set(keys).size, 231, "duplicate keys after spread-merge"); + assert.equal(keys.length, 233); + assert.equal(new Set(keys).size, 233, "duplicate keys after spread-merge"); // the merged object's entry-count equals the sum of the 6 semantic family files; families are a - // strict partition (every provider in exactly one), so the sum must be exactly 231. + // strict partition (every provider in exactly one), so the sum must be exactly 233. const families: [string, string][] = [ ["gateways", "APIKEY_PROVIDERS_GATEWAYS"], ["frontier-labs", "APIKEY_PROVIDERS_FRONTIER"], @@ -80,7 +83,7 @@ test("APIKEY_PROVIDERS merges the 6 family files into 231 entries (no loss / no seen.add(k); } } - assert.equal(famTotal, 231, "families must partition all 231 providers"); + assert.equal(famTotal, 233, "families must partition all 233 providers"); }); test("AI_PROVIDERS Proxy aggregates all sections; lookups resolve", () => {