fix(elevenlabs): reject blank realtime transcription keys (#108916)

* fix(elevenlabs): treat blank env API key as unconfigured in speech and transcription providers

* fix(elevenlabs): normalize realtime auth lookup

Co-authored-by: RileyJJY <0668000974@xydigit.com>

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
RileyJJY
2026-07-24 00:22:17 +08:00
committed by GitHub
parent acd92f6a3d
commit f20e747c8f
2 changed files with 36 additions and 8 deletions

View File

@@ -2,9 +2,18 @@
import { createServer } from "node:http";
import type { AddressInfo } from "node:net";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import { afterEach, describe, expect, it } from "vitest";
import { afterEach, describe, expect, it, vi } from "vitest";
import type WebSocket from "ws";
import { WebSocketServer } from "ws";
const { resolveElevenLabsApiKeyWithProfileFallbackMock } = vi.hoisted(() => ({
resolveElevenLabsApiKeyWithProfileFallbackMock: vi.fn(),
}));
vi.mock("./config-api.js", () => ({
resolveElevenLabsApiKeyWithProfileFallback: resolveElevenLabsApiKeyWithProfileFallbackMock,
}));
import { buildElevenLabsRealtimeTranscriptionProvider } from "./realtime-transcription-provider.js";
let cleanup: (() => Promise<void>) | undefined;
@@ -48,6 +57,8 @@ describe("buildElevenLabsRealtimeTranscriptionProvider", () => {
afterEach(async () => {
await cleanup?.();
cleanup = undefined;
vi.unstubAllEnvs();
resolveElevenLabsApiKeyWithProfileFallbackMock.mockReset();
});
it("normalizes nested provider config", () => {
@@ -160,4 +171,16 @@ describe("buildElevenLabsRealtimeTranscriptionProvider", () => {
expect(requests[0]?.searchParams.get("commit_strategy")).toBe("vad");
expect(requests[0]?.searchParams.get("language_code")).toBe("en");
});
it("rejects whitespace-only environment keys before session creation", () => {
resolveElevenLabsApiKeyWithProfileFallbackMock.mockReturnValue(null);
vi.stubEnv("ELEVENLABS_API_KEY", "");
vi.stubEnv("XI_API_KEY", " ");
const provider = buildElevenLabsRealtimeTranscriptionProvider();
expect(provider.isConfigured({ cfg: {} as OpenClawConfig, providerConfig: {} })).toBe(false);
expect(() => provider.createSession({ providerConfig: {} })).toThrow(
"ElevenLabs API key missing",
);
});
});

View File

@@ -247,6 +247,16 @@ function createElevenLabsRealtimeTranscriptionSession(
});
}
function resolveElevenLabsRealtimeApiKey(
config: ElevenLabsRealtimeTranscriptionProviderConfig,
): string | null | undefined {
return (
config.apiKey ??
resolveElevenLabsApiKeyWithProfileFallback() ??
normalizeOptionalString(process.env.XI_API_KEY)
);
}
export function buildElevenLabsRealtimeTranscriptionProvider(): RealtimeTranscriptionProviderPlugin {
return {
id: "elevenlabs",
@@ -256,15 +266,10 @@ export function buildElevenLabsRealtimeTranscriptionProvider(): RealtimeTranscri
autoSelectOrder: 40,
resolveConfig: ({ rawConfig }) => normalizeProviderConfig(rawConfig),
isConfigured: ({ providerConfig }) =>
Boolean(
normalizeProviderConfig(providerConfig).apiKey ||
resolveElevenLabsApiKeyWithProfileFallback() ||
process.env.XI_API_KEY,
),
Boolean(resolveElevenLabsRealtimeApiKey(normalizeProviderConfig(providerConfig))),
createSession: (req) => {
const config = normalizeProviderConfig(req.providerConfig);
const apiKey =
config.apiKey || resolveElevenLabsApiKeyWithProfileFallback() || process.env.XI_API_KEY;
const apiKey = resolveElevenLabsRealtimeApiKey(config);
if (!apiKey) {
throw new Error("ElevenLabs API key missing");
}