


























@@ -5,15 +5,36 @@ import type WebSocket from "ws";
55import { WebSocketServer } from "ws";
66import { buildXaiRealtimeTranscriptionProvider } from "./realtime-transcription-provider.js";
778+const { isProviderAuthProfileConfiguredMock, resolveApiKeyForProviderMock } = vi.hoisted(() => ({
9+isProviderAuthProfileConfiguredMock: vi.fn(() => false),
10+resolveApiKeyForProviderMock: vi.fn(
11+async (): Promise<{ apiKey: string | undefined }> => ({ apiKey: undefined }),
12+),
13+}));
14+15+vi.mock("openclaw/plugin-sdk/provider-auth", () => ({
16+isProviderAuthProfileConfigured: isProviderAuthProfileConfiguredMock,
17+}));
18+19+vi.mock("openclaw/plugin-sdk/provider-auth-runtime", () => ({
20+resolveApiKeyForProvider: resolveApiKeyForProviderMock,
21+}));
22+823let cleanup: (() => Promise<void>) | undefined;
9241025afterEach(async () => {
1126await cleanup?.();
1227cleanup = undefined;
28+isProviderAuthProfileConfiguredMock.mockReset();
29+isProviderAuthProfileConfiguredMock.mockReturnValue(false);
30+resolveApiKeyForProviderMock.mockReset();
31+resolveApiKeyForProviderMock.mockResolvedValue({ apiKey: undefined });
32+delete process.env.XAI_API_KEY;
33+vi.unstubAllEnvs();
1334});
14351536async function createRealtimeSttServer(params?: {
16-onRequest?: (url: URL) => void;
37+onRequest?: (url: URL, headers: Record<string, string | string[] | undefined>) => void;
1738onBinary?: (audio: Buffer) => void;
1839initialEvent?: unknown;
1940}) {
@@ -28,7 +49,7 @@ async function createRealtimeSttServer(params?: {
28492950server.on("upgrade", (request, socket, head) => {
3051const url = new URL(request.url ?? "/", "http://127.0.0.1");
31-params?.onRequest?.(url);
52+params?.onRequest?.(url, request.headers);
3253wss.handleUpgrade(request, socket, head, (ws) => {
3354clients.add(ws);
3455ws.on("close", () => clients.delete(ws));
@@ -122,10 +143,15 @@ describe("xai realtime transcription provider", () => {
122143});
123144124145it("streams raw binary audio and maps partial and final transcript events", async () => {
146+vi.stubEnv("OPENCLAW_VERSION", "2026.3.22");
125147const binaryFrames: Buffer[] = [];
126148const requestUrls: URL[] = [];
149+const upgradeHeaders: Array<Record<string, string | string[] | undefined>> = [];
127150const server = await createRealtimeSttServer({
128-onRequest: (url) => requestUrls.push(url),
151+onRequest: (url, headers) => {
152+requestUrls.push(url);
153+upgradeHeaders.push(headers);
154+},
129155onBinary: (audio) => binaryFrames.push(audio),
130156});
131157const provider = buildXaiRealtimeTranscriptionProvider();
@@ -166,10 +192,13 @@ describe("xai realtime transcription provider", () => {
166192expect(requestUrls[0]?.searchParams.get("encoding")).toBe("pcm");
167193expect(requestUrls[0]?.searchParams.get("interim_results")).toBe("true");
168194expect(requestUrls[0]?.searchParams.get("endpointing")).toBe("500");
195+expect(upgradeHeaders[0]?.["user-agent"]).toBeUndefined();
196+expect(upgradeHeaders[0]?.authorization).toBe("Bearer xai-test-key");
169197expect(Buffer.concat(binaryFrames).toString()).toContain("queued-before-ready");
170198expect(Buffer.concat(binaryFrames).toString()).toContain("after-ready");
171199expect(onSpeechStart).toHaveBeenCalled();
172200expect(onPartial).toHaveBeenCalledWith("hello openclaw");
201+vi.unstubAllEnvs();
173202});
174203175204it("rejects setup errors before the stream is ready", async () => {
@@ -203,4 +232,42 @@ describe("xai realtime transcription provider", () => {
203232expect(provider.aliases).toContain("xai-realtime");
204233expect(provider.aliases).toContain("grok-stt-streaming");
205234});
235+236+it("reports configured when an xAI auth profile exists, even without env or config apiKey", () => {
237+delete process.env.XAI_API_KEY;
238+isProviderAuthProfileConfiguredMock.mockReturnValue(true);
239+const provider = buildXaiRealtimeTranscriptionProvider();
240+expect(provider.isConfigured({ cfg: {}, providerConfig: {} })).toBe(true);
241+expect(isProviderAuthProfileConfiguredMock).toHaveBeenCalledWith({
242+provider: "xai",
243+cfg: {},
244+});
245+});
246+247+it("threads cfg into the lazy WebSocket bearer resolver", async () => {
248+delete process.env.XAI_API_KEY;
249+resolveApiKeyForProviderMock.mockResolvedValue({ apiKey: "oauth-bearer" });
250+const upgradeHeaders: Array<Record<string, string | string[] | undefined>> = [];
251+const server = await createRealtimeSttServer({
252+onRequest: (_url, headers) => {
253+upgradeHeaders.push(headers);
254+},
255+});
256+257+const provider = buildXaiRealtimeTranscriptionProvider();
258+const cfg = { agents: { defaults: {} } };
259+const session = provider.createSession({
260+ cfg,
261+providerConfig: {
262+baseUrl: server.baseUrl,
263+},
264+});
265+266+await session.connect();
267+session.close();
268+await server.donePromise;
269+270+expect(resolveApiKeyForProviderMock).toHaveBeenCalledWith({ provider: "xai", cfg });
271+expect(upgradeHeaders[0]?.authorization).toBe("Bearer oauth-bearer");
272+});
206273});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。