← back to Ventura Claw
server/connectors/elevenlabs.js
88 lines
// ElevenLabs — REAL. Text-to-speech generation + voice library + user account.
// Free tier: 10K characters/month. Voice cloning available on paid tiers.
function key(creds) {
const k = creds?.ELEVENLABS_API_KEY || process.env.ELEVENLABS_API_KEY;
if (!k) throw new Error("ELEVENLABS_API_KEY not set");
return k;
}
async function call(method, path, body, creds, accept = "application/json") {
const r = await fetch("https://api.elevenlabs.io/v1" + path, {
method,
headers: {
"xi-api-key": key(creds),
"Content-Type": "application/json",
"Accept": accept,
},
body: body ? JSON.stringify(body) : undefined,
});
if (accept === "audio/mpeg") {
if (!r.ok) {
const txt = await r.text();
throw new Error(`elevenlabs ${path} ${r.status}: ${txt.slice(0, 200)}`);
}
const buf = Buffer.from(await r.arrayBuffer());
return { audio_mp3_base64: buf.toString("base64"), bytes: buf.length };
}
const d = r.status === 204 ? {} : await r.json();
if (!r.ok) throw new Error(`elevenlabs ${path} ${r.status}: ${d.detail?.message || d.message || JSON.stringify(d).slice(0, 200)}`);
return d;
}
module.exports = {
meta: {
id: "elevenlabs",
name: "ElevenLabs",
category: "ai-voice",
docsUrl: "https://elevenlabs.io/docs/api-reference/introduction",
auth: "api_key",
realImpl: true,
},
fields: [
{ key: "ELEVENLABS_API_KEY", label: "API Key", type: "password", required: true, hint: "elevenlabs.io → Profile → API Keys (free tier: 10K chars/month)" },
],
configured(c) { return !!(c?.ELEVENLABS_API_KEY || process.env.ELEVENLABS_API_KEY); },
async health(c) {
if (!this.configured(c)) return { ok: false, reason: "no_token" };
try {
const u = await call("GET", "/user", null, c);
return {
ok: true,
subscription: u.subscription?.tier || "free",
chars_used: u.subscription?.character_count || 0,
chars_limit: u.subscription?.character_limit || 0,
};
} catch (e) { return { ok: false, reason: e.message }; }
},
// Read actions
async voicesList(c) {
const d = await call("GET", "/voices", null, c);
return { voices: (d.voices || []).map(v => ({
id: v.voice_id, name: v.name, category: v.category,
labels: v.labels || {}, description: v.description || "",
preview_url: v.preview_url || "",
})) };
},
async user(c) { return call("GET", "/user", null, c); },
async modelsList(c) { return call("GET", "/models", null, c); },
// Write actions (TTS generation counts against free-tier quota)
async textToSpeech(c, args) {
const { voice_id, text, model_id, stability, similarity_boost } = args || {};
if (!voice_id) throw new Error("voice_id required");
if (!text) throw new Error("text required");
return call("POST", `/text-to-speech/${voice_id}`, {
text: String(text).slice(0, 5000),
model_id: model_id || "eleven_turbo_v2_5",
voice_settings: {
stability: Number.isFinite(stability) ? stability : 0.5,
similarity_boost: Number.isFinite(similarity_boost) ? similarity_boost : 0.75,
},
}, c, "audio/mpeg");
},
};