Reuse Athena router key for OpenClaw dictation
This commit is contained in:
@@ -67,8 +67,10 @@ the 8 kHz G.711 audio through the Gateway. The plugin converts it to PCM WAV
|
|||||||
and calls the same Athena `/audio/transcriptions` endpoint used by Talk. The
|
and calls the same Athena `/audio/transcriptions` endpoint used by Talk. The
|
||||||
transcribed text is returned to the composer; this path does not invoke the
|
transcribed text is returned to the composer; this path does not invoke the
|
||||||
agent or TTS. The transcription provider reuses `talk.realtime.providers.athena-talk`
|
agent or TTS. The transcription provider reuses `talk.realtime.providers.athena-talk`
|
||||||
and the configured model provider for its URL/key, so no second credential is
|
and the configured model provider for its URL/key. If that model provider has
|
||||||
needed. In `talk.catalog`, it appears under `transcription.providers`. OpenClaw
|
no key, it reuses `tts.providers.openai.apiKey` only when the TTS and STT URLs
|
||||||
|
have the same origin. No second credential is needed. In `talk.catalog`, it
|
||||||
|
appears under `transcription.providers`. OpenClaw
|
||||||
currently gives a transcription provider five seconds to return its final text
|
currently gives a transcription provider five seconds to return its final text
|
||||||
after recording stops; the plugin caps its Whisper request at 4.5 seconds.
|
after recording stops; the plugin caps its Whisper request at 4.5 seconds.
|
||||||
|
|
||||||
|
|||||||
+11
-2
@@ -95,10 +95,19 @@ function resolveModelProvider(cfg, requested) {
|
|||||||
function resolveConfig(req) {
|
function resolveConfig(req) {
|
||||||
const raw = record(req.providerConfig);
|
const raw = record(req.providerConfig);
|
||||||
const modelProvider = resolveModelProvider(req.cfg, raw.modelProvider);
|
const modelProvider = resolveModelProvider(req.cfg, raw.modelProvider);
|
||||||
|
const baseUrl = String(raw.baseUrl || modelProvider.baseUrl || "http://192.168.1.212:8081/v1").replace(/\/$/, "");
|
||||||
|
const tts = record(record(record(req.cfg).tts).providers).openai;
|
||||||
|
let sharedTtsKey = "";
|
||||||
|
try {
|
||||||
|
if (new URL(String(record(tts).baseUrl)).origin === new URL(baseUrl).origin) {
|
||||||
|
sharedTtsKey = typeof record(tts).apiKey === "string" ? record(tts).apiKey : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { /* The TTS provider may be absent or use another URL. */ }
|
||||||
return {
|
return {
|
||||||
modelProvider: String(raw.modelProvider || ""),
|
modelProvider: String(raw.modelProvider || ""),
|
||||||
baseUrl: String(raw.baseUrl || modelProvider.baseUrl || "http://192.168.1.212:8081/v1").replace(/\/$/, ""),
|
baseUrl,
|
||||||
apiKey: String(raw.apiKey || modelProvider.apiKey || ""),
|
apiKey: String(raw.apiKey || modelProvider.apiKey || sharedTtsKey || ""),
|
||||||
voice: String(raw.voice || req.voice || "alloy"),
|
voice: String(raw.voice || req.voice || "alloy"),
|
||||||
language: String(raw.language || req.language || "de"),
|
language: String(raw.language || req.language || "de"),
|
||||||
vadThreshold: Number(raw.vadThreshold ?? 0.018),
|
vadThreshold: Number(raw.vadThreshold ?? 0.018),
|
||||||
|
|||||||
@@ -114,10 +114,18 @@ function resolveModelProvider(cfg: unknown, requested?: unknown): Record<string,
|
|||||||
function resolveConfig(req: any): Required<ProviderConfig> {
|
function resolveConfig(req: any): Required<ProviderConfig> {
|
||||||
const raw = record(req.providerConfig);
|
const raw = record(req.providerConfig);
|
||||||
const modelProvider = resolveModelProvider(req.cfg, raw.modelProvider);
|
const modelProvider = resolveModelProvider(req.cfg, raw.modelProvider);
|
||||||
|
const baseUrl = String(raw.baseUrl || modelProvider.baseUrl || "http://192.168.1.212:8081/v1").replace(/\/$/, "");
|
||||||
|
const tts = record(record(record(req.cfg).tts).providers).openai;
|
||||||
|
let sharedTtsKey = "";
|
||||||
|
try {
|
||||||
|
if (new URL(String(record(tts).baseUrl)).origin === new URL(baseUrl).origin) {
|
||||||
|
sharedTtsKey = typeof record(tts).apiKey === "string" ? record(tts).apiKey : "";
|
||||||
|
}
|
||||||
|
} catch { /* The TTS provider may be absent or use another URL. */ }
|
||||||
return {
|
return {
|
||||||
modelProvider: String(raw.modelProvider || ""),
|
modelProvider: String(raw.modelProvider || ""),
|
||||||
baseUrl: String(raw.baseUrl || modelProvider.baseUrl || "http://192.168.1.212:8081/v1").replace(/\/$/, ""),
|
baseUrl,
|
||||||
apiKey: String(raw.apiKey || modelProvider.apiKey || ""),
|
apiKey: String(raw.apiKey || modelProvider.apiKey || sharedTtsKey || ""),
|
||||||
voice: String(raw.voice || req.voice || "alloy"),
|
voice: String(raw.voice || req.voice || "alloy"),
|
||||||
language: String(raw.language || req.language || "de"),
|
language: String(raw.language || req.language || "de"),
|
||||||
vadThreshold: Number(raw.vadThreshold ?? 0.018),
|
vadThreshold: Number(raw.vadThreshold ?? 0.018),
|
||||||
|
|||||||
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@casaderoll/openclaw-athena-talk",
|
"name": "@casaderoll/openclaw-athena-talk",
|
||||||
"version": "1.2.0",
|
"version": "1.2.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@casaderoll/openclaw-athena-talk",
|
"name": "@casaderoll/openclaw-athena-talk",
|
||||||
"version": "1.2.0",
|
"version": "1.2.1",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^24.0.0",
|
"@types/node": "^24.0.0",
|
||||||
"openclaw": "2026.9.4",
|
"openclaw": "2026.9.4",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@casaderoll/openclaw-athena-talk",
|
"name": "@casaderoll/openclaw-athena-talk",
|
||||||
"version": "1.2.0",
|
"version": "1.2.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Local OpenClaw Talk provider backed by Athena Whisper and Qwen3-TTS",
|
"description": "Local OpenClaw Talk provider backed by Athena Whisper and Qwen3-TTS",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -57,3 +57,25 @@ test("dictation registers separately and sends G.711 audio to Athena Whisper", a
|
|||||||
await new Promise((resolve) => server.close(resolve));
|
await new Promise((resolve) => server.close(resolve));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("dictation reuses only a TTS key for the same Athena origin", () => {
|
||||||
|
let transcription;
|
||||||
|
plugin.register({
|
||||||
|
registerRealtimeTranscriptionProvider: (value) => { transcription = value; },
|
||||||
|
registerRealtimeVoiceProvider: () => {},
|
||||||
|
registerHttpRoute: () => {},
|
||||||
|
});
|
||||||
|
const base = {
|
||||||
|
talk: { realtime: { providers: { "athena-talk": { modelProvider: "llama-cpp" } } } },
|
||||||
|
models: { providers: { "llama-cpp": { baseUrl: "http://athena:8081/v1" } } },
|
||||||
|
};
|
||||||
|
const withTts = (baseUrl) => ({ ...base, tts: { providers: { openai: {
|
||||||
|
baseUrl, apiKey: "shared-key",
|
||||||
|
} } } });
|
||||||
|
assert.equal(transcription.resolveConfig({
|
||||||
|
cfg: withTts("http://athena:8081/v1"), rawConfig: {},
|
||||||
|
}).apiKey, "shared-key");
|
||||||
|
assert.equal(transcription.resolveConfig({
|
||||||
|
cfg: withTts("http://other:8081/v1"), rawConfig: {},
|
||||||
|
}).apiKey, "");
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user