async function createPlugin(ctx) { return { async execute(action, input = {}) { const backendCapability = String(ctx.getSetting("backendCapability", "tx.audio.backend") || "tx.audio.backend").trim() || "tx.audio.backend"; const mappedAction = mapAction(action); if (!mappedAction) { throw new Error(`Unknown action: ${action}`); } const result = await ctx.executeCapability(backendCapability, mappedAction, input, { skipTxSafety: true }); if (action === "audioStatus") { return normalizeAudioStatus(result, input); } return result; }, async health() { return { ok: true }; } }; } function mapAction(action) { switch (String(action || "")) { case "audioConnect": return "backendStart"; case "audioDisconnect": return "backendStop"; case "audioStatus": return "backendStatus"; case "audioRegisterClient": return "backendRegisterClient"; case "audioUnregisterClient": return "backendUnregisterClient"; case "audioWriteChunk": return "backendWrite"; default: return null; } } function normalizeAudioStatus(result, input) { const raw = result && typeof result === "object" ? result : {}; const userId = String((input && input.userId) || ""); const running = Boolean(raw.running); const ownerUserId = raw.ownerUserId ? String(raw.ownerUserId) : null; const enabled = raw.enabled !== false; return { enabled, state: raw.state || (enabled ? (running ? "running" : "disconnected") : "disabled"), running, clients: Number.isFinite(Number(raw.clients)) ? Number(raw.clients) : 0, ownerUserId, ownerMatchesCaller: Boolean(ownerUserId && userId && ownerUserId === userId), startedAt: raw.startedAt || null, lastError: raw.lastError || null, lastExit: raw.lastExit || null, ffmpegPath: raw.ffmpegPath || null, alsaDevice: raw.alsaDevice || null, chunkMs: Number.isFinite(Number(raw.chunkMs)) ? Number(raw.chunkMs) : null, wsPath: raw.wsPath || null, backend: { providerId: raw.providerId || null, providerEnabled: raw.providerEnabled !== false, state: raw.state || null } }; } module.exports = { createPlugin };