Files
VolcanicAgenticCore/hosts/Volcanic.AgenticCore.Telegram/Speech/ElevenLabsOptions.cs
EugeneTes 7fef3ce94e feat(telegram): ElevenLabs text to speech for the voice tool
ElevenLabs speaks for the voice tool when ELEVENLABS_API_KEY and ELEVENLABS_VOICE_ID
are set (TTS_PROVIDER=elevenlabs|openai picks, the other is the fallback). Opus output
(opus_48000_64) goes to Telegram as is; voice names resolve through /v2/voices;
instructions become audio tags on eleven_v3 and are ignored on other models. The key
is redacted from tool output and never reaches agent processes.
2026-10-14 13:07:36 +00:00

45 lines
2.4 KiB
C#

namespace Volcanic.AgenticCore.Telegram.Speech;
/// <summary>
/// The <c>Telegram:Speech:ElevenLabs</c> section: ElevenLabs text to speech for the <c>voice</c> tool. Configured exactly
/// when both <see cref="ApiKey"/> and <see cref="VoiceId"/> are set.
/// </summary>
public sealed class ElevenLabsOptions
{
public const string DefaultBaseUrl = "https://api.elevenlabs.io";
public const string DefaultModel = "eleven_multilingual_v2";
/// <summary>OGG/Opus, which Telegram shows as a voice note.</summary>
public const string DefaultOutputFormat = "opus_48000_64";
/// <summary>Falls back to <c>ELEVENLABS_API_KEY</c>.</summary>
public string ApiKey { get; set; } = "";
/// <summary>Falls back to <c>ELEVENLABS_VOICE_ID</c>. A voice id, or the name of a voice in the account's library.</summary>
public string VoiceId { get; set; } = "";
/// <summary>Falls back to <c>ELEVENLABS_MODEL</c>, then <see cref="DefaultModel"/>.</summary>
public string Model { get; set; } = "";
public string BaseUrl { get; set; } = DefaultBaseUrl;
/// <summary>An <c>opus_48000_*</c> format; others aren't OGG/Opus and are refused.</summary>
public string OutputFormat { get; set; } = DefaultOutputFormat;
public bool IsConfigured => !string.IsNullOrWhiteSpace(ApiKey) && !string.IsNullOrWhiteSpace(VoiceId);
public string ResolvedModel => string.IsNullOrWhiteSpace(Model) ? DefaultModel : Model.Trim();
/// <summary>Only v3 models read audio tags such as <c>[whispers]</c>; older models would speak them aloud.</summary>
public bool SupportsAudioTags => ResolvedModel.StartsWith("eleven_v3", StringComparison.OrdinalIgnoreCase);
/// <summary>Reads the environment shortcuts; the environment fills only empty settings, and an empty variable counts as unset.</summary>
public void ApplyEnvironment(Func<string, string?> getEnv)
{
ArgumentNullException.ThrowIfNull(getEnv);
if (string.IsNullOrWhiteSpace(ApiKey) && getEnv("ELEVENLABS_API_KEY") is { } key && !string.IsNullOrWhiteSpace(key)) ApiKey = key.Trim();
if (string.IsNullOrWhiteSpace(VoiceId) && getEnv("ELEVENLABS_VOICE_ID") is { } voice && !string.IsNullOrWhiteSpace(voice)) VoiceId = voice.Trim();
if (string.IsNullOrWhiteSpace(Model) && getEnv("ELEVENLABS_MODEL") is { } model && !string.IsNullOrWhiteSpace(model)) Model = model.Trim();
}
}