mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-22 07:02:16 +03:00
feat(speech): accept response_format=ogg as an opus alias (#10822)
Merged — validated together with a batch of related RaviTharuma PRs in one combined worktree (typecheck:core clean, complexity/file-size/changelog gates green, focused tests passing). Thanks for the contribution!
This commit is contained in:
1
changelog.d/features/10587-ogg-speech-alias.md
Normal file
1
changelog.d/features/10587-ogg-speech-alias.md
Normal file
@@ -0,0 +1 @@
|
||||
- **feat(providers):** accept `response_format=ogg` on `/v1/audio/speech` as an alias for the existing Opus/Ogg encoder ([#10587](https://github.com/diegosouzapw/OmniRoute/issues/10587))
|
||||
@@ -229,6 +229,16 @@ async function handleDeepgramSpeech(providerConfig, body, modelId, token) {
|
||||
return audioStreamResponse(res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Voice-note clients send response_format=ogg. OpenAI TTS documents opus, not ogg.
|
||||
* OmniRoute already returns Ogg/Opus bytes for opus — alias ogg → opus (#10587).
|
||||
*/
|
||||
export function normalizeSpeechResponseFormat(fmt) {
|
||||
if (typeof fmt !== "string" || !fmt) return "mp3";
|
||||
const lower = fmt.toLowerCase();
|
||||
return lower === "ogg" ? "opus" : lower;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Soniox TTS (OpenAI speech shape → Soniox /tts, returns raw audio bytes)
|
||||
*/
|
||||
@@ -963,7 +973,7 @@ export async function handleAudioSpeech({
|
||||
model: modelId,
|
||||
input: body.input,
|
||||
voice: body.voice || "alloy",
|
||||
response_format: body.response_format || "mp3",
|
||||
response_format: normalizeSpeechResponseFormat(body.response_format),
|
||||
speed: body.speed || 1.0,
|
||||
}),
|
||||
});
|
||||
|
||||
42
tests/unit/audio-speech-ogg-alias-10587.test.ts
Normal file
42
tests/unit/audio-speech-ogg-alias-10587.test.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const { normalizeSpeechResponseFormat, handleAudioSpeech } = await import(
|
||||
"../../open-sse/handlers/audioSpeech.ts"
|
||||
);
|
||||
|
||||
test("normalizeSpeechResponseFormat aliases ogg to opus (#10587)", () => {
|
||||
assert.equal(normalizeSpeechResponseFormat("ogg"), "opus");
|
||||
assert.equal(normalizeSpeechResponseFormat("OGG"), "opus");
|
||||
assert.equal(normalizeSpeechResponseFormat("opus"), "opus");
|
||||
assert.equal(normalizeSpeechResponseFormat("mp3"), "mp3");
|
||||
assert.equal(normalizeSpeechResponseFormat(undefined), "mp3");
|
||||
});
|
||||
|
||||
test("OpenAI-compat speech path remaps ogg to opus before upstream", async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
let captured;
|
||||
globalThis.fetch = async (_url, options = {}) => {
|
||||
captured = JSON.parse(String(options.body || "{}"));
|
||||
return new Response(new Uint8Array([1, 2, 3]), {
|
||||
status: 200,
|
||||
headers: { "content-type": "audio/opus" },
|
||||
});
|
||||
};
|
||||
try {
|
||||
const response = await handleAudioSpeech({
|
||||
body: {
|
||||
model: "openai/tts-1",
|
||||
input: "format check",
|
||||
voice: "alloy",
|
||||
response_format: "ogg",
|
||||
},
|
||||
credentials: { apiKey: "openai-key" },
|
||||
});
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(captured.response_format, "opus");
|
||||
assert.equal(captured.model, "tts-1");
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user