Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **Audio Bridge:** fix production transcription self-loop uploads so real audio reaches the configured STT provider instead of falling back to an unavailable-provider stub ([#10229](https://github.com/diegosouzapw/OmniRoute/pull/10229)).
28 changes: 23 additions & 5 deletions src/lib/guardrails/audioBridgeHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { randomUUID } from "node:crypto";

import { AUDIO_TRANSCRIPTION_PROVIDERS } from "@omniroute/open-sse/config/audioRegistry.ts";
import { detectMediaParts } from "@omniroute/open-sse/utils/mediaParts";

Expand Down Expand Up @@ -201,10 +203,25 @@ export async function callAudioTranscription(
(detectedMime?.startsWith("audio/") ? detectedMime : undefined) ??
AUDIO_FORMAT_MIME[format] ??
"application/octet-stream";
const file = new Blob([Uint8Array.from(bytes)], { type: mime });
const form = new FormData();
form.set("file", file, `audio.${format.replace(/[^a-z0-9]/g, "") || "wav"}`);
form.set("model", config.model);
const safeMime = /^[a-z0-9][a-z0-9.+-]*\/[a-z0-9][a-z0-9.+-]*$/i.test(mime)
? mime
: "application/octet-stream";
const fileName = `audio.${format.replace(/[^a-z0-9]/g, "") || "wav"}`;
const boundary = `----OmniRouteAudioBridge${randomUUID().replace(/-/g, "")}`;
const CRLF = "\r\n";
const multipartBody = Buffer.concat([
Buffer.from(
`--${boundary}${CRLF}` +
`Content-Disposition: form-data; name="file"; filename="${fileName}"${CRLF}` +
`Content-Type: ${safeMime}${CRLF}${CRLF}`
),
bytes,
Buffer.from(
`${CRLF}--${boundary}${CRLF}` +
`Content-Disposition: form-data; name="model"${CRLF}${CRLF}` +
`${config.model}${CRLF}--${boundary}--${CRLF}`
),
]);

const port = (deps.getPort ?? (() => getRuntimePorts().port))();
const bearer = (deps.getBearer ?? resolveSelfLoopBearer)();
Expand All @@ -216,8 +233,9 @@ export async function callAudioTranscription(
headers: {
Accept: "application/json",
Authorization: `Bearer ${bearer}`,
"Content-Type": `multipart/form-data; boundary=${boundary}`,
},
body: form,
body: multipartBody,
}
);
if (!response.ok) {
Expand Down
63 changes: 47 additions & 16 deletions tests/unit/guardrails/audioBridgeHelpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,33 @@ import {
type AudioPart,
} from "../../../src/lib/guardrails/audioBridgeHelpers.ts";

function assertMultipartFile(
init: RequestInit | undefined,
expectedFileName: string,
expectedMime: string,
expectedBytes: Buffer
): void {
const contentType = new Headers(init?.headers).get("content-type");
assert.match(contentType ?? "", /^multipart\/form-data; boundary=/);
const boundary = contentType?.split("boundary=", 2)[1];
assert.ok(boundary);
assert.ok(Buffer.isBuffer(init?.body));
const body = init?.body as Buffer;
assert.ok(
body.includes(
Buffer.concat([
Buffer.from(
`--${boundary}\r\n` +
`Content-Disposition: form-data; name="file"; filename="${expectedFileName}"\r\n` +
`Content-Type: ${expectedMime}\r\n\r\n`
),
expectedBytes,
Buffer.from("\r\n"),
])
)
);
}

test("fixed STT model is honored when its credential is usable", async () => {
const checked: string[] = [];
const selected = await selectAudioBridgeModel("deepgram/nova-2", async (model) => {
Expand Down Expand Up @@ -62,13 +89,20 @@ test("input_audio is posted as multipart to the authenticated transcription self
assert.equal(capturedUrl, "http://localhost:3210/v1/audio/transcriptions");
assert.equal(capturedInit?.method, "POST");
assert.equal(new Headers(capturedInit?.headers).get("authorization"), "Bearer internal-test-key");

const form = capturedInit?.body as FormData;
assert.equal(form.get("model"), "deepgram/nova-3");
const file = form.get("file") as File;
assert.equal(file.name, "audio.wav");
assert.equal(file.type, "audio/wav");
assert.equal(Buffer.from(await file.arrayBuffer()).toString(), "RIFF test audio");
const contentType = new Headers(capturedInit?.headers).get("content-type");
const boundary = contentType?.split("boundary=", 2)[1];
assert.ok(boundary);
assertMultipartFile(capturedInit, "audio.wav", "audio/wav", Buffer.from("RIFF test audio"));
const body = capturedInit?.body as Buffer;
assert.ok(
body.includes(
Buffer.from(
`--${boundary}\r\n` +
'Content-Disposition: form-data; name="model"\r\n\r\n' +
`deepgram/nova-3\r\n--${boundary}--\r\n`
)
)
);
});

test("audio extraction and replacement cover the full history without dropping failed clips", () => {
Expand Down Expand Up @@ -127,7 +161,7 @@ test("audio extraction and replacement cover the full history without dropping f
});

test("audio_url data URIs are decoded before multipart upload", async () => {
let uploaded: File | null = null;
let uploaded: RequestInit | undefined;
await callAudioTranscription(
{
messageIndex: 0,
Expand All @@ -139,22 +173,20 @@ test("audio_url data URIs are decoded before multipart upload", async () => {
{ model: "deepgram/nova-3", timeoutMs: 1_000 },
{
fetchImpl: async (_input, init) => {
uploaded = (init?.body as FormData).get("file") as File;
uploaded = init;
return Response.json({ text: "ok" });
},
getPort: () => 3210,
getBearer: () => "internal-test-key",
}
);

assert.ok(uploaded);
assert.equal(uploaded.type, "audio/mpeg");
assert.equal(Buffer.from(await uploaded.arrayBuffer()).toString(), "ID3");
assertMultipartFile(uploaded, "audio.mp3", "audio/mpeg", Buffer.from("ID3"));
});

test("remote audio_url uses the guarded remote fetch before self-loop upload", async () => {
let fetchedUrl = "";
let uploaded: File | null = null;
let uploaded: RequestInit | undefined;
await callAudioTranscription(
{
messageIndex: 0,
Expand All @@ -174,7 +206,7 @@ test("remote audio_url uses the guarded remote fetch before self-loop upload", a
};
},
fetchImpl: async (_input, init) => {
uploaded = (init?.body as FormData).get("file") as File;
uploaded = init;
return Response.json({ text: "ok" });
},
getPort: () => 3210,
Expand All @@ -183,6 +215,5 @@ test("remote audio_url uses the guarded remote fetch before self-loop upload", a
);

assert.equal(fetchedUrl, "https://media.example.test/clip.ogg");
assert.ok(uploaded);
assert.equal(Buffer.from(await uploaded.arrayBuffer()).toString(), "OggS remote audio");
assertMultipartFile(uploaded, "audio.ogg", "audio/ogg", Buffer.from("OggS remote audio"));
});
Loading