agents
Version:
A home for your AI agents
122 lines (121 loc) • 3.69 kB
JavaScript
import { t as isChannelMessageSurface } from "../surface-bZZJqBka.js";
import "../voice/types.js";
//#region src/channels/adapters/voice.ts
const DEFAULT_SAMPLE_RATE = 16e3;
function validSurface(surface) {
if (!isChannelMessageSurface(surface) || surface.version !== 1 || surface.address === null || typeof surface.address !== "object" || Array.isArray(surface.address)) return false;
return surface.address.connection === "current";
}
function unavailable() {
return {
status: "failed",
retryable: true,
error: {
code: "BROWSER_VOICE_UNAVAILABLE",
message: "No browser voice surface is connected"
}
};
}
function synthesisFailure(error) {
return {
status: "failed",
retryable: true,
error: {
code: "BROWSER_VOICE_TTS_FAILED",
message: error instanceof Error ? error.message : "The text-to-speech provider did not return audio"
}
};
}
function deliveryFailure(error, contentDeliveryStarted) {
if (!contentDeliveryStarted) {
const connectionClosed = error instanceof TypeError && error.message.includes("WebSocket send() after close");
return {
status: "failed",
retryable: true,
error: {
code: connectionClosed ? "BROWSER_VOICE_CONNECTION_CLOSED" : "BROWSER_VOICE_DELIVERY_FAILED",
message: connectionClosed ? "The browser voice surface disconnected before delivery" : error instanceof Error ? error.message : "Browser voice delivery failed before sending content"
}
};
}
return {
status: "uncertain",
error: {
code: "BROWSER_VOICE_DELIVERY_ERROR",
message: error instanceof Error ? error.message : "Browser voice delivery failed with an unknown outcome"
}
};
}
function sendJSON(connection, message) {
connection.send(JSON.stringify(message));
}
/**
* Create an output-only channel that synthesizes one message and sends its
* encoded audio to one browser using the Cloudflare Voice wire protocol.
*
* The browser should connect with `VoiceClient` or `useVoiceAgent()`. No
* `withVoice()` mixin, microphone, or speech-to-text provider is required.
*/
function browserVoice(options) {
const audioFormat = options.audioFormat ?? "mp3";
const sampleRate = options.sampleRate ?? DEFAULT_SAMPLE_RATE;
const toSpeechText = options.toSpeechText ?? ((message) => message.markdown);
return {
isAvailable() {
return options.getConnection() !== void 0;
},
async deliver(surface, message) {
if (!validSurface(surface)) return {
status: "failed",
retryable: false,
error: {
code: "BROWSER_VOICE_SURFACE_INVALID",
message: `Browser voice cannot parse the address for Channel "${surface.channelKey}"`
}
};
const connection = options.getConnection();
if (!connection) return unavailable();
let audio;
const speechText = toSpeechText(message);
try {
audio = await options.tts.synthesize(speechText);
} catch (error) {
return synthesisFailure(error);
}
if (!audio) return synthesisFailure();
let contentDeliveryStarted = false;
try {
sendJSON(connection, {
type: "welcome",
protocol_version: 1
});
sendJSON(connection, {
type: "audio_config",
format: audioFormat,
sampleRate
});
sendJSON(connection, {
type: "status",
status: "speaking"
});
sendJSON(connection, {
type: "transcript",
role: "assistant",
text: speechText
});
contentDeliveryStarted = true;
connection.send(audio);
sendJSON(connection, {
type: "status",
status: "idle"
});
return { status: "delivered" };
} catch (error) {
return deliveryFailure(error, contentDeliveryStarted);
}
}
};
}
//#endregion
export { browserVoice };
//# sourceMappingURL=voice.js.map