
















@@ -0,0 +1,123 @@
1+import fs from "node:fs/promises";
2+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3+import { ErrorCodes } from "../protocol/index.js";
4+import { MAX_PAYLOAD_BYTES } from "../server-constants.js";
5+6+const mocks = vi.hoisted(() => ({
7+transcribeAudioFile: vi.fn(async () => ({
8+text: "hello from audio",
9+provider: "openai",
10+model: "gpt-4o-transcribe",
11+})),
12+}));
13+14+vi.mock("../../media-understanding/runtime.js", () => ({
15+transcribeAudioFile:
16+mocks.transcribeAudioFile as typeof import("../../media-understanding/runtime.js").transcribeAudioFile,
17+}));
18+19+describe("chatTranscribeAudioHandlers", () => {
20+beforeEach(() => {
21+mocks.transcribeAudioFile.mockReset();
22+mocks.transcribeAudioFile.mockResolvedValue({
23+text: "hello from audio",
24+provider: "openai",
25+model: "gpt-4o-transcribe",
26+});
27+});
28+29+afterEach(() => {
30+vi.restoreAllMocks();
31+});
32+33+it("keeps the decoded audio cap below the base64 WebSocket frame limit", async () => {
34+const { MAX_CHAT_TRANSCRIBE_AUDIO_BYTES } = await import("./chat-transcribe-audio.js");
35+const base64Bytes = Math.ceil(MAX_CHAT_TRANSCRIBE_AUDIO_BYTES / 3) * 4;
36+37+expect(base64Bytes + 64 * 1024).toBeLessThanOrEqual(MAX_PAYLOAD_BYTES);
38+expect(MAX_CHAT_TRANSCRIBE_AUDIO_BYTES).toBeLessThan(20 * 1024 * 1024);
39+});
40+41+it("transcribes uploaded chat dictation audio through media understanding", async () => {
42+const { chatTranscribeAudioHandlers } = await import("./chat-transcribe-audio.js");
43+const respond = vi.fn();
44+45+await chatTranscribeAudioHandlers["chat.transcribeAudio"]({
46+params: {
47+audioDataUrl: `data:audio/webm;base64,${Buffer.from("audio").toString("base64")}`,
48+},
49+ respond,
50+context: { getRuntimeConfig: () => ({ tools: { media: {} } }) },
51+} as never);
52+53+expect(mocks.transcribeAudioFile).toHaveBeenCalledWith(
54+expect.objectContaining({
55+cfg: { tools: { media: {} } },
56+mime: "audio/webm",
57+}),
58+);
59+const call = (mocks.transcribeAudioFile.mock.calls as unknown as Array<[{ filePath?: string }]>)
60+.at(0)
61+?.at(0);
62+const filePath = call?.filePath;
63+expect(filePath).toMatch(/dictation\.webm$/);
64+await expect(fs.stat(filePath ?? "")).rejects.toMatchObject({ code: "ENOENT" });
65+expect(respond).toHaveBeenCalledWith(true, {
66+text: "hello from audio",
67+provider: "openai",
68+model: "gpt-4o-transcribe",
69+});
70+});
71+72+it("returns INVALID_REQUEST for missing audio payloads", async () => {
73+const { chatTranscribeAudioHandlers } = await import("./chat-transcribe-audio.js");
74+const respond = vi.fn();
75+76+await chatTranscribeAudioHandlers["chat.transcribeAudio"]({
77+params: {},
78+ respond,
79+context: { getRuntimeConfig: () => ({}) },
80+} as never);
81+82+expect(respond).toHaveBeenCalledWith(
83+false,
84+undefined,
85+expect.objectContaining({
86+code: ErrorCodes.INVALID_REQUEST,
87+message: expect.stringContaining("requires audioDataUrl or audioBase64"),
88+}),
89+);
90+expect(mocks.transcribeAudioFile).not.toHaveBeenCalled();
91+});
92+93+it("returns UNAVAILABLE when no transcription provider is configured", async () => {
94+mocks.transcribeAudioFile.mockResolvedValue({
95+text: undefined,
96+decision: {
97+capability: "audio",
98+outcome: "skipped",
99+attachments: [{ attempts: [] }],
100+},
101+} as never);
102+const { chatTranscribeAudioHandlers } = await import("./chat-transcribe-audio.js");
103+const respond = vi.fn();
104+105+await chatTranscribeAudioHandlers["chat.transcribeAudio"]({
106+params: {
107+audioBase64: Buffer.from("audio").toString("base64"),
108+mimeType: "audio/ogg",
109+},
110+ respond,
111+context: { getRuntimeConfig: () => ({}) },
112+} as never);
113+114+expect(respond).toHaveBeenCalledWith(
115+false,
116+undefined,
117+expect.objectContaining({
118+code: ErrorCodes.UNAVAILABLE,
119+message: expect.stringContaining("No audio transcription provider"),
120+}),
121+);
122+});
123+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。