Skip to content

Instantly share code, notes, and snippets.

@sblack4
Created March 28, 2026 21:23
Show Gist options
  • Select an option

  • Save sblack4/9815f9c4186a552e364756c7be203943 to your computer and use it in GitHub Desktop.

Select an option

Save sblack4/9815f9c4186a552e364756c7be203943 to your computer and use it in GitHub Desktop.
xAI TTS → OpenAI-compatible proxy for OpenClaw (workaround for openclaw/openclaw#48454)
// xAI TTS → OpenAI-compatible proxy
// Translates OpenAI /v1/audio/speech requests to xAI /v1/tts
// Usage: XAI_API_KEY=xai-... node xai-tts-proxy.js
//
// OpenClaw config:
// messages.tts.provider = "openai"
// messages.tts.openai.baseUrl = "http://127.0.0.1:18790/v1"
// messages.tts.openai.apiKey = "sk-proxy-not-needed"
//
// Voice mapping (OpenAI → xAI):
// alloy→eve, echo→rex, fable→ara, onyx→leo, nova→sal
// xAI native names (eve, ara, rex, sal, leo) also work directly.
const http = require("http");
const https = require("https");
const PORT = process.env.PORT || 18790;
const XAI_API_KEY = process.env.XAI_API_KEY;
if (!XAI_API_KEY) {
console.error("XAI_API_KEY is required");
process.exit(1);
}
const VOICE_MAP = {
alloy: "eve", echo: "rex", fable: "ara", onyx: "leo", nova: "sal", shimmer: "eve",
eve: "eve", ara: "ara", rex: "rex", sal: "sal", leo: "leo",
};
const server = http.createServer((req, res) => {
if (req.method !== "POST" || !req.url.endsWith("/audio/speech")) {
res.writeHead(404);
res.end("Not found");
return;
}
let body = "";
req.on("data", (chunk) => (body += chunk));
req.on("end", () => {
let parsed;
try { parsed = JSON.parse(body); } catch {
res.writeHead(400);
res.end("Invalid JSON");
return;
}
const voice = VOICE_MAP[parsed.voice] || "eve";
const format = parsed.response_format || "mp3";
const xaiBody = JSON.stringify({
text: parsed.input,
voice_id: voice,
language: "auto",
output_format: {
codec: format === "opus" ? "mp3" : format,
sample_rate: 24000,
bit_rate: 128000,
},
});
const xaiReq = https.request("https://api.x.ai/v1/tts", {
method: "POST",
headers: {
Authorization: `Bearer ${XAI_API_KEY}`,
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(xaiBody),
},
}, (xaiRes) => {
res.writeHead(xaiRes.statusCode, {
"Content-Type": xaiRes.headers["content-type"] || "audio/mpeg",
});
xaiRes.pipe(res);
});
xaiReq.on("error", (err) => {
console.error("xAI request failed:", err.message);
res.writeHead(502);
res.end("xAI TTS request failed");
});
xaiReq.write(xaiBody);
xaiReq.end();
});
});
server.listen(PORT, "127.0.0.1", () => {
console.log(`xai-tts-proxy listening on 127.0.0.1:${PORT}`);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment