Add native OpenClaw Athena Talk plugin

This commit is contained in:
Mikei386
2026-09-15 23:00:56 +02:00
parent 6cacfc1dc8
commit 724e20fec0
8 changed files with 5212 additions and 23 deletions
+24 -7
View File
@@ -6,13 +6,25 @@ function record(value) {
? value
: {};
}
function resolveModelProvider(cfg, requested) {
const providers = record(record(cfg).models).providers;
const requestedId = typeof requested === "string" ? requested.trim() : "";
if (requestedId)
return record(record(providers)[requestedId]);
for (const id of ["athena", "llama-cpp", "openai"]) {
const candidate = record(record(providers)[id]);
if (typeof candidate.baseUrl === "string" && candidate.baseUrl.trim())
return candidate;
}
return {};
}
function resolveConfig(req) {
const raw = record(req.providerConfig);
const modelProviders = record(record(req.cfg).models).providers;
const athena = record(record(modelProviders).athena);
const modelProvider = resolveModelProvider(req.cfg, raw.modelProvider);
return {
baseUrl: String(raw.baseUrl || athena.baseUrl || "http://192.168.1.212:8081/v1").replace(/\/$/, ""),
apiKey: String(raw.apiKey || athena.apiKey || ""),
modelProvider: String(raw.modelProvider || ""),
baseUrl: String(raw.baseUrl || modelProvider.baseUrl || "http://192.168.1.212:8081/v1").replace(/\/$/, ""),
apiKey: String(raw.apiKey || modelProvider.apiKey || ""),
voice: String(raw.voice || req.voice || "alloy"),
language: String(raw.language || req.language || "de"),
vadThreshold: Number(raw.vadThreshold ?? 0.018),
@@ -145,6 +157,10 @@ class AthenaTalkBridge {
sendAudio(chunk) {
if (!this.isConnected() || chunk.length === 0)
return;
// Half-duplex by design: while Athena is transcribing, consulting the
// agent, synthesizing, or playing a reply, microphone input is ignored.
// This prevents speaker feedback and background noise from cancelling the
// response that is currently being delivered.
if (this.transcribing || this.active)
return;
const voiced = pcmRms(chunk) >= this.cfg.vadThreshold;
@@ -225,7 +241,8 @@ class AthenaTalkBridge {
}
async transcribe(pcm) {
const form = new FormData();
form.append("file", new Blob([wavFromPcm16(pcm)], { type: "audio/wav" }), "talk.wav");
const wavBytes = Uint8Array.from(wavFromPcm16(pcm));
form.append("file", new Blob([wavBytes], { type: "audio/wav" }), "talk.wav");
form.append("model", "whisper-1");
form.append("language", this.cfg.language);
const response = await fetch(`${this.cfg.baseUrl}/audio/transcriptions`, {
@@ -303,6 +320,7 @@ class AthenaTalkBridge {
const response = await fetch(`${this.cfg.baseUrl}/audio/speech`, {
method: "POST",
headers: { "Content-Type": "application/json", ...this.authHeaders() },
// Athena normalizes the request and serves it through Qwen3-TTS.
body: JSON.stringify({ voice: this.cfg.voice, input: text, response_format: "wav" }),
signal,
});
@@ -338,8 +356,7 @@ export default definePluginEntry({
resolveConfig: ({ rawConfig }) => record(rawConfig),
isConfigured: ({ providerConfig, cfg }) => {
const raw = record(providerConfig);
const athena = record(record(record(cfg).models).providers).athena;
return Boolean(raw.baseUrl || record(athena).baseUrl);
return Boolean(raw.baseUrl || resolveModelProvider(cfg, raw.modelProvider).baseUrl);
},
createBridge: (req) => new AthenaTalkBridge(req, resolveConfig(req)),
});